Skip to content
Snippets Groups Projects
Unverified Commit 3f443be2 authored by David Pisek's avatar David Pisek Committed by GitLab
Browse files

Merge branch '494204-use-search-suggestions-image-token' into 'master'

parents d22974b9 9f0187d5
No related branches found
No related tags found
No related merge requests found
Showing
with 124 additions and 158 deletions
<script>
import { GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
import { GlFilteredSearchSuggestion, GlIcon, GlTruncate } from '@gitlab/ui';
 
export default {
components: {
GlFilteredSearchSuggestion,
GlIcon,
GlTruncate,
},
props: {
/**
Loading
Loading
@@ -31,6 +32,11 @@ export default {
type: Boolean,
required: true,
},
truncate: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
Loading
Loading
@@ -43,7 +49,8 @@ export default {
:class="{ 'gl-invisible': !selected }"
:data-testid="`${name}-icon-${value}`"
/>
{{ text }}
<gl-truncate v-if="truncate" position="middle" :text="text" />
<template v-else>{{ text }}</template>
</div>
</gl-filtered-search-suggestion>
</template>
Loading
Loading
@@ -250,7 +250,7 @@ export default {
@complete="emitFiltersChanged"
>
<template #view>
{{ toggleText }}
<span data-testid="activity-token-placeholder">{{ toggleText }}</span>
</template>
<template #suggestions>
<template v-for="(group, index) in activityTokenGroups">
Loading
Loading
Loading
Loading
@@ -155,7 +155,7 @@ export default {
@complete="emitFiltersChanged"
>
<template #view>
{{ toggleText }}
<span data-testid="cluster-token-placeholder">{{ toggleText }}</span>
</template>
<template #suggestions>
<gl-loading-icon v-if="isLoading" size="sm" />
Loading
Loading
<script>
import {
GlIcon,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlLoadingIcon,
GlTruncate,
} from '@gitlab/ui';
import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
import { getSelectedOptionsText } from '~/lib/utils/listbox_helpers';
import { s__ } from '~/locale';
import { createAlert } from '~/alert';
import agentImagesQuery from 'ee/security_dashboard/graphql/queries/agent_images.query.graphql';
import projectImagesQuery from 'ee/security_dashboard/graphql/queries/project_images.query.graphql';
import SearchSuggestion from '../components/search_suggestion.vue';
import QuerystringSync from '../../filters/querystring_sync.vue';
import { ALL_ID as ALL_IMAGES_VALUE } from '../../filters/constants';
import eventHub from '../event_hub';
 
export default {
components: {
GlIcon,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlLoadingIcon,
GlTruncate,
QuerystringSync,
SearchSuggestion,
},
inject: {
agentName: { default: '' },
Loading
Loading
@@ -163,22 +156,16 @@ export default {
</template>
<template #suggestions>
<gl-loading-icon v-if="isLoading" size="sm" />
<gl-filtered-search-suggestion
<search-suggestion
v-for="image in items"
v-else
:key="image.value"
:value="image.value"
>
<div class="gl-flex gl-items-center">
<gl-icon
name="check"
class="gl-mr-3 gl-shrink-0 gl-text-gray-700"
:class="{ 'gl-invisible': !isImageSelected(image.value) }"
:data-testid="`image-icon-${image.value}`"
/>
<gl-truncate position="middle" :text="image.text" data-testid="truncate-image" />
</div>
</gl-filtered-search-suggestion>
:text="image.text"
:selected="isImageSelected(image.value)"
:data-testid="`suggestion-${image.value}`"
truncate
/>
</template>
</gl-filtered-search-token>
</querystring-sync>
Loading
Loading
Loading
Loading
@@ -186,7 +186,7 @@ export default {
@complete="emitFiltersChanged"
>
<template #view>
{{ toggleText }}
<span data-testid="status-token-placeholder">{{ toggleText }}</span>
</template>
<template #suggestions>
<gl-dropdown-section-header>{{ $options.i18n.statusLabel }}</gl-dropdown-section-header>
Loading
Loading
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import { GlFilteredSearchSuggestion, GlTruncate } from '@gitlab/ui';
import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_search/components/search_suggestion.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 
describe('Search Suggestion', () => {
let wrapper;
 
const createWrapper = ({ text, name, value, selected }) => {
const createWrapper = ({ text, name, value, selected, truncate }) => {
wrapper = shallowMountExtended(SearchSuggestion, {
propsData: {
text,
name,
value,
selected,
truncate,
},
});
};
Loading
Loading
@@ -35,4 +36,18 @@ describe('Search Suggestion', () => {
expect(findGlSearchSuggestion().props('value')).toBe('my_value');
expect(wrapper.findByTestId('test-icon-my_value').classes('gl-invisible')).toBe(!selected);
});
it.each`
truncate
${true}
${false}
`('truncates the text when `truncate` property is $truncate', ({ truncate }) => {
createWrapper({ text: 'My text', value: 'My value', selected: false, truncate });
expect(wrapper.findComponent(GlTruncate).exists()).toBe(truncate);
});
it('truncates the text when `truncate` property is $truncate', () => {
createWrapper({ text: 'My text', value: 'My value', selected: false, truncate: true });
expect(wrapper.findComponent(GlTruncate).props('text')).toBe('My text');
});
});
import { GlFilteredSearchToken, GlBadge } from '@gitlab/ui';
import { GlFilteredSearchToken, GlDropdownSectionHeader, GlBadge } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import ActivityToken from 'ee/security_dashboard/components/shared/filtered_search/tokens/activity_token.vue';
Loading
Loading
@@ -76,23 +76,9 @@ describe('ActivityToken', () => {
};
 
describe('default view', () => {
const findViewSlot = () => wrapper.findByTestId('slot-view');
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
const createWrapperWithAbility = ({ resolveVulnerabilityWithAi } = {}) => {
createWrapper({
stubs: {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `
<div>
<div data-testid="slot-view">
<slot name="view"></slot>
</div>
<div data-testid="slot-suggestions">
<slot name="suggestions"></slot>
</div>
</div>`,
}),
},
provide: {
glFeatures: {
vulnerabilityReportVrFilter: true,
Loading
Loading
@@ -106,31 +92,34 @@ describe('ActivityToken', () => {
 
it('shows the label', () => {
createWrapperWithAbility();
expect(findViewSlot().text()).toBe('Still detected');
expect(findFilteredSearchToken().props('value')).toEqual({
data: ['STILL_DETECTED'],
operator: '||',
});
expect(wrapper.findByTestId('activity-token-placeholder').text()).toBe('Still detected');
});
 
const baseOptions = [
'All activity',
'Detection', // group header
'Still detected',
'No longer detected',
'Issue', // group header
'Has issue',
'Does not have issue',
'Merge Request', // group header
'Has merge request',
'Does not have merge request',
'Solution available', // group header
'Has a solution',
'Does not have a solution',
];
 
const aiOptions = [
'GitLab Duo (AI)', // group header
'Vulnerability Resolution available',
'Vulnerability Resolution unavailable',
];
 
const baseGroupHeaders = ['Detection', 'Issue', 'Merge Request', 'Solution available'];
const aiGroupHeaders = ['GitLab Duo (AI)'];
it.each`
resolveVulnerabilityWithAi | expectedOptions
${true} | ${[...baseOptions, ...aiOptions]}
Loading
Loading
@@ -140,16 +129,26 @@ describe('ActivityToken', () => {
({ resolveVulnerabilityWithAi, expectedOptions }) => {
createWrapperWithAbility({ resolveVulnerabilityWithAi });
 
// All options are rendered in the #suggestions slot of GlFilteredSearchToken
const findDropdownOptions = () => wrapper.findByTestId('slot-suggestions');
const findDropdownOptions = () =>
wrapper.findAllComponents(SearchSuggestion).wrappers.map((c) => c.text());
expect(findDropdownOptions()).toEqual(expectedOptions);
},
);
it.each`
resolveVulnerabilityWithAi | expectedGroups
${true} | ${[...baseGroupHeaders, ...aiGroupHeaders]}
${false} | ${baseGroupHeaders}
`(
'shows the group headers correctly resolveVulnerabilityWithAi=$resolveVulnerabilityWithAi',
({ resolveVulnerabilityWithAi, expectedGroups }) => {
createWrapperWithAbility({ resolveVulnerabilityWithAi });
const findDropdownGroupHeaders = () =>
wrapper.findAllComponents(GlDropdownSectionHeader).wrappers.map((c) => c.text());
 
expect(
findDropdownOptions()
.text()
.split('\n')
.map((s) => s.trim())
.filter((i) => i),
).toEqual(expectedOptions);
expect(findDropdownGroupHeaders()).toEqual(expectedGroups);
},
);
 
Loading
Loading
Loading
Loading
@@ -11,7 +11,6 @@ import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_s
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 { OPERATORS_OR } from '~/vue_shared/components/filtered_search_bar/constants';
import { stubComponent } from 'helpers/stub_component';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { projectClusters } from 'ee_jest/security_dashboard/components/mock_data';
 
Loading
Loading
@@ -78,44 +77,29 @@ describe('Cluster Token component', () => {
};
 
describe('default view', () => {
const findViewSlot = () => wrapper.findByTestId('slot-view');
const findSuggestionsSlot = () => wrapper.findByTestId('slot-suggestions');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
 
const stubs = {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `
<div>
<div data-testid="slot-view">
<slot name="view"></slot>
</div>
<div data-testid="slot-suggestions">
<slot name="suggestions"></slot>
</div>
</div>`,
}),
};
beforeEach(() => {
createWrapper({
stubs,
});
createWrapper();
});
 
it('shows the label', () => {
expect(findViewSlot().text()).toBe('All clusters');
expect(findFilteredSearchToken().props('value')).toEqual({ data: ['ALL'] });
expect(wrapper.findByTestId('cluster-token-placeholder').text()).toBe('All clusters');
});
 
it('shows the dropdown with correct options', async () => {
await waitForPromises();
 
expect(
findSuggestionsSlot()
.text()
.split('\n')
.map((s) => s.trim())
.filter((i) => i),
).toEqual(['All clusters', 'primary-agent', 'james-bond-agent', 'jason-bourne-agent']);
const findDropdownOptions = () =>
wrapper.findAllComponents(SearchSuggestion).wrappers.map((c) => c.text());
expect(findDropdownOptions()).toEqual([
'All clusters',
'primary-agent',
'james-bond-agent',
'jason-bourne-agent',
]);
});
 
it('shows the loading icon when cluster agents are not yet loaded', async () => {
Loading
Loading
Loading
Loading
@@ -8,6 +8,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import agentImagesQuery from 'ee/security_dashboard/graphql/queries/agent_images.query.graphql';
import projectImagesQuery from 'ee/security_dashboard/graphql/queries/project_images.query.graphql';
import ImageToken from 'ee/security_dashboard/components/shared/filtered_search/tokens/image_token.vue';
import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_search/components/search_suggestion.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 { OPERATORS_OR } from '~/vue_shared/components/filtered_search_bar/constants';
Loading
Loading
@@ -68,14 +69,16 @@ describe('Image Token component', () => {
projectFullPath,
...provide,
},
stubs,
stubs: {
SearchSuggestion,
...stubs,
},
});
};
 
const findQuerystringSync = () => wrapper.findComponent(QuerystringSync);
const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
const findCheckedIcon = (value) => wrapper.findByTestId(`image-icon-${value}`);
const isOptionChecked = (v) => !findCheckedIcon(v).classes('gl-invisible');
const isOptionChecked = (v) => wrapper.findByTestId(`suggestion-${v}`).props('selected') === true;
 
const clickDropdownItem = async (...ids) => {
await Promise.all(
Loading
Loading
@@ -94,8 +97,9 @@ describe('Image Token component', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findTruncateTexts = () =>
wrapper
.findAllByTestId('truncate-image')
.wrappers.map((component) => component.props('text'));
.findAllComponents(SearchSuggestion)
.wrappers.filter((component) => component.props('truncate'))
.map((component) => component.props('text'));
 
const stubs = {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
Loading
Loading
Loading
Loading
@@ -6,7 +6,6 @@ import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_s
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 { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
import { stubComponent } from 'helpers/stub_component';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
 
Vue.use(VueRouter);
Loading
Loading
@@ -74,45 +73,29 @@ describe('Status Token component', () => {
};
 
describe('default view', () => {
const findSlotView = () => wrapper.findByTestId('slot-view');
const findSlotSuggestions = () => wrapper.findByTestId('slot-suggestions');
const findDropdownOptions = () =>
wrapper.findAllComponents(SearchSuggestion).wrappers.map((c) => c.props('text'));
 
beforeEach(() => {
createWrapper({
stubs: {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `
<div>
<div data-testid="slot-view">
<slot name="view"></slot>
</div>
<div data-testid="slot-suggestions">
<slot name="suggestions"></slot>
</div>
</div>`,
}),
},
});
createWrapper();
});
 
it('shows the label', () => {
expect(findSlotView().text()).toBe('Needs triage, Confirmed');
expect(findFilteredSearchToken().props('value')).toEqual({
data: ['DETECTED', 'CONFIRMED'],
operator: '=',
});
expect(wrapper.findByTestId('status-token-placeholder').text()).toBe(
'Needs triage, Confirmed',
);
});
 
it('shows the dropdown with correct options', () => {
expect(
findSlotSuggestions()
.text()
.split('\n')
.map((s) => s.trim())
.filter((i) => i),
).toEqual([
'Status', // subheader
expect(findDropdownOptions()).toEqual([
'All statuses',
'Needs triage',
'Confirmed',
'Resolved',
'Dismissed as...', // subheader
'All dismissal reasons',
'Acceptable risk',
'False positive',
Loading
Loading
import { GlFilteredSearchToken } from '@gitlab/ui';
import { GlFilteredSearchToken, GlDropdownSectionHeader } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import ToolToken from 'ee/security_dashboard/components/shared/filtered_search/tokens/tool_token.vue';
Loading
Loading
@@ -6,7 +6,6 @@ import QuerystringSync from 'ee/security_dashboard/components/shared/filters/que
import SearchSuggestion from 'ee/security_dashboard/components/shared/filtered_search/components/search_suggestion.vue';
import eventHub from 'ee/security_dashboard/components/shared/filtered_search/event_hub';
import { OPERATORS_OR } from '~/vue_shared/components/filtered_search_bar/constants';
import { stubComponent } from 'helpers/stub_component';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { MOCK_SCANNERS } from './mock_data';
 
Loading
Loading
@@ -81,61 +80,49 @@ describe('ToolToken', () => {
};
 
describe('default view', () => {
const findViewSlot = () => wrapper.findByTestId('slot-view');
beforeEach(() => {
createWrapper({
stubs: {
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
template: `
<div>
<div data-testid="slot-view">
<slot name="view"></slot>
</div>
<div data-testid="slot-suggestions">
<slot name="suggestions"></slot>
</div>
</div>`,
}),
},
});
createWrapper();
});
 
it('shows the label', () => {
expect(findViewSlot().text()).toBe('All tools');
expect(findFilteredSearchToken().props('value')).toEqual({
data: ['ALL'],
operator: '||',
});
expect(wrapper.findByTestId('tool-token-value').text()).toBe('All tools');
});
 
it('shows the dropdown with correct options', () => {
// All options are rendered in the #suggestions slot of GlFilteredSearchToken
const findDropdownOptions = () => wrapper.findByTestId('slot-suggestions');
expect(
findDropdownOptions()
.text()
.split('\n')
.map((s) => s.trim())
.filter((i) => i),
).toEqual([
'Tool', // group header
const findDropdownOptions = () =>
wrapper.findAllComponents(SearchSuggestion).wrappers.map((c) => c.text());
const findDropdownGroupHeaders = () =>
wrapper.findAllComponents(GlDropdownSectionHeader).wrappers.map((c) => c.text());
expect(findDropdownOptions()).toEqual([
'All tools',
'Manually added',
'API Fuzzing', // group header
'GitLab API Fuzzing',
'Container Scanning', // group header
'Trivy',
'Coverage Fuzzing', // group header
'libfuzzer',
'DAST', // group header
'OWASP Zed Attack Proxy (ZAP)',
'Dependency Scanning', // group header
'Gemnasium',
'SAST', // group header
'ESLint',
'Find Security Bugs',
'A Custom Scanner (SamScan)',
'Secret Detection', // group header
'GitLeaks',
]);
expect(findDropdownGroupHeaders()).toEqual([
'Tool',
'API Fuzzing',
'Container Scanning',
'Coverage Fuzzing',
'DAST',
'Dependency Scanning',
'SAST',
'Secret Detection',
]);
});
});
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment