Commit 5740a53b authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '228740-migrate-refresh-vuln-method-to-graphql' into 'master'

Migrate refreshVulnerability to GraphQL

See merge request gitlab-org/gitlab!50938
parents d0cef8ed b87735c0
......@@ -303,11 +303,6 @@ export default {
return axios.post(url, params);
},
fetchVulnerability(id, params) {
const url = Api.buildUrl(this.vulnerabilityPath).replace(':id', id);
return axios.get(url, params);
},
changeVulnerabilityState(id, state) {
const url = Api.buildUrl(this.vulnerabilityActionPath)
.replace(':id', id)
......
query vulnerability($id: VulnerabilityID!) {
vulnerability(id: $id) {
state
confirmedAt
detectedAt
dismissedAt
resolvedAt
dismissedBy {
id
}
confirmedBy {
id
}
resolvedBy {
id
}
}
}
<script>
import { GlLoadingIcon, GlButton, GlBadge } from '@gitlab/ui';
import Api from 'ee/api';
import { CancelToken } from 'axios';
import SplitButton from 'ee/vue_shared/security_reports/components/split_button.vue';
import fetchHeaderVulnerabilityQuery from 'ee/security_dashboard/graphql/header_vulnerability.graphql';
import vulnerabilityStateMutations from 'ee/security_dashboard/graphql/mutate_vulnerability_state';
import axios from '~/lib/utils/axios_utils';
import download from '~/lib/utils/downloader';
......@@ -14,9 +13,13 @@ import UsersCache from '~/lib/utils/users_cache';
import ResolutionAlert from './resolution_alert.vue';
import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue';
import StatusDescription from './status_description.vue';
import { VULNERABILITY_STATE_OBJECTS, FEEDBACK_TYPES, HEADER_ACTION_BUTTONS } from '../constants';
const gidPrefix = 'gid://gitlab/Vulnerability/';
import { normalizeGraphQLVulnerability } from '../helpers';
import {
VULNERABILITY_STATE_OBJECTS,
FEEDBACK_TYPES,
HEADER_ACTION_BUTTONS,
gidPrefix,
} from '../constants';
export default {
name: 'VulnerabilityHeader',
......@@ -47,7 +50,7 @@ export default {
// prop leading to an error in the footer component.
vulnerability: { ...this.initialVulnerability },
user: undefined,
refreshVulnerabilitySource: undefined,
shouldRefreshVulnerability: false,
};
},
......@@ -57,6 +60,38 @@ export default {
detected: 'warning',
},
apollo: {
vulnerability: {
query: fetchHeaderVulnerabilityQuery,
manual: true,
fetchPolicy: 'no-cache',
variables() {
return {
id: `${gidPrefix}${this.vulnerability.id}`,
};
},
result({ data: { vulnerability } }) {
this.shouldRefreshVulnerability = false;
this.isLoadingVulnerability = false;
this.vulnerability = {
...this.vulnerability,
...normalizeGraphQLVulnerability(vulnerability),
};
},
error() {
createFlash(
s__(
'VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later.',
),
);
},
skip() {
return !this.shouldRefreshVulnerability;
},
},
},
computed: {
stateVariant() {
return this.$options.badgeVariants[this.vulnerability.state] || 'neutral';
......@@ -144,13 +179,10 @@ export default {
variables: { id: `${gidPrefix}${this.vulnerability.id}`, ...payload },
});
const [queryName] = Object.keys(data);
const { vulnerability } = data[queryName];
vulnerability.id = vulnerability.id.replace(gidPrefix, '');
vulnerability.state = vulnerability.state.toLowerCase();
this.vulnerability = {
...this.vulnerability,
...vulnerability,
...normalizeGraphQLVulnerability(data[queryName].vulnerability),
};
this.$emit('vulnerability-state-change');
......@@ -207,34 +239,7 @@ export default {
},
refreshVulnerability() {
this.isLoadingVulnerability = true;
// Cancel any pending API requests.
if (this.refreshVulnerabilitySource) {
this.refreshVulnerabilitySource.cancel();
}
this.refreshVulnerabilitySource = CancelToken.source();
Api.fetchVulnerability(this.vulnerability.id, {
cancelToken: this.refreshVulnerabilitySource.token,
})
.then(({ data }) => {
Object.assign(this.vulnerability, data);
})
.catch((e) => {
// Don't show an error message if the request was cancelled through the cancel token.
if (!axios.isCancel(e)) {
createFlash(
s__(
'VulnerabilityManagement|Something went wrong while trying to refresh the vulnerability. Please try again later.',
),
);
}
})
.finally(() => {
this.isLoadingVulnerability = false;
this.refreshVulnerabilitySource = undefined;
});
this.shouldRefreshVulnerability = true;
},
},
};
......
......@@ -6,6 +6,9 @@ import {
const falsePositiveMessage = s__('VulnerabilityManagement|Will not fix or a false-positive');
export const gidPrefix = 'gid://gitlab/Vulnerability/';
export const uidPrefix = 'gid://gitlab/User/';
export const VULNERABILITY_STATE_OBJECTS = {
detected: {
action: 'revert',
......
import { isAbsolute, isSafeURL } from '~/lib/utils/url_utility';
import { REGEXES } from './constants';
import { REGEXES, gidPrefix, uidPrefix } from './constants';
// Get the issue in the format expected by the descendant components of related_issues_block.vue.
export const getFormattedIssue = (issue) => ({
......@@ -27,3 +27,28 @@ export const getAddRelatedIssueRequestParams = (reference, defaultProjectId) =>
return { target_issue_iid: issueId, target_project_id: projectId };
};
export const normalizeGraphQLVulnerability = (vulnerability) => {
if (!vulnerability) {
return null;
}
const newVulnerability = { ...vulnerability };
if (vulnerability.id) {
newVulnerability.id = vulnerability.id.replace(gidPrefix, '');
}
if (vulnerability.state) {
newVulnerability.state = vulnerability.state.toLowerCase();
}
['confirmed', 'resolved', 'dismissed'].forEach((state) => {
if (vulnerability[`${state}By`]?.id) {
newVulnerability[`${state}ById`] = vulnerability[`${state}By`].id.replace(uidPrefix, '');
delete newVulnerability[`${state}By`];
}
});
return newVulnerability;
};
......@@ -9,6 +9,7 @@ import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue'
import StatusDescription from 'ee/vulnerabilities/components/status_description.vue';
import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue';
import vulnerabilityStateMutations from 'ee/security_dashboard/graphql/mutate_vulnerability_state';
import fetchHeaderVulnerabilityQuery from 'ee/security_dashboard/graphql/header_vulnerability.graphql';
import { FEEDBACK_TYPES, VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -418,4 +419,63 @@ describe('Vulnerability Header', () => {
});
});
});
describe('refresh vulnerability', () => {
describe('on success', () => {
beforeEach(() => {
const apolloProvider = createApolloProvider([
fetchHeaderVulnerabilityQuery,
jest.fn().mockResolvedValue({
data: {
errors: [],
vulnerability: {
id: 'gid://gitlab/Vulnerability/54',
[`resolvedAt`]: '2020-09-16T11:13:26Z',
state: 'RESOLVED',
},
},
}),
]);
createWrapper({
apolloProvider,
vulnerability: getVulnerability({}),
});
});
it('fetches the vulnerability when refreshVulnerability method is called', async () => {
expect(findBadge().text()).toBe('detected');
wrapper.vm.refreshVulnerability();
await waitForPromises();
expect(findBadge().text()).toBe('resolved');
});
});
describe('on failure', () => {
beforeEach(() => {
const apolloProvider = createApolloProvider([
fetchHeaderVulnerabilityQuery,
jest.fn().mockRejectedValue({
data: {
errors: [{ message: 'something went wrong while fetching the vulnerability' }],
vulnerability: null,
},
}),
]);
createWrapper({
apolloProvider,
vulnerability: getVulnerability({}),
});
});
it('calls createFlash', async () => {
expect(findBadge().text()).toBe('detected');
wrapper.vm.refreshVulnerability();
await waitForPromises();
expect(findBadge().text()).toBe('detected');
expect(createFlash).toHaveBeenCalledTimes(1);
});
});
});
});
import { getFormattedIssue, getAddRelatedIssueRequestParams } from 'ee/vulnerabilities/helpers';
import {
getFormattedIssue,
getAddRelatedIssueRequestParams,
normalizeGraphQLVulnerability,
} from 'ee/vulnerabilities/helpers';
describe('Vulnerabilities helpers', () => {
describe('getFormattedIssue', () => {
......@@ -37,4 +41,28 @@ describe('Vulnerabilities helpers', () => {
},
);
});
describe('normalizeGraphQLVulnerability', () => {
it('returns null when vulnerability is null', () => {
expect(normalizeGraphQLVulnerability(null)).toBe(null);
});
it('normalizes the GraphQL response when the vulnerability is not null', () => {
expect(
normalizeGraphQLVulnerability({
confirmedBy: { id: 'gid://gitlab/User/16' },
resolvedBy: { id: 'gid://gitlab/User/16' },
dismissedBy: { id: 'gid://gitlab/User/16' },
state: 'DISMISSED',
id: 'gid://gitlab/Vulnerability/54',
}),
).toEqual({
confirmedById: '16',
resolvedById: '16',
dismissedById: '16',
state: 'dismissed',
id: '54',
});
});
});
});
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