Commit 576a41a9 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '352087-dast-queries-edges' into 'master'

Simplify DAST profiles GraphQL queries

See merge request gitlab-org/gitlab!80012
parents cb3c3277 ab93e29b
......@@ -52,11 +52,11 @@ const createProfilesApolloOptions = (name, field, { fetchQuery, fetchError }) =>
};
},
update(data) {
const edges = data?.project?.[name]?.edges ?? [];
if (edges.length === 1) {
this[field] = edges[0].node.id;
const nodes = data?.project?.[name]?.nodes ?? [];
if (nodes.length === 1) {
this[field] = nodes[0].id;
}
return edges.map(({ node }) => node);
return nodes;
},
error(e) {
Sentry.captureException(e);
......
......@@ -19,11 +19,11 @@ const createProfilesApolloOptions = (name, field, { fetchQuery, fetchError }) =>
};
},
update(data) {
const edges = data?.project?.[name]?.edges ?? [];
if (edges.length === 1) {
this[field] = edges[0].node.id;
const nodes = data?.project?.[name]?.nodes ?? [];
if (nodes.length === 1) {
this[field] = nodes[0].id;
}
return edges.map(({ node }) => node);
return nodes;
},
error(e) {
Sentry.captureException(e);
......
......@@ -100,11 +100,9 @@ export default {
result({ data, error }) {
if (!error) {
const { project } = data;
const profileEdges = project?.[profileType]?.edges ?? [];
const profiles = profileEdges.map(({ node }) => node);
const pageInfo = project?.[profileType].pageInfo;
this.profileTypes[profileType].profiles = profiles;
this.profileTypes[profileType].profiles = project?.[profileType]?.nodes;
this.profileTypes[profileType].pageInfo = pageInfo;
}
},
......
......@@ -11,10 +11,10 @@ import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graph
*/
export const appendToPreviousResult = (profileType) => (previousResult, { fetchMoreResult }) => {
const newResult = { ...fetchMoreResult };
const previousEdges = previousResult.project[profileType].edges;
const newEdges = newResult.project[profileType].edges;
const previousNodes = previousResult.project[profileType].nodes;
const newNodes = newResult.project[profileType].nodes;
newResult.project[profileType].edges = [...previousEdges, ...newEdges];
newResult.project[profileType].nodes = [...previousNodes, ...newNodes];
return newResult;
};
......@@ -31,11 +31,9 @@ export const removeProfile = ({ profileId, profileType, store, queryBody }) => {
const sourceData = store.readQuery(queryBody);
const data = produce(sourceData, (draftState) => {
draftState.project[profileType].edges = draftState.project[profileType].edges.filter(
({ node }) => {
return node.id !== profileId;
},
);
draftState.project[profileType].nodes = draftState.project[profileType].nodes.filter((node) => {
return node.id !== profileId;
});
});
store.writeQuery({ ...queryBody, data });
......@@ -67,8 +65,8 @@ export const updateSiteProfilesStatuses = ({ fullPath, normalizedTargetUrl, stat
const sourceData = store.readQuery(queryBody);
const profilesWithNormalizedTargetUrl = sourceData.project.siteProfiles.edges.flatMap(
({ node }) => (node.normalizedTargetUrl === normalizedTargetUrl ? node : []),
const profilesWithNormalizedTargetUrl = sourceData.project.siteProfiles.nodes.map((node) =>
node.normalizedTargetUrl === normalizedTargetUrl ? node : [],
);
profilesWithNormalizedTargetUrl.forEach(({ id }) => {
......
......@@ -18,19 +18,17 @@ query DastScannerProfiles(
pageInfo {
...PageInfo
}
edges {
cursor
node {
id
profileName
spiderTimeout
targetTimeout
scanType
useAjaxSpider
showDebugMessages
editPath
referencedInSecurityPolicies
}
nodes {
__typename
id
profileName
spiderTimeout
targetTimeout
scanType
useAjaxSpider
showDebugMessages
editPath
referencedInSecurityPolicies
}
}
}
......
......@@ -8,27 +8,25 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first:
pageInfo {
...PageInfo
}
edges {
cursor
node {
id
profileName
normalizedTargetUrl
targetUrl
targetType
editPath
validationStatus
referencedInSecurityPolicies
auth {
enabled
url
usernameField
passwordField
username
}
excludedUrls
requestHeaders
nodes {
__typename
id
profileName
normalizedTargetUrl
targetUrl
targetType
editPath
validationStatus
referencedInSecurityPolicies
auth {
enabled
url
usernameField
passwordField
username
}
excludedUrls
requestHeaders
}
}
}
......
......@@ -22,7 +22,7 @@ RSpec.describe 'DAST profiles (GraphQL fixtures)' do
})
expect_graphql_errors_to_be_empty
expect(graphql_data_at(:project, :siteProfiles, :edges)).to have_attributes(size: dast_site_profiles.length)
expect(graphql_data_at(:project, :siteProfiles, :nodes)).to have_attributes(size: dast_site_profiles.length)
end
end
......@@ -38,7 +38,7 @@ RSpec.describe 'DAST profiles (GraphQL fixtures)' do
})
expect_graphql_errors_to_be_empty
expect(graphql_data_at(:project, :scannerProfiles, :edges)).to have_attributes(size: dast_scanner_profiles.length)
expect(graphql_data_at(:project, :scannerProfiles, :nodes)).to have_attributes(size: dast_scanner_profiles.length)
end
end
......
......@@ -3,6 +3,8 @@ import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
import siteProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql.basic.json';
import scannerProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql.basic.json';
import OnDemandScansForm from 'ee/on_demand_scans_form/components/on_demand_scans_form.vue';
import ScannerProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/site_profile_selector.vue';
......@@ -15,7 +17,6 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createApolloProvider from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import RefSelector from '~/ref/components/ref_selector.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
......@@ -26,7 +27,7 @@ import {
nonValidatedSiteProfile,
validatedSiteProfile,
} from 'ee_jest/security_configuration/dast_profiles/mocks/mock_data';
import * as responses from '../mocks/apollo_mocks';
import { itSelectsOnlyAvailableProfile } from './shared_assertions';
const dastSiteValidationDocsPath = '/application_security/dast/index#dast-site-validation';
const projectPath = 'group/project';
......@@ -125,10 +126,11 @@ describe('OnDemandScansForm', () => {
localVue.use(VueApollo);
requestHandlers = {
dastScannerProfiles: jest.fn().mockResolvedValue(responses.dastScannerProfiles()),
dastSiteProfiles: jest.fn().mockResolvedValue(responses.dastSiteProfiles()),
dastScannerProfiles: jest.fn().mockResolvedValue(scannerProfilesFixtures),
dastSiteProfiles: jest.fn().mockResolvedValue(siteProfilesFixtures),
...handlers,
};
return createApolloProvider([
[dastScannerProfilesQuery, requestHandlers.dastScannerProfiles],
[dastSiteProfilesQuery, requestHandlers.dastSiteProfiles],
......@@ -189,6 +191,7 @@ describe('OnDemandScansForm', () => {
},
),
);
return wrapper;
};
const createComponent = createComponentFactory(mount);
const createShallowComponent = createComponentFactory();
......@@ -205,6 +208,8 @@ describe('OnDemandScansForm', () => {
localStorage.clear();
});
itSelectsOnlyAvailableProfile(createShallowComponent);
describe('when creating a new scan', () => {
it('renders properly', () => {
createComponent();
......@@ -559,29 +564,6 @@ describe('OnDemandScansForm', () => {
},
);
describe.each`
profileType | query | selector | profiles
${'scanner'} | ${'dastScannerProfiles'} | ${ScannerProfileSelector} | ${scannerProfiles}
${'site'} | ${'dastSiteProfiles'} | ${SiteProfileSelector} | ${siteProfiles}
`('when there is a single $profileType profile', ({ query, selector, profiles }) => {
const [profile] = profiles;
beforeEach(async () => {
createShallowComponent(
{},
{
[query]: jest.fn().mockResolvedValue(responses[query]([profile])),
},
);
await waitForPromises();
});
it('automatically selects the only available profile', () => {
expect(wrapper.findComponent(selector).attributes('value')).toBe(profile.id);
});
});
describe('scanner profile summary', () => {
const [{ id }] = scannerProfiles;
......
......@@ -3,6 +3,8 @@ import { createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
import siteProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql.basic.json';
import scannerProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql.basic.json';
import DastProfilesSelector from 'ee/on_demand_scans_form/components/profile_selector/dast_profiles_selector.vue';
import ScannerProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/site_profile_selector.vue';
......@@ -11,7 +13,6 @@ import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graph
import createApolloProvider from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import {
siteProfiles,
......@@ -19,7 +20,7 @@ import {
nonValidatedSiteProfile,
validatedSiteProfile,
} from 'ee_jest/security_configuration/dast_profiles/mocks/mock_data';
import * as responses from '../../mocks/apollo_mocks';
import { itSelectsOnlyAvailableProfile } from '../shared_assertions';
const URL_HOST = 'https://localhost/';
......@@ -43,8 +44,8 @@ describe('EE - DAST Profiles Selector', () => {
const createMockApolloProvider = (handlers) => {
localVue.use(VueApollo);
requestHandlers = {
dastScannerProfiles: jest.fn().mockResolvedValue(responses.dastScannerProfiles()),
dastSiteProfiles: jest.fn().mockResolvedValue(responses.dastSiteProfiles()),
dastScannerProfiles: jest.fn().mockResolvedValue(scannerProfilesFixtures),
dastSiteProfiles: jest.fn().mockResolvedValue(siteProfilesFixtures),
...handlers,
};
return createApolloProvider([
......@@ -98,6 +99,7 @@ describe('EE - DAST Profiles Selector', () => {
},
),
);
return wrapper;
};
const createComponent = createComponentFactory();
......@@ -106,6 +108,8 @@ describe('EE - DAST Profiles Selector', () => {
wrapper.destroy();
});
itSelectsOnlyAvailableProfile(createComponent);
describe('loading state', () => {
it.each`
scannerProfilesLoading | siteProfilesLoading | isLoading
......@@ -161,29 +165,6 @@ describe('EE - DAST Profiles Selector', () => {
},
);
describe.each`
profileType | query | selector | profiles
${'scanner'} | ${'dastScannerProfiles'} | ${ScannerProfileSelector} | ${scannerProfiles}
${'site'} | ${'dastSiteProfiles'} | ${SiteProfileSelector} | ${siteProfiles}
`('when there is a single $profileType profile', ({ query, selector, profiles }) => {
const [profile] = profiles;
beforeEach(async () => {
createComponent(
{},
{
[query]: jest.fn().mockResolvedValue(responses[query]([profile])),
},
);
await waitForPromises();
});
it('automatically selects the only available profile', () => {
expect(wrapper.findComponent(selector).attributes('value')).toBe(profile.id);
});
});
describe('populate profiles from query params', () => {
const [siteProfile] = siteProfiles;
const [scannerProfile] = scannerProfiles;
......
/* eslint-disable jest/no-export */
import { cloneDeep, set } from 'lodash';
import siteProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql.basic.json';
import scannerProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql.basic.json';
import waitForPromises from 'helpers/wait_for_promises';
import ScannerProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/site_profile_selector.vue';
const [firstSiteProfile] = siteProfilesFixtures.data.project.siteProfiles.nodes;
const [firstScannerProfile] = scannerProfilesFixtures.data.project.scannerProfiles.nodes;
const siteProfilesReponseWithSingleProfile = set(
cloneDeep(siteProfilesFixtures),
'data.project.siteProfiles.nodes',
[firstSiteProfile],
);
const scannerProfilesReponseWithSingleProfile = set(
cloneDeep(scannerProfilesFixtures),
'data.project.scannerProfiles.nodes',
[firstScannerProfile],
);
export const itSelectsOnlyAvailableProfile = (componentFactory) => {
let wrapper;
describe.each`
profileType | query | selector | response | expectedId
${'site'} | ${'dastSiteProfiles'} | ${SiteProfileSelector} | ${siteProfilesReponseWithSingleProfile} | ${firstSiteProfile.id}
${'scanner'} | ${'dastScannerProfiles'} | ${ScannerProfileSelector} | ${scannerProfilesReponseWithSingleProfile} | ${firstScannerProfile.id}
`('when there is a single $profileType profile', ({ query, selector, response, expectedId }) => {
beforeEach(async () => {
wrapper = componentFactory(
{},
{
[query]: jest.fn().mockResolvedValue(response),
},
);
await waitForPromises();
});
it('automatically selects the only available profile', () => {
expect(wrapper.findComponent(selector).attributes('value')).toBe(expectedId);
});
});
};
import {
siteProfiles,
scannerProfiles,
} from 'ee_jest/security_configuration/dast_profiles/mocks/mock_data';
const defaults = {
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
};
export const dastScannerProfiles = (profiles = scannerProfiles) => ({
data: {
project: {
id: '1',
scannerProfiles: {
...defaults,
edges: profiles.map((profile) => ({
cursor: '',
node: profile,
})),
},
},
},
});
export const dastSiteProfiles = (profiles = siteProfiles) => ({
data: {
project: {
id: '1',
siteProfiles: {
...defaults,
edges: profiles.map((profile) => ({
cursor: '',
node: profile,
})),
},
},
},
});
......@@ -12,10 +12,10 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
it.each(['siteProfiles', 'scannerProfiles'])(
'appends new results to previous',
(profileType) => {
const previousResult = { project: { [profileType]: { edges: ['foo'] } } };
const fetchMoreResult = { project: { [profileType]: { edges: ['bar'] } } };
const previousResult = { project: { [profileType]: { nodes: ['foo'] } } };
const fetchMoreResult = { project: { [profileType]: { nodes: ['bar'] } } };
const expected = { project: { [profileType]: { edges: ['foo', 'bar'] } } };
const expected = { project: { [profileType]: { nodes: ['foo', 'bar'] } } };
const result = appendToPreviousResult(profileType)(previousResult, { fetchMoreResult });
expect(result).toEqual(expected);
......@@ -32,7 +32,7 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
const mockData = {
project: {
[profileType]: {
edges: [{ node: mockProfiles[0] }, { node: mockProfiles[1] }],
nodes: [mockProfiles[0], mockProfiles[1]],
},
},
};
......@@ -53,7 +53,7 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
data: {
project: {
[profileType]: {
edges: [{ node: mockProfiles[1] }],
nodes: [mockProfiles[1]],
},
},
},
......@@ -91,7 +91,7 @@ describe('EE - DastProfiles GraphQL CacheUtils', () => {
const mockData = {
project: {
siteProfiles: {
edges: [{ node: siteProfile }],
nodes: [siteProfile],
},
},
};
......
......@@ -4,9 +4,7 @@ import policySiteProfilesFixtures from 'test_fixtures/graphql/security_configura
import policyScannerProfilesFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql.from_policies.json';
import dastFailedSiteValidationsFixtures from 'test_fixtures/graphql/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql.json';
export const siteProfiles = siteProfilesFixture.data.project.siteProfiles.edges.map(
({ node }) => node,
);
export const siteProfiles = siteProfilesFixture.data.project.siteProfiles.nodes;
export const nonValidatedSiteProfile = siteProfiles.find(
({ validationStatus }) => validationStatus === 'NONE',
......@@ -15,17 +13,12 @@ export const validatedSiteProfile = siteProfiles.find(
({ validationStatus }) => validationStatus === 'PASSED_VALIDATION',
);
export const policySiteProfiles = policySiteProfilesFixtures.data.project.siteProfiles.edges.map(
({ node }) => node,
);
export const policySiteProfiles = policySiteProfilesFixtures.data.project.siteProfiles.nodes;
export const policyScannerProfiles = policyScannerProfilesFixtures.data.project.scannerProfiles.edges.map(
({ node }) => node,
);
export const policyScannerProfiles =
policyScannerProfilesFixtures.data.project.scannerProfiles.nodes;
export const scannerProfiles = scannerProfilesFixtures.data.project.scannerProfiles.edges.map(
({ node }) => node,
);
export const scannerProfiles = scannerProfilesFixtures.data.project.scannerProfiles.nodes;
export const failedSiteValidations =
dastFailedSiteValidationsFixtures.data.project.validations.nodes;
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