Commit bdd5faad authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'leipert-update-vue-apollo' into 'master'

Update to ApolloClient@3

See merge request gitlab-org/gitlab!55062
parents 8c062bbb f07ac8a5
......@@ -22,6 +22,7 @@ query accessTokensGetProjects(
avatarUrl
}
pageInfo {
__typename
...PageInfo
}
}
......
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloLink, Observable } from '@apollo/client/core';
import { print } from 'graphql';
import cable from '~/actioncable_consumer';
import { uuids } from '~/lib/utils/uuids';
......
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
......
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './graphql/fragmentTypes.json';
import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
Vue.use(VueApollo);
const resolvers = {
......@@ -55,9 +49,5 @@ const resolvers = {
};
export default new VueApollo({
defaultClient: createDefaultClient(resolvers, {
cacheConfig: {
fragmentMatcher,
},
}),
defaultClient: createDefaultClient(resolvers),
});
{"__schema":{"types":[{"kind":"UNION","name":"AlertManagementIntegration","possibleTypes":[{"name":"AlertManagementHttpIntegration"},{"name":"AlertManagementPrometheusIntegration"}]}]}}
......@@ -5,6 +5,7 @@ query getIntegrations($projectPath: ID!) {
id
alertManagementIntegrations {
nodes {
__typename
...IntegrationItem
}
}
......
fragment Count on UsageTrendsMeasurement {
__typename
count
recordedAt
}
import { IntrospectionFragmentMatcher, defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from '~/sidebar/fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export const gqlClient = createDefaultClient(
{},
......@@ -14,8 +9,6 @@ export const gqlClient = createDefaultClient(
// eslint-disable-next-line no-underscore-dangle
return object.__typename === 'BoardList' ? object.iid : defaultDataIdFromObject(object);
},
fragmentMatcher,
},
},
);
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue';
import store from '~/boards/stores';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import introspectionQueryResultData from '~/sidebar/fragmentTypes.json';
Vue.use(VueApollo);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
},
),
defaultClient: createDefaultClient(),
});
export default (params = {}) => {
......
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloLink, Observable } from '@apollo/client/core';
export const apolloCaptchaLink = new ApolloLink((operation, forward) =>
forward(operation).flatMap((result) => {
......
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { vulnerabilityLocationTypes } from '~/graphql_shared/fragment_types/vulnerability_location_types';
Vue.use(VueApollo);
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: vulnerabilityLocationTypes,
});
const defaultClient = createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
},
);
const defaultClient = createDefaultClient();
export default new VueApollo({
defaultClient,
......
import { defaultDataIdFromObject, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import produce from 'immer';
import { uniqueId } from 'lodash';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import introspectionQueryResultData from './graphql/fragmentTypes.json';
import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql';
import getDesignQuery from './graphql/queries/get_design.query.graphql';
import typeDefs from './graphql/typedefs.graphql';
......@@ -13,10 +12,6 @@ import { addPendingTodoToStore } from './utils/cache_update';
import { extractTodoIdFromDeletePath, createPendingTodo } from './utils/design_management_utils';
import { CREATE_DESIGN_TODO_EXISTS_ERROR } from './utils/error_messages';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
Vue.use(VueApollo);
const resolvers = {
......@@ -85,7 +80,6 @@ const defaultClient = createDefaultClient(
}
return defaultDataIdFromObject(object);
},
fragmentMatcher,
},
typeDefs,
},
......
{"__schema":{"types":[{"kind":"INTERFACE","name":"User","possibleTypes":[{"name":"UserCore"}]},{"kind":"UNION","name":"NoteableType","possibleTypes":[{"name":"Design"},{"name":"Issue"},{"name":"MergeRequest"}]}]}}
export const vulnerabilityLocationTypes = {
__schema: {
types: [
{
kind: 'UNION',
name: 'VulnerabilityLocation',
possibleTypes: [
{ name: 'VulnerabilityLocationContainerScanning' },
{ name: 'VulnerabilityLocationDast' },
{ name: 'VulnerabilityLocationDependencyScanning' },
{ name: 'VulnerabilityLocationSast' },
{ name: 'VulnerabilityLocationSecretDetection' },
],
},
],
},
};
{"AlertManagementIntegration":["AlertManagementHttpIntegration","AlertManagementPrometheusIntegration"],"CurrentUserTodos":["BoardEpic","Design","Epic","EpicIssue","Issue","MergeRequest"],"DependencyLinkMetadata":["NugetDependencyLinkMetadata"],"DesignFields":["Design","DesignAtVersion"],"Entry":["Blob","Submodule","TreeEntry"],"Eventable":["BoardEpic","Epic"],"Issuable":["Epic","Issue","MergeRequest"],"JobNeedUnion":["CiBuildNeed","CiJob"],"MemberInterface":["GroupMember","ProjectMember"],"NoteableInterface":["AlertManagementAlert","BoardEpic","Design","Epic","EpicIssue","Issue","MergeRequest","Snippet","Vulnerability"],"NoteableType":["Design","Issue","MergeRequest"],"OrchestrationPolicy":["ScanExecutionPolicy","ScanResultPolicy"],"PackageFileMetadata":["ConanFileMetadata","HelmFileMetadata"],"PackageMetadata":["ComposerMetadata","ConanMetadata","MavenMetadata","NugetMetadata","PypiMetadata"],"ResolvableInterface":["Discussion","Note"],"Service":["BaseService","JiraService"],"TimeboxReportInterface":["Iteration","Milestone"],"User":["MergeRequestAssignee","MergeRequestReviewer","UserCore"],"VulnerabilityDetail":["VulnerabilityDetailBase","VulnerabilityDetailBoolean","VulnerabilityDetailCode","VulnerabilityDetailCommit","VulnerabilityDetailDiff","VulnerabilityDetailFileLocation","VulnerabilityDetailInt","VulnerabilityDetailList","VulnerabilityDetailMarkdown","VulnerabilityDetailModuleLocation","VulnerabilityDetailTable","VulnerabilityDetailText","VulnerabilityDetailUrl"],"VulnerabilityLocation":["VulnerabilityLocationClusterImageScanning","VulnerabilityLocationContainerScanning","VulnerabilityLocationCoverageFuzzing","VulnerabilityLocationDast","VulnerabilityLocationDependencyScanning","VulnerabilityLocationGeneric","VulnerabilityLocationSast","VulnerabilityLocationSecretDetection"]}
......@@ -181,6 +181,9 @@ export default {
return data[this.namespace]?.issues.nodes ?? [];
},
result({ data }) {
if (!data) {
return;
}
this.pageInfo = data[this.namespace]?.issues.pageInfo ?? {};
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
},
......
fragment IssueFragment on Issue {
__typename
id
iid
closedAt
......@@ -18,6 +19,7 @@ fragment IssueFragment on Issue {
webUrl
assignees {
nodes {
__typename
id
avatarUrl
name
......@@ -26,6 +28,7 @@ fragment IssueFragment on Issue {
}
}
author {
__typename
id
avatarUrl
name
......
......@@ -51,7 +51,9 @@ export default {
},
data() {
return {
jobs: {},
jobs: {
list: [],
},
hasError: false,
isAlertDismissed: false,
scope: null,
......
import { ApolloLink } from 'apollo-link';
import { ApolloLink } from '@apollo/client/core';
import { memoize } from 'lodash';
export const FEATURE_CATEGORY_HEADER = 'x-gitlab-feature-category';
......
import { Observable } from 'apollo-link';
import { onError } from 'apollo-link-error';
import { Observable } from '@apollo/client/core';
import { onError } from '@apollo/client/link/error';
import { isNavigatingAway } from '~/lib/utils/is_navigating_away';
/**
......
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { HttpLink } from 'apollo-link-http';
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink } from '@apollo/client/core';
import { BatchHttpLink } from '@apollo/client/link/batch-http';
import { createUploadLink } from 'apollo-upload-client';
import ActionCableLink from '~/actioncable_link';
import { apolloCaptchaLink } from '~/captcha/apollo_captcha_link';
import possibleTypes from '~/graphql_shared/possibleTypes.json';
import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
import csrf from '~/lib/utils/csrf';
import { objectToQuery, queryToObject } from '~/lib/utils/url_utility';
......@@ -21,6 +19,33 @@ export const fetchPolicies = {
CACHE_ONLY: 'cache-only',
};
export const typePolicies = {
Repository: {
merge: true,
},
UserPermissions: {
merge: true,
},
MergeRequestPermissions: {
merge: true,
},
ContainerRepositoryConnection: {
merge: true,
},
TimelogConnection: {
merge: true,
},
BranchList: {
merge: true,
},
InstanceSecurityDashboard: {
merge: true,
},
PipelinePermissions: {
merge: true,
},
};
export const stripWhitespaceFromQuery = (url, path) => {
/* eslint-disable-next-line no-unused-vars */
const [_, params] = url.split(path);
......@@ -46,6 +71,30 @@ export const stripWhitespaceFromQuery = (url, path) => {
return `${path}?${reassembled}`;
};
const acs = [];
let pendingApolloMutations = 0;
// ### Why track pendingApolloMutations, but calculate pendingApolloRequests?
//
// In Apollo 2, we had a single link for counting operations.
//
// With Apollo 3, the `forward().map(...)` of deduped queries is never called.
// So, we resorted to calculating the sum of `inFlightLinkObservables?.size`.
// However! Mutations don't use `inFLightLinkObservables`, but since they are likely
// not deduped we can count them...
//
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55062#note_838943715
// https://www.apollographql.com/docs/react/v2/networking/network-layer/#query-deduplication
Object.defineProperty(window, 'pendingApolloRequests', {
get() {
return acs.reduce(
(sum, ac) => sum + (ac?.queryManager?.inFlightLinkObservables?.size || 0),
pendingApolloMutations,
);
},
});
export default (resolvers = {}, config = {}) => {
const {
baseUrl,
......@@ -56,6 +105,7 @@ export default (resolvers = {}, config = {}) => {
path = '/api/graphql',
useGet = false,
} = config;
let ac = null;
let uri = `${gon.relative_url_root || ''}${path}`;
if (baseUrl) {
......@@ -75,16 +125,6 @@ export default (resolvers = {}, config = {}) => {
batchMax,
};
const requestCounterLink = new ApolloLink((operation, forward) => {
window.pendingApolloRequests = window.pendingApolloRequests || 0;
window.pendingApolloRequests += 1;
return forward(operation).map((response) => {
window.pendingApolloRequests -= 1;
return response;
});
});
/*
This custom fetcher intervention is to deal with an issue where we are using GET to access
eTag polling, but Apollo Client adds excessive whitespace, which causes the
......@@ -138,6 +178,22 @@ export default (resolvers = {}, config = {}) => {
);
};
const hasMutation = (operation) =>
(operation?.query?.definitions || []).some((x) => x.operation === 'mutation');
const requestCounterLink = new ApolloLink((operation, forward) => {
if (hasMutation(operation)) {
pendingApolloMutations += 1;
}
return forward(operation).map((response) => {
if (hasMutation(operation)) {
pendingApolloMutations -= 1;
}
return response;
});
});
const appLink = ApolloLink.split(
hasSubscriptionOperation,
new ActionCableLink(),
......@@ -155,19 +211,23 @@ export default (resolvers = {}, config = {}) => {
),
);
return new ApolloClient({
ac = new ApolloClient({
typeDefs,
link: appLink,
cache: new InMemoryCache({
typePolicies,
possibleTypes,
...cacheConfig,
freezeResults: true,
}),
resolvers,
assumeImmutableResults: true,
defaultOptions: {
query: {
fetchPolicy,
},
},
});
acs.push(ac);
return ac;
};
import { ApolloLink, Observable } from 'apollo-link';
import { ApolloLink, Observable } from '@apollo/client/core';
import { parse } from 'graphql';
import { isEqual, pickBy } from 'lodash';
......
......@@ -96,6 +96,9 @@ export default {
return data[this.graphqlResource]?.containerRepositories.nodes;
},
result({ data }) {
if (!data) {
return;
}
this.pageInfo = data[this.graphqlResource]?.containerRepositories?.pageInfo;
this.containerRepositoriesCount = data[this.graphqlResource]?.containerRepositoriesCount;
},
......
{
"__schema": {
"types": [
{
"kind": "UNION",
"name": "PackageMetadata",
"possibleTypes": [
{ "name": "ComposerMetadata" },
{ "name": "ConanMetadata" },
{ "name": "MavenMetadata" },
{ "name": "NugetMetadata" },
{ "name": "PypiMetadata" }
]
}
]
}
}
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
Vue.use(VueApollo);
export const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
},
),
defaultClient: createDefaultClient(),
});
......@@ -49,7 +49,7 @@ export default {
pipelineEtag: {
query: getPipelineEtag,
update(data) {
return data.etags.pipeline;
return data.etags?.pipeline;
},
},
pipeline: {
......
......@@ -196,7 +196,7 @@ export default {
currentBranch: {
query: getCurrentBranch,
update(data) {
return data.workBranches.current.name;
return data.workBranches?.current?.name;
},
},
starterTemplate: {
......@@ -214,7 +214,7 @@ export default {
return data.project?.ciTemplate?.content || '';
},
result({ data }) {
this.updateCiConfig(data.project?.ciTemplate?.content || '');
this.updateCiConfig(data?.project?.ciTemplate?.content || '');
},
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
......
......@@ -36,6 +36,9 @@ export default {
return data.project?.pipeline?.jobs?.nodes || [];
},
result({ data }) {
if (!data) {
return;
}
this.jobsPageInfo = data.project?.pipeline?.jobs?.pageInfo || {};
},
error() {
......
{"__schema":{"types":[{"kind":"UNION","name":"JobNeedUnion","possibleTypes":[{"name":"CiBuildNeed"},{"name":"CiJob"}]}]}}
\ No newline at end of file
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './graphql/fragmentTypes.json';
export const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
useGet: true,
},
),
......
{"__schema":{"types":[{"kind":"INTERFACE","name":"Entry","possibleTypes":[{"name":"Blob"},{"name":"Submodule"},{"name":"TreeEntry"}]}]}}
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import introspectionQueryResultData from './fragmentTypes.json';
import { fetchLogsTree } from './log_tree';
Vue.use(VueApollo);
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
const defaultClient = createDefaultClient(
{
Query: {
......@@ -43,7 +35,6 @@ const defaultClient = createDefaultClient(
},
{
cacheConfig: {
fragmentMatcher,
dataIdFromObject: (obj) => {
/* eslint-disable @gitlab/require-i18n-strings */
// eslint-disable-next-line no-underscore-dangle
......
......@@ -28,10 +28,12 @@ query getGroupRunners(
edges {
webUrl
node {
__typename
...RunnerNode
}
}
pageInfo {
__typename
...PageInfo
}
}
......
......@@ -4,6 +4,7 @@ query getRunner($id: CiRunnerID!) {
# We have an id in deeply nested fragment
# eslint-disable-next-line @graphql-eslint/require-id-when-available
runner(id: $id) {
__typename
...RunnerDetails
}
}
......@@ -29,6 +29,7 @@ query getRunners(
editAdminUrl
}
pageInfo {
__typename
...PageInfo
}
}
......
fragment RunnerNode on CiRunner {
__typename
id
description
runnerType
......
......@@ -96,6 +96,9 @@ export default {
return data.workspace?.issuable;
},
result({ data }) {
if (!data) {
return;
}
const issuable = data.workspace?.issuable;
if (issuable) {
this.selected = cloneDeep(issuable.assignees.nodes);
......
......@@ -66,6 +66,9 @@ export default {
return data.workspace?.issuable?.confidential || false;
},
result({ data }) {
if (!data) {
return;
}
this.$emit('confidentialityUpdated', data.workspace?.issuable?.confidential);
},
error() {
......
......@@ -86,6 +86,9 @@ export default {
return data.workspace?.issuable || {};
},
result({ data }) {
if (!data) {
return;
}
this.$emit(`${this.dateType}Updated`, data.workspace?.issuable?.[this.dateType]);
},
error() {
......
......@@ -61,6 +61,9 @@ export default {
return data.workspace?.issuable?.subscribed || false;
},
result({ data }) {
if (!data) {
return;
}
this.emailsDisabled = this.parentIsGroup
? data.workspace?.emailsDisabled
: data.workspace?.issuable?.emailsDisabled;
......
......@@ -59,6 +59,10 @@ export default {
return data.workspace?.issuable?.currentUserTodos.nodes[0]?.id;
},
result({ data }) {
if (!data) {
return;
}
const currentUserTodos = data.workspace?.issuable?.currentUserTodos?.nodes ?? [];
this.todoId = currentUserTodos[0]?.id;
this.$emit('todoUpdated', currentUserTodos.length > 0);
......
{"__schema":{"types":[{"kind":"UNION","name":"Issuable","possibleTypes":[{"name":"Issue"},{"name":"MergeRequest"}]}, {"kind":"INTERFACE","name":"User","possibleTypes":[{"name":"UserCore"}]}]}}
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import produce from 'immer';
import VueApollo from 'vue-apollo';
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
import { resolvers as workItemResolvers } from '~/work_items/graphql/resolvers';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
const resolvers = {
...workItemResolvers,
......@@ -24,11 +18,7 @@ const resolvers = {
},
};
export const defaultClient = createDefaultClient(resolvers, {
cacheConfig: {
fragmentMatcher,
},
});
export const defaultClient = createDefaultClient(resolvers);
export const apolloProvider = new VueApollo({
defaultClient,
......
import { defaultDataIdFromObject } from '@apollo/client/core';
import { GlToast } from '@gitlab/ui';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
......
......@@ -3,7 +3,6 @@ query getState($projectPath: ID!, $iid: String!) {
id
archived
onlyAllowMergeIfPipelineSucceeds
mergeRequest(iid: $iid) {
id
autoMergeEnabled
......
......@@ -4,6 +4,7 @@ query autoMergeEnabled($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
id
mergeRequest(iid: $iid) {
id
...autoMergeEnabled
}
}
......
......@@ -2,6 +2,7 @@
query readyToMerge($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
id
...ReadyToMerge
}
}
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
......
......@@ -102,8 +102,8 @@ export default {
error(error) {
this.showError(error);
},
result({ loading }) {
if (loading) {
result({ loading, data }) {
if (loading || !data) {
return;
}
......
{"__schema":{"types":[{"kind":"INTERFACE","name":"LocalWorkItemWidget","possibleTypes":[{"name":"LocalTitleWidget"}]}]}}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import workItemQuery from './work_item.query.graphql';
import introspectionQueryResultData from './fragmentTypes.json';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export function createApolloProvider() {
Vue.use(VueApollo);
const defaultClient = createDefaultClient(resolvers, {
cacheConfig: {
fragmentMatcher,
},
typeDefs,
});
......
......@@ -150,6 +150,8 @@ function generateEntries() {
}
const alias = {
// Map Apollo client to apollo/client/core to prevent react related imports from being loaded
'@apollo/client$': '@apollo/client/core',
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
......
......@@ -47,7 +47,7 @@ module.exports = {
'bootstrap/dist/js/bootstrap.js',
'sortablejs/modular/sortable.esm.js',
'popper.js',
'apollo-client',
'@apollo/client/core',
'source-map',
'mousetrap',
],
......
......@@ -152,7 +152,7 @@ Root-level queries are defined in
### Multiplex queries
GitLab supports batching queries into a single request using
[apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http/). More
[`@apollo/client/link/batch-http`](https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/). More
information about multiplexed queries is also available for
[GraphQL Ruby](https://graphql-ruby.org/queries/multiplex.html), the
library GitLab uses on the backend.
......
......@@ -105,6 +105,9 @@ export default {
};
},
result({ data }) {
if (!data) {
return;
}
if (this.hasSubgroups === undefined) {
this.hasSubgroups = data.groups?.nodes?.length > 0;
}
......
......@@ -9,6 +9,7 @@ query devopsAdoptionEnabledNamespaces($displayNamespaceId: NamespaceID) {
...LatestSnapshot
}
namespace {
__typename
...Namespace
}
}
......
import gql from 'graphql-tag';
import { gql } from '@apollo/client/core';
import { computeMonthRangeData } from '../utils';
/**
......
......@@ -15,6 +15,7 @@ query getCodeQualityViolations($projectPath: ID!, $iid: ID!, $first: Int, $after
severity
}
pageInfo {
__typename
...PageInfo
}
}
......
......@@ -7,6 +7,8 @@ import { parseIssuableData } from '~/issues/show/utils/parse_data';
import { defaultClient } from '~/sidebar/graphql';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
import { IssuableType } from '~/issues/constants';
import EpicApp from './components/epic_app.vue';
import createStore from './store';
......@@ -17,6 +19,16 @@ const apolloProvider = new VueApollo({
defaultClient,
});
apolloProvider.clients.defaultClient.cache.writeQuery({
query: getIssueStateQuery,
data: {
issueState: {
isDirty: false,
issuableType: IssuableType.Epic,
},
},
});
export default () => {
const el = document.getElementById('epic-app-root');
......
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
......@@ -16,23 +15,11 @@ import createDefaultClient from '~/lib/graphql';
import '~/boards/filters/due_date_filters';
import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
import introspectionQueryResultData from '~/sidebar/fragmentTypes.json';
Vue.use(VueApollo);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
fragmentMatcher,
},
},
),
defaultClient: createDefaultClient(),
});
function mountBoardApp(el) {
......
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
......
import gql from 'graphql-tag';
import { gql } from '@apollo/client/core';
import PageInfo from '~/graphql_shared/fragments/pageInfo.fragment.graphql';
export default (graphQlFieldName) => {
......
......@@ -58,6 +58,9 @@ export default {
};
},
result({ data }) {
if (!data) {
return;
}
this.formData = this.extractComplianceFramework(data);
},
error(error) {
......
#import "../fragments/oncall_schedule_participant.fragment.graphql"
fragment OnCallRotation on IncidentManagementOncallRotation {
__typename
id
name
startsAt
......@@ -13,6 +14,7 @@ fragment OnCallRotation on IncidentManagementOncallRotation {
}
participants {
nodes {
__typename
...OnCallParticipant
}
}
......
......@@ -5,6 +5,7 @@ fragment OnCallRotationWithShifts on IncidentManagementOncallRotation {
shifts(startTime: $startsAt, endTime: $endsAt) {
nodes {
participant {
__typename
...OnCallParticipant
}
endsAt
......
import gql from 'graphql-tag';
import { gql } from '@apollo/client/core';
import createFlash from '~/flash';
import createDefaultClient from '~/lib/graphql';
import { s__ } from '~/locale';
......
......@@ -152,7 +152,7 @@ export default {
</template>
<template #default>
<gl-skeleton-loader v-if="$apollo.queries.projectQuality.loading" />
<div v-else class="row gl-ml-2">
<div v-else-if="projectQuality.testReportSummary" class="row gl-ml-2">
<gl-single-stat
class="col-sm-6 col-md-4"
data-testid="test-runs-stat"
......
{"__schema":{"types":[{"kind":"INTERFACE","name":"User","possibleTypes":[{"name":"UserCore"}]}]}}
import { defaultDataIdFromObject } from '@apollo/client/core';
import { GlToast } from '@gitlab/ui';
import { defaultDataIdFromObject, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import introspectionQueryResultData from './queries/fragmentTypes.json';
import RequirementsRoot from './components/requirements_root.vue';
......@@ -13,10 +12,6 @@ import { FilterState } from './constants';
Vue.use(VueApollo);
Vue.use(GlToast);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export default () => {
const el = document.getElementById('js-requirements-app');
......@@ -32,7 +27,6 @@ export default () => {
dataIdFromObject: (object) =>
// eslint-disable-next-line no-underscore-dangle, @gitlab/require-i18n-strings
object.__typename === 'Requirement' ? object.iid : defaultDataIdFromObject(object),
fragmentMatcher,
},
},
),
......
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { helpPagePath } from '~/helpers/help_page_helper';
......
import gql from 'graphql-tag';
import { gql } from '@apollo/client/core';
import { produce } from 'immer';
import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql';
......
......@@ -9,13 +9,16 @@ query DastScannerProfiles(
) {
project(fullPath: $fullPath) {
id
__typename
scannerProfiles: dastScannerProfiles(
after: $after
before: $before
first: $first
last: $last
) {
__typename
pageInfo {
__typename
...PageInfo
}
nodes {
......
......@@ -2,10 +2,13 @@
query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first: Int, $last: Int) {
project(fullPath: $fullPath) {
__typename
id
siteProfiles: dastSiteProfiles(after: $after, before: $before, first: $first, last: $last)
@connection(key: "dastSiteProfiles") {
__typename
pageInfo {
__typename
...PageInfo
}
nodes {
......
......@@ -74,6 +74,9 @@ export default {
id: finding.uuid,
})),
result({ data }) {
if (!data) {
return;
}
this.pageInfo = preparePageInfo(data.project?.pipeline?.securityReportFindings?.pageInfo);
},
error() {
......
......@@ -49,7 +49,7 @@ export default {
update(data) {
const summary = {
reports: data?.project?.pipeline?.securityReportSummary,
jobs: data?.project?.pipeline?.jobs.nodes,
jobs: data?.project?.pipeline?.jobs?.nodes,
};
return summary?.reports && Object.keys(summary.reports).length ? summary : null;
},
......
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { vulnerabilityLocationTypes } from '~/graphql_shared/fragment_types/vulnerability_location_types';
import tempResolvers from '~/security_configuration/resolver';
Vue.use(VueApollo);
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: vulnerabilityLocationTypes,
const defaultClient = createDefaultClient({
...tempResolvers,
});
const defaultClient = createDefaultClient(
{
...tempResolvers,
},
{
cacheConfig: {
fragmentMatcher,
},
},
);
export default new VueApollo({
defaultClient,
});
......@@ -70,9 +70,6 @@ export default {
update(data) {
return data.workspace?.issuable?.weight;
},
result({ data }) {
return data.workspace?.issuable?.weight;
},
error() {
createFlash({
message: sprintf(__('Something went wrong while setting %{issuableType} weight.'), {
......
......@@ -62,6 +62,9 @@ export default {
},
manual: true,
result({ data }) {
if (!data) {
return;
}
if (data.errors) {
this.hasError = true;
} else if (data.orderPreview) {
......
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import { defaultDataIdFromObject } from '@apollo/client/core';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
......
......@@ -45,7 +45,7 @@ export default {
id: convertToGraphQLId(TYPE_VULNERABILITY, this.vulnerability.id),
};
},
result({ data: { vulnerability } }) {
result({ data: { vulnerability } = {} }) {
this.shouldSkipQuery = true;
this.vulnerability = {
...this.vulnerability,
......
......@@ -202,10 +202,12 @@ describe('SubscriptionManagementApp', () => {
describe('activating the license', () => {
it('shows the activation success notification', async () => {
await findActivateSubscriptionCard().vm.$emit(
findActivateSubscriptionCard().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE,
);
await waitForPromises();
expect(findSubscriptionActivationSuccessAlert().props('title')).toBe(
subscriptionActivationNotificationText,
);
......@@ -225,9 +227,9 @@ describe('SubscriptionManagementApp', () => {
describe('activating the license', () => {
beforeEach(async () => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
currentSubscriptionResolver = jest.fn().mockResolvedValue({
data: { currentLicense: { __typename: 'CurrentLicense', ...license.ULTIMATE } },
});
pastSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { licenseHistoryEntries: { nodes: subscriptionPastHistory } },
});
......@@ -272,9 +274,9 @@ describe('SubscriptionManagementApp', () => {
describe('with active license', () => {
beforeEach(async () => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
currentSubscriptionResolver = jest.fn().mockResolvedValue({
data: { currentLicense: { __typename: 'CurrentLicense', ...license.ULTIMATE } },
});
pastSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { licenseHistoryEntries: { nodes: subscriptionPastHistory } },
});
......
......@@ -193,7 +193,7 @@ describe('DevopsAdoptionAddDropdown', () => {
});
describe('on error', () => {
beforeEach(() => {
beforeEach(async () => {
jest.spyOn(Sentry, 'captureException');
createComponent({
......@@ -202,6 +202,7 @@ describe('DevopsAdoptionAddDropdown', () => {
});
clickFirstRow();
await waitForPromises();
});
it('calls sentry', async () => {
......
......@@ -225,7 +225,6 @@ describe('DevopsAdoptionApp', () => {
};
wrapper = createComponent({ mockApollo, provide });
await waitForPromises();
await nextTick();
});
it('does not attempt to enable a group', () => {
......
......@@ -172,6 +172,7 @@ describe('Iteration Breadcrumb', () => {
iterationCadences: {
nodes: [
{
__typename: 'IterationCadence',
title: cadenceTitle,
id: 'cadenceid',
automatic: '',
......
......@@ -325,17 +325,20 @@ describe('Iteration cadence form', () => {
});
it('updates roll over issues checkbox', async () => {
await waitForPromises();
const rollOver = true;
setRollOver(rollOver);
const { __typename, ...cadenceWithoutTypename } = iterationCadence;
clickSave();
await nextTick();
await waitForPromises();
expect(findError().exists()).toBe(false);
expect(mutationMock).toHaveBeenCalledWith({
input: {
...iterationCadence,
...cadenceWithoutTypename,
rollOver,
},
});
......
......@@ -60,6 +60,7 @@ describe('Iteration cadence list item', () => {
iterations: {
nodes: iterations,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: false,
startCursor,
......
......@@ -58,6 +58,7 @@ describe('Iteration cadences list', () => {
iterationCadences: {
nodes: cadences,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: false,
startCursor,
......@@ -85,6 +86,10 @@ describe('Iteration cadences list', () => {
},
};
const queryErrorResponse = {
message: 'Network error',
};
function createComponent({
canCreateCadence,
canEditCadence,
......@@ -188,7 +193,7 @@ describe('Iteration cadences list', () => {
it('shows alert on query error', async () => {
await createComponent({
resolverMock: jest.fn().mockRejectedValue(queryEmptyResponse),
resolverMock: jest.fn().mockRejectedValue(queryErrorResponse),
});
await waitForPromises();
......@@ -203,7 +208,6 @@ describe('Iteration cadences list', () => {
beforeEach(async () => {
resolverMock = jest.fn().mockResolvedValue(querySuccessResponse);
await createComponent({ resolverMock });
await waitForPromises();
resolverMock.mockReset();
......
......@@ -38,6 +38,7 @@ export const mockProjectIterations = {
};
export const manualIterationCadence = {
__typename: 'IterationCadence',
active: true,
id: `gid://gitlab/Iterations::Cadence/72`,
title: 'A manual iteration cadence',
......@@ -65,6 +66,7 @@ export const updateMutationSuccess = {
export const emptyGroupIterationsSuccess = {
data: {
workspace: {
__typename: 'Group',
id: 'gid://gitlab/Group/114',
iterations: {
nodes: [],
......
[
{
"__typename": "IncidentManagementOncallRotation",
"id": "gid://gitlab/IncidentManagement::OncallRotation/2",
"name": "Rotation 242",
"startsAt": "2021-01-13T11:04:56.333Z",
......@@ -13,6 +14,7 @@
"participants": {
"nodes": [
{
"__typename": "OncallParticipantType",
"id": "49",
"user": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/49",
......@@ -29,6 +31,7 @@
"nodes": [
{
"participant": {
"__typename": "OncallParticipantType",
"id": "1",
"colorWeight": "500",
"colorPalette": "blue",
......@@ -44,6 +47,7 @@
},
{
"participant": {
"__typename": "OncallParticipantType",
"id": "2",
"colorWeight": "500",
"colorPalette": "orange",
......@@ -61,6 +65,7 @@
}
},
{
"__typename": "IncidentManagementOncallRotation",
"id": "gid://gitlab/IncidentManagement::OncallRotation/55",
"name": "Rotation 242",
"startsAt": "2021-01-13T11:04:56.333Z",
......@@ -74,6 +79,7 @@
"participants": {
"nodes": [
{
"__typename": "OncallParticipantType",
"id": "99",
"user": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/99",
......@@ -90,6 +96,7 @@
"nodes": [
{
"participant": {
"__typename": "OncallParticipantType",
"id": "38",
"colorWeight": "500",
"colorPalette": "aqua",
......@@ -105,6 +112,7 @@
},
{
"participant": {
"__typename": "OncallParticipantType",
"id": "39",
"colorWeight": "500",
"colorPalette": "green",
......@@ -122,6 +130,7 @@
}
},
{
"__typename": "IncidentManagementOncallRotation",
"id": "gid://gitlab/IncidentManagement::OncallRotation/3",
"name": "Rotation 244",
"startsAt": "2021-01-06T10:04:56.333Z",
......@@ -135,6 +144,7 @@
"participants": {
"nodes": [
{
"__typename": "OncallParticipantType",
"id": "48",
"user": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/48",
......@@ -151,6 +161,7 @@
"nodes": [
{
"participant": {
"__typename": "OncallParticipantType",
"id": "40",
"colorWeight": "500",
"colorPalette": "magenta",
......@@ -166,6 +177,7 @@
},
{
"participant": {
"__typename": "OncallParticipantType",
"id": "41",
"colorWeight": "600",
"colorPalette": "blue",
......@@ -183,6 +195,7 @@
}
},
{
"__typename": "IncidentManagementOncallRotation",
"id": "gid://gitlab/IncidentManagement::OncallRotation/5",
"name": "Rotation 247",
"startsAt": "2021-01-06T10:04:56.333Z",
......@@ -196,6 +209,7 @@
"participants": {
"nodes": [
{
"__typename": "OncallParticipantType",
"id": "51",
"user": {
"id": "gid://gitlab/IncidentManagement::OncallParticipant/51",
......@@ -212,6 +226,7 @@
"nodes": [
{
"participant": {
"__typename": "OncallParticipantType",
"id": "43",
"colorWeight": "600",
"colorPalette": "orange",
......@@ -227,6 +242,7 @@
},
{
"participant": {
"__typename": "OncallParticipantType",
"id": "44",
"colorWeight": "600",
"colorPalette": "aqua",
......
......@@ -37,7 +37,6 @@ const createComponent = async (mockData = {}) => {
downloadCode = userPermissionsMock.downloadCode,
createMergeRequestIn = userPermissionsMock.createMergeRequestIn,
isBinary,
inject = {},
path = propsMock.projectPath,
} = mockData;
......@@ -66,7 +65,7 @@ const createComponent = async (mockData = {}) => {
path,
},
mixins: [{ data: () => ({ ref: refMock }) }],
provide: { ...inject },
provide: { targetBranch: 'test', originalBranch: 'test' },
});
await waitForPromises();
......
import { GlPagination, GlBadge } from '@gitlab/ui';
import { defaultDataIdFromObject } from '@apollo/client/core';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
......
......@@ -105,6 +105,7 @@ export const getCorpusesQueryResponse = {
corpuses: {
nodes: corpuses,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'start-cursor',
......
......@@ -13,6 +13,7 @@ import {
} from 'ee/security_configuration/dast_site_validation/constants';
import dastSiteValidationsQuery from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validations.query.graphql';
import createApolloProvider from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import * as responses from '../mocks/apollo_mock';
import { siteProfiles } from '../mocks/mock_data';
......@@ -201,7 +202,9 @@ describe('EE - DastSiteProfileList', () => {
});
});
it('fetches validation statuses for all profiles that are being validated and updates the cache', () => {
it('fetches validation statuses for all profiles that are being validated and updates the cache', async () => {
await waitForPromises();
expect(requestHandlers.dastSiteValidations).toHaveBeenCalledWith({
fullPath: defaultProps.fullPath,
urls: urlsPendingValidation,
......@@ -215,7 +218,9 @@ describe('EE - DastSiteProfileList', () => {
${2} | ${inProgressValidation.normalizedTargetUrl} | ${DAST_SITE_VALIDATION_STATUS.PASSED}
`(
'in the local cache, profile with normalized URL $normalizedTargetUrl has its status set to $status',
({ nthCall, normalizedTargetUrl, status }) => {
async ({ nthCall, normalizedTargetUrl, status }) => {
await waitForPromises();
expect(updateSiteProfilesStatuses).toHaveBeenNthCalledWith(nthCall, {
fullPath: defaultProps.fullPath,
normalizedTargetUrl,
......
import gql from 'graphql-tag';
import { gql } from '@apollo/client/core';
import {
appendToPreviousResult,
removeProfile,
......
......@@ -230,6 +230,7 @@ export const pipelineSecurityReportSummaryWithErrors = merge({}, pipelineSecurit
id: 'pipeline-1',
securityReportSummary: {
dast: {
__typename: 'SecurityReportSummarySection',
scans: {
nodes: scansWithErrors,
},
......
......@@ -116,8 +116,8 @@ describe('Pipeline findings', () => {
beforeEach(async () => {
createWrapperWithApollo(
jest.fn().mockResolvedValue(mockPipelineFindingsResponse({ hasNextPage: true })),
await waitForPromises(),
);
await waitForPromises();
});
it('shows the intersection loader', () => {
......
......@@ -4,10 +4,10 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import VulnerabilityCounts from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_counts.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { DASHBOARD_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import createFlash from '~/flash';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import countsQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql';
import { SEVERITIES } from '~/vulnerabilities/constants';
......@@ -26,7 +26,7 @@ const getCountsRequestHandler = ({
data: {
[dashboardType]: {
id: '1',
vulnerabilitySeveritiesCount: data,
vulnerabilitySeveritiesCount: { __typename: 'VulnerabilitySeveritiesCount', ...data },
},
},
});
......@@ -92,7 +92,7 @@ describe('Vulnerability counts component', () => {
createWrapper({ countsHandler });
await waitForPromises();
expect(createFlash).toHaveBeenCalledTimes(2);
expect(createFlash).toHaveBeenCalled();
});
it.each([DASHBOARD_TYPES.PROJECT, DASHBOARD_TYPES.GROUP, DASHBOARD_TYPES.INSTANCE])(
......
......@@ -113,7 +113,7 @@ describe('Vulnerability list GraphQL component', () => {
createWrapper({ vulnerabilitiesHandler });
await waitForPromises();
expect(createFlash).toHaveBeenCalledTimes(2);
expect(createFlash).toHaveBeenCalled();
});
});
......
export const mockProjectsWithSeverityCounts = () => [
{
__typename: 'Project',
id: 'gid://gitlab/Project/1',
name: 'Gitlab Test',
nameWithNamespace: 'Gitlab Org / Gitlab Test',
......@@ -8,6 +9,7 @@ export const mockProjectsWithSeverityCounts = () => [
avatarUrl: null,
path: 'gitlab-test',
vulnerabilitySeveritiesCount: {
__typename: 'VulnerabilitySeveritiesCount',
critical: 2,
high: 0,
info: 4,
......@@ -17,6 +19,7 @@ export const mockProjectsWithSeverityCounts = () => [
},
},
{
__typename: 'Project',
id: 'gid://gitlab/Project/2',
name: 'Gitlab Shell',
nameWithNamespace: 'Gitlab Org / Gitlab Shell',
......@@ -25,6 +28,7 @@ export const mockProjectsWithSeverityCounts = () => [
avatarUrl: null,
path: 'gitlab-shell',
vulnerabilitySeveritiesCount: {
__typename: 'VulnerabilitySeveritiesCount',
critical: 0,
high: 2,
info: 2,
......@@ -34,6 +38,7 @@ export const mockProjectsWithSeverityCounts = () => [
},
},
{
__typename: 'Project',
id: 'gid://gitlab/Project/4',
name: 'Gitlab Perfectly Secure',
nameWithNamespace: 'Gitlab Org / Perfectly Secure',
......@@ -42,6 +47,7 @@ export const mockProjectsWithSeverityCounts = () => [
avatarUrl: null,
path: 'gitlab-perfectly-secure',
vulnerabilitySeveritiesCount: {
__typename: 'VulnerabilitySeveritiesCount',
critical: 0,
high: 0,
info: 0,
......@@ -51,6 +57,7 @@ export const mockProjectsWithSeverityCounts = () => [
},
},
{
__typename: 'Project',
id: 'gid://gitlab/Project/5',
name: 'Gitlab Perfectly Secure 2 ',
nameWithNamespace: 'Gitlab Org / Perfectly Secure 2',
......@@ -59,6 +66,7 @@ export const mockProjectsWithSeverityCounts = () => [
avatarUrl: null,
path: 'gitlab-perfectly-secure-2',
vulnerabilitySeveritiesCount: {
__typename: 'VulnerabilitySeveritiesCount',
critical: 0,
high: 0,
info: 0,
......@@ -222,12 +230,7 @@ export const mockVulnerableProjectsGroup = () => ({
group: {
id: 'group-1',
projects: {
nodes: [
{
id: 'gid://gitlab/Project/2',
name: 'Gitlab Shell',
},
],
nodes: [{ __typename: 'Project', id: 'gid://gitlab/Project/2', name: 'Gitlab Shell' }],
},
},
},
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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