Skip to content
Snippets Groups Projects
Unverified Commit 9f0187d5 authored by Savas Vedova's avatar Savas Vedova
Browse files

Refactor image component

This is a tiny refactoring to use the SearchSuggestion in the
Image component.
parent ead6a067
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