Commit f07ac8a5 authored by Lukas 'Eipi' Eipert's avatar Lukas 'Eipi' Eipert Committed by Natalia Tepluhina

Update to ApolloClient@3

This is an attempt to update to ApolloClient@3. Multiple packages have
been replaced by a single one. This will likely result in a red pipeline
because Caches are now immutable. But it should be a good starting point
to explore what goes wrong:

TODOs:
1. Check that caching works correctly
2. Check that startup graphql works correctly
3. Update documentation with correct links
4. Link against importing from `@apollo/client`, we should always import
from `@apollo/client/core` as we otherwise pull in React related
dependencies.

Map `@apollo/client` to `@apollo/client/core`

Fix vendor dll

Migrate remaining apollo-link uses

Fixed Introspection fragment matcher

Added merge policies

Fixed more fragment matchers

Fixed dataIdFromObject imports

Fixed gql imports

Fixed apollo-link imports

Remove change to upload link

Removed duplicated ids

Fixed MR widget type policies

Fixed structure
Fixed incidents app

Added id to alert management

Added ids to pipeline editor

Add a resolution for subscriptions-transport-ws

Note: This is needed because @apollo/client has a peer dependency and
some other dependency pulls in a newer version. Both are functionally
equivalent.
Replaced fragmentMatcher with possible types

Removed id from type Repository

Added a few basic type policies

Fixed unit tests to pass

Added typenames to unit tests to make them pass
Cleaned up data checks in components

Apollo 3 doesn't return data property with result on error; thus, we need to check on result hooks if data exists before doing any operations with it
Fixed stylelint errors

Add missing typename in spec

Removed check for data

Cleaning up changes

Removed some unnecessary changes like imports order
Apply 1 suggestion(s) to 1 file(s)
Manually set error policy to 'none'

Removed unnecessary guards from result hook in queries
Revert "Manually set error policy to 'none'"

This reverts commit 657eac6727c563555dcbe8a3013801ae710b80db.
Apply 1 suggestion(s) to 1 file(s)
Fixed stripTypenames to be used on widget

Fixed specs after review

Add comment about pendingApolloRequests

- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55062#note_838943715

Reverted subsriptions transport change
parent f536b8cd
......@@ -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