Skip to content
代码片段 群组 项目
未验证 提交 51a5528f 编辑于 作者: Lorenz van Herwaarden's avatar Lorenz van Herwaarden 提交者: GitLab
浏览文件

Merge branch '508713-identifier-filter-group-level' into 'master'

No related branches found
No related tags found
无相关合并请求
......@@ -2,7 +2,8 @@
import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import { createAlert } from '~/alert';
import identifiersQuery from 'ee/security_dashboard/graphql/queries/project_identifiers.query.graphql';
import projectIdentifiersQuery from 'ee/security_dashboard/graphql/queries/project_identifiers.query.graphql';
import groupIdentifiersQuery from 'ee/security_dashboard/graphql/queries/group_identifiers.query.graphql';
import SearchSuggestion from '../components/search_suggestion.vue';
import QuerystringSync from '../../filters/querystring_sync.vue';
import eventHub from '../event_hub';
......@@ -16,7 +17,14 @@ export default {
QuerystringSync,
SearchSuggestion,
},
inject: ['projectFullPath'],
inject: {
projectFullPath: {
default: '',
},
groupFullPath: {
default: '',
},
},
props: {
config: {
type: Object,
......@@ -34,16 +42,18 @@ export default {
},
apollo: {
identifiers: {
query: identifiersQuery,
query() {
return this.queryConfig.query || '';
},
debounce: 300,
variables() {
return {
searchTerm: this.searchTerm,
fullPath: this.projectFullPath,
fullPath: this.queryConfig.fullPath || '',
};
},
update(data) {
return data.project?.vulnerabilityIdentifierSearch || [];
return data?.[this.queryConfig?.dataPath].vulnerabilityIdentifierSearch || [];
},
result() {
this.isLoadingIdentifiers = false;
......@@ -71,6 +81,24 @@ export default {
};
},
computed: {
queryConfig() {
const namespaceType = this.groupFullPath ? 'group' : 'project';
const queryTypes = {
group: {
query: groupIdentifiersQuery,
fullPath: this.groupFullPath,
dataPath: 'group',
},
project: {
query: projectIdentifiersQuery,
fullPath: this.projectFullPath,
dataPath: 'project',
},
};
return queryTypes[namespaceType];
},
queryStringValue() {
return this.selectedIdentifier ? [this.selectedIdentifier] : [];
},
......
......@@ -26,6 +26,9 @@ export default {
toolFilterType: {
default: 'scanner', // scanner or reportType
},
projectFullPath: {
default: '',
},
},
props: {
availableFilters: {
......@@ -55,7 +58,9 @@ export default {
// This `includes` part is necessary because we don't include this filter everywhere.
if (this.availableFilters.includes(FILTERS.IDENTIFIER)) {
nonDefaultTypes.push('identifier');
if (this.projectFullPath || this.glFeatures?.vulnerabilityFilteringByIdentifierGroup) {
nonDefaultTypes.push('identifier');
}
}
nonDefaultTypes.forEach((type) => {
......@@ -112,7 +117,11 @@ export default {
case FILTERS.PROJECT:
return PROJECT_TOKEN_DEFINITION;
case FILTERS.IDENTIFIER:
return IDENTIFIER_TOKEN_DEFINITION;
if (this.projectFullPath || this.glFeatures?.vulnerabilityFilteringByIdentifierGroup) {
return IDENTIFIER_TOKEN_DEFINITION;
}
return undefined;
default:
return undefined;
}
......
query groupIdentifiers($fullPath: ID!, $searchTerm: String!) {
group(fullPath: $fullPath) {
id
vulnerabilityIdentifierSearch(name: $searchTerm)
}
}
......@@ -14,6 +14,7 @@ query groupVulnerabilities(
$scannerId: [VulnerabilitiesScannerID!]
$state: [VulnerabilityState!]
$dismissalReason: [VulnerabilityDismissalReason!]
$identifierName: String
$sort: VulnerabilitySort
$hasIssues: Boolean
$hasResolution: Boolean
......@@ -46,6 +47,7 @@ query groupVulnerabilities(
hasAiResolution: $hasAiResolution
clusterAgentId: $clusterAgentId
owaspTopTen: $owaspTopTen
identifierName: $identifierName
) {
nodes {
...VulnerabilityFragment
......
......@@ -18,6 +18,7 @@ class VulnerabilitiesController < Groups::ApplicationController
push_frontend_feature_flag(:vulnerability_report_vr_badge, @group, type: :beta)
push_frontend_feature_flag(:vulnerability_report_vr_filter, @group, type: :beta)
push_frontend_feature_flag(:vulnerability_report_filtered_search_v2, @project, type: :wip)
push_frontend_feature_flag(:vulnerability_filtering_by_identifier_group, @group, type: :beta)
push_frontend_feature_flag(:enhanced_vulnerability_bulk_actions, @group, type: :wip)
push_frontend_ability(ability: :resolve_vulnerability_with_ai, resource: @group, user: current_user)
......
......@@ -5,14 +5,16 @@ import VueApollo from 'vue-apollo';
import IdentifierToken from 'ee/security_dashboard/components/shared/filtered_search/tokens/identifier_token.vue';
import QuerystringSync from 'ee/security_dashboard/components/shared/filters/querystring_sync.vue';
import eventHub from 'ee/security_dashboard/components/shared/filtered_search/event_hub';
import identifierSearch from 'ee/security_dashboard/graphql/queries/project_identifiers.query.graphql';
import projectIdentifierSearch from 'ee/security_dashboard/graphql/queries/project_identifiers.query.graphql';
import groupIdentifierSearch from 'ee/security_dashboard/graphql/queries/group_identifiers.query.graphql';
import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_search/components/search_suggestion.vue';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
import { createAlert } from '~/alert';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import { createAlert } from '~/alert';
Vue.use(VueApollo);
Vue.use(VueRouter);
......@@ -30,21 +32,23 @@ describe('Identifier Token component', () => {
operators: OPERATORS_IS,
};
const createMockApolloProvider = ({ handlers = {} } = {}) => {
const createMockApolloProvider = ({ handlers = {}, namespace, query } = {}) => {
const capitalized = capitalizeFirstCharacter(namespace);
const defaultHandlers = {
identifierSearch: jest.fn().mockResolvedValue({
data: {
project: {
id: 'gid://gitlab/Project/19',
[namespace]: {
id: `gid://gitlab/${capitalized}/19`,
vulnerabilityIdentifierSearch: ['CVE-2018-3741'],
__typename: 'Project',
__typename: capitalized,
},
},
}),
};
const handlerMocks = { ...defaultHandlers, ...handlers };
const requestHandlers = [[identifierSearch, handlerMocks.identifierSearch]];
const requestHandlers = [[query, handlerMocks.identifierSearch]];
return createMockApollo(requestHandlers);
};
......@@ -55,6 +59,8 @@ describe('Identifier Token component', () => {
stubs,
provide = {},
handlers = {},
namespace = 'project',
query = projectIdentifierSearch,
mountFn = shallowMountExtended,
} = {}) => {
router = new VueRouter({ mode: 'history' });
......@@ -89,7 +95,9 @@ describe('Identifier Token component', () => {
...stubs,
},
apolloProvider: createMockApolloProvider({
namespace,
handlers,
query,
}),
});
};
......@@ -225,6 +233,42 @@ describe('Identifier Token component', () => {
});
});
describe('group level', () => {
beforeEach(() => {
createWrapper({
provide: {
projectFullPath: '',
groupFullPath: 'my-group',
},
query: groupIdentifierSearch,
namespace: 'group',
});
});
it('handles fuzzy search', async () => {
const CVE = 'CVE-2018-3741';
eventSpy = jest.fn();
eventHub.$on('filters-changed', eventSpy);
await searchTerm('CVE-2018');
await waitForPromises();
expect(wrapper.findByTestId(`suggestion-${CVE}`).exists()).toBe(true);
// The alert should not be called on succesful calls
expect(createAlert).not.toHaveBeenCalled();
await selectOption(CVE);
expect(eventSpy).toHaveBeenCalledWith({
identifierName: CVE,
});
expect(eventSpy).toHaveBeenCalledTimes(1);
});
});
describe('QuerystringSync component', () => {
beforeEach(() => {
eventSpy = jest.fn();
......
......@@ -82,7 +82,10 @@ describe('Vulnerability Report Filtered Search component', () => {
`(
`passes the expected available tokens for filters '$name'`,
({ availableFilters, availableTokens }) => {
createWrapper({ availableFilters });
createWrapper({
availableFilters,
glFeatures: { vulnerabilityFilteringByIdentifierGroup: true },
});
expect(findFilteredSearchComponent().props('availableTokens')).toEqual(availableTokens);
},
......@@ -149,4 +152,53 @@ describe('Vulnerability Report Filtered Search component', () => {
]);
});
});
describe('when vulnerabilityFilteringByIdentifierGroup feature flag is on', () => {
beforeEach(() => {
createWrapper({
availableFilters: [FILTERS.STATUS, FILTERS.ACTIVITY, FILTERS.PROJECT, FILTERS.IDENTIFIER],
query: {
identifier: 'cve-test',
},
glFeatures: {
vulnerabilityFilteringByIdentifierGroup: true,
},
});
});
it('includes identifier token in available tokens', () => {
expect(findFilteredSearchComponent().props('availableTokens')).toEqual([
STATUS_TOKEN_DEFINITION,
ACTIVITY_TOKEN_DEFINITION,
PROJECT_TOKEN_DEFINITION,
IDENTIFIER_TOKEN_DEFINITION,
]);
});
it('should pass route parameters to the identifier token', () => {
expect(findFilteredSearchComponent().props('value')).toEqual([
{
type: 'state',
value: {
data: StatusToken.DEFAULT_VALUES,
operator: '||',
},
},
{
type: 'activity',
value: {
data: ActivityToken.DEFAULT_VALUES,
operator: '||',
},
},
{
type: 'identifier',
value: {
data: ['cve-test'],
operator: '=',
},
},
]);
});
});
});
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册