Commit 21b082b0 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'leipert-generalize-startup-js-for-graphql' into 'master'

Try to generalize GraphQL startupJS approach

See merge request gitlab-org/gitlab!45060
parents ceafd6c9 78e71f16
......@@ -5,6 +5,7 @@ import { ApolloLink } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
import csrf from '~/lib/utils/csrf';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
export const fetchPolicies = {
CACHE_FIRST: 'cache-first',
......@@ -62,7 +63,7 @@ export default (resolvers = {}, config = {}) => {
return new ApolloClient({
typeDefs: config.typeDefs,
link: ApolloLink.from([performanceBarLink, uploadsLink]),
link: ApolloLink.from([performanceBarLink, new StartupJSLink(), uploadsLink]),
cache: new InMemoryCache({
...config.cacheConfig,
freezeResults: config.assumeImmutableResults,
......
import { ApolloLink, Observable } from 'apollo-link';
import { parse } from 'graphql';
import { isEqual, pickBy } from 'lodash';
/**
* Remove undefined values from object
* @param obj
* @returns {Dictionary<unknown>}
*/
const pickDefinedValues = obj => pickBy(obj, x => x !== undefined);
/**
* Compares two set of variables, order independent
*
* Ignores undefined values (in the top level) and supports arrays etc.
*/
const variablesMatch = (var1 = {}, var2 = {}) => {
return isEqual(pickDefinedValues(var1), pickDefinedValues(var2));
};
export class StartupJSLink extends ApolloLink {
constructor() {
super();
this.startupCalls = new Map();
this.parseStartupCalls(window.gl?.startup_graphql_calls || []);
}
// Extract operationNames from the queries and ensure that we can
// match operationName => element from result array
parseStartupCalls(calls) {
calls.forEach(call => {
const { query, variables, fetchCall } = call;
const operationName = parse(query)?.definitions?.find(x => x.kind === 'OperationDefinition')
?.name?.value;
if (operationName) {
this.startupCalls.set(operationName, {
variables,
fetchCall,
});
}
});
}
static noopRequest = (operation, forward) => forward(operation);
disable() {
this.request = StartupJSLink.noopRequest;
this.startupCalls = null;
}
request(operation, forward) {
// Disable StartupJSLink in case all calls are done or none are set up
if (this.startupCalls && this.startupCalls.size === 0) {
this.disable();
return forward(operation);
}
const { operationName } = operation;
// Skip startup call if the operationName doesn't match
if (!this.startupCalls.has(operationName)) {
return forward(operation);
}
const { variables: startupVariables, fetchCall } = this.startupCalls.get(operationName);
this.startupCalls.delete(operationName);
// Skip startup call if the variables values do not match
if (!variablesMatch(startupVariables, operation.variables)) {
return forward(operation);
}
return new Observable(observer => {
fetchCall
.then(response => {
// Handle HTTP errors
if (!response.ok) {
throw new Error('fetchCall failed');
}
operation.setContext({ response });
return response.json();
})
.then(result => {
if (result && (result.errors || !result.data)) {
throw new Error('Received GraphQL error');
}
// we have data and can send it to back up the link chain
observer.next(result);
observer.complete();
})
.catch(() => {
forward(operation).subscribe({
next: result => {
observer.next(result);
},
error: error => {
observer.error(error);
},
complete: observer.complete.bind(observer),
});
});
});
}
}
......@@ -6,12 +6,12 @@ import {
GlDropdownItem,
GlIcon,
} from '@gitlab/ui';
import permissionsQuery from 'shared_queries/repository/permissions.query.graphql';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import { __ } from '../../locale';
import getRefMixin from '../mixins/get_ref';
import projectShortPathQuery from '../queries/project_short_path.query.graphql';
import projectPathQuery from '../queries/project_path.query.graphql';
import permissionsQuery from '../queries/permissions.query.graphql';
const ROW_TYPES = {
header: 'header',
......
<script>
import filesQuery from 'shared_queries/repository/files.query.graphql';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __ } from '../../locale';
import FileTable from './table/index.vue';
import getRefMixin from '../mixins/get_ref';
import filesQuery from '../queries/files.query.graphql';
import projectPathQuery from '../queries/project_path.query.graphql';
import FilePreview from './preview/index.vue';
import { readmeFile } from '../utils/readme';
......
import Vue from 'vue';
import PathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { escapeFileUrl } from '../lib/utils/url_utility';
import createRouter from './router';
import App from './components/app.vue';
......@@ -19,10 +18,6 @@ export default function setupVueRepositoryList() {
const { dataset } = el;
const { projectPath, projectShortPath, ref, escapedRef, fullName } = dataset;
const router = createRouter(projectPath, escapedRef);
const pathRegex = /-\/tree\/[^/]+\/(.+$)/;
const matches = window.location.href.match(pathRegex);
const currentRoutePath = matches ? matches[1] : '';
apolloProvider.clients.defaultClient.cache.writeData({
data: {
......@@ -48,28 +43,7 @@ export default function setupVueRepositoryList() {
},
});
if (window.gl.startup_graphql_calls) {
const query = window.gl.startup_graphql_calls.find(
call => call.operationName === 'pathLastCommit',
);
query.fetchCall
.then(res => res.json())
.then(res => {
apolloProvider.clients.defaultClient.writeQuery({
query: PathLastCommitQuery,
data: res.data,
variables: {
projectPath,
ref,
path: currentRoutePath,
},
});
})
.catch(() => {})
.finally(() => initLastCommitApp());
} else {
initLastCommitApp();
}
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);
......
import filesQuery from '../queries/files.query.graphql';
import filesQuery from 'shared_queries/repository/files.query.graphql';
import getRefMixin from './get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
fragment PageInfo on PageInfo {
__typename
hasNextPage
hasPreviousPage
startCursor
endCursor
}
fragment TreeEntry on Entry {
__typename
id
sha
name
......@@ -16,10 +23,15 @@ query getFiles(
$nextPageCursor: String
) {
project(fullPath: $projectPath) {
__typename
repository {
__typename
tree(path: $path, ref: $ref) {
__typename
trees(first: $pageSize, after: $nextPageCursor) {
__typename
edges {
__typename
node {
...TreeEntry
webPath
......@@ -30,7 +42,9 @@ query getFiles(
}
}
submodules(first: $pageSize, after: $nextPageCursor) {
__typename
edges {
__typename
node {
...TreeEntry
webUrl
......@@ -42,7 +56,9 @@ query getFiles(
}
}
blobs(first: $pageSize, after: $nextPageCursor) {
__typename
edges {
__typename
node {
...TreeEntry
mode
......
query getPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
__typename
userPermissions {
__typename
pushCode
forkProject
createMergeRequestIn
......
......@@ -25,7 +25,7 @@
};
gl.startup_graphql_calls = gl.startup_graphql_calls.map(call => ({
operationName: call.query.match(/^query (.+)\(/)[1],
...call,
fetchCall: fetch(url, {
...opts,
credentials: 'same-origin',
......
- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path })
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
- add_page_startup_graphql_call('repository/permissions', { projectPath: @project.full_path })
- add_page_startup_graphql_call('repository/files', { nextPageCursor: "", pageSize: 100, projectPath: @project.full_path, ref: current_ref, path: current_route_path || "/"})
- breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment