Commit 29917364 authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Enrique Alcántara

Animate the bulk actions section

parent 2702d91f
<script> <script>
import { GlEmptyState, GlFormCheckbox } from '@gitlab/ui'; import { GlCollapse, GlEmptyState, GlFormCheckbox } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import Pagination from '~/vue_shared/components/pagination_links.vue'; import Pagination from '~/vue_shared/components/pagination_links.vue';
import SecurityDashboardTableRow from './security_dashboard_table_row.vue'; import SecurityDashboardTableRow from './security_dashboard_table_row.vue';
...@@ -8,6 +8,7 @@ import SelectionSummary from './selection_summary_vuex.vue'; ...@@ -8,6 +8,7 @@ import SelectionSummary from './selection_summary_vuex.vue';
export default { export default {
name: 'SecurityDashboardTable', name: 'SecurityDashboardTable',
components: { components: {
GlCollapse,
GlEmptyState, GlEmptyState,
GlFormCheckbox, GlFormCheckbox,
Pagination, Pagination,
...@@ -61,7 +62,9 @@ export default { ...@@ -61,7 +62,9 @@ export default {
<template> <template>
<div class="ci-table js-security-dashboard-table" data-qa-selector="security_report_content"> <div class="ci-table js-security-dashboard-table" data-qa-selector="security_report_content">
<selection-summary v-if="isSelectingVulnerabilities" /> <gl-collapse :visible="isSelectingVulnerabilities" data-testid="selection-summary-collapse">
<selection-summary />
</gl-collapse>
<div class="gl-responsive-table-row table-row-header gl-bg-gray-50 text-2 px-2" role="row"> <div class="gl-responsive-table-row table-row-header gl-bg-gray-50 text-2 px-2" role="row">
<div class="table-section section-5"> <div class="table-section section-5">
<gl-form-checkbox <gl-form-checkbox
......
<script> <script>
import { GlButton, GlAlert } from '@gitlab/ui'; import { GlCollapse, GlButton, GlAlert } from '@gitlab/ui';
import vulnerabilityStateMutations from 'ee/security_dashboard/graphql/mutate_vulnerability_state'; import vulnerabilityStateMutations from 'ee/security_dashboard/graphql/mutate_vulnerability_state';
import { __, s__, n__ } from '~/locale'; import { __, s__, n__ } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
...@@ -9,6 +9,7 @@ import StatusDropdown from './status_dropdown.vue'; ...@@ -9,6 +9,7 @@ import StatusDropdown from './status_dropdown.vue';
export default { export default {
name: 'SelectionSummary', name: 'SelectionSummary',
components: { components: {
GlCollapse,
GlButton, GlButton,
GlAlert, GlAlert,
StatusDropdown, StatusDropdown,
...@@ -18,6 +19,11 @@ export default { ...@@ -18,6 +19,11 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
visible: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -97,30 +103,36 @@ export default { ...@@ -97,30 +103,36 @@ export default {
</script> </script>
<template> <template>
<div class="card gl-z-index-2!" :class="{ 'with-error': Boolean(updateErrorText) }"> <gl-collapse
<gl-alert v-if="updateErrorText" variant="danger" :dismissible="false"> :visible="visible"
{{ updateErrorText }} class="selection-summary gl-z-index-2!"
</gl-alert> data-testid="selection-summary-collapse"
>
<div class="card" :class="{ 'with-error': Boolean(updateErrorText) }">
<gl-alert v-if="updateErrorText" variant="danger" :dismissible="false">
{{ updateErrorText }}
</gl-alert>
<form class="card-body gl-display-flex gl-align-items-center" @submit.prevent="handleSubmit"> <form class="card-body gl-display-flex gl-align-items-center" @submit.prevent="handleSubmit">
<div <div
class="gl-line-height-0 gl-border-r-solid gl-border-gray-100 gl-pr-6 gl-border-1 gl-h-7 gl-display-flex gl-align-items-center" class="gl-line-height-0 gl-border-r-solid gl-border-gray-100 gl-pr-6 gl-border-1 gl-h-7 gl-display-flex gl-align-items-center"
>
<span
><b>{{ selectedVulnerabilitiesCount }}</b> {{ $options.i18n.selected }}</span
> >
</div> <span
<div class="gl-flex-fill-1 gl-ml-6 gl-mr-4"> ><b>{{ selectedVulnerabilitiesCount }}</b> {{ $options.i18n.selected }}</span
<status-dropdown @change="handleStatusDropdownChange" /> >
</div> </div>
<template v-if="shouldShowActionButtons"> <div class="gl-flex-fill-1 gl-ml-6 gl-mr-4">
<gl-button type="button" class="gl-mr-4" @click="resetSelected"> <status-dropdown @change="handleStatusDropdownChange" />
{{ $options.i18n.cancel }} </div>
</gl-button> <template v-if="shouldShowActionButtons">
<gl-button type="submit" category="primary" variant="confirm"> <gl-button type="button" class="gl-mr-4" @click="resetSelected">
{{ $options.i18n.changeStatus }} {{ $options.i18n.cancel }}
</gl-button> </gl-button>
</template> <gl-button type="submit" category="primary" variant="confirm">
</form> {{ $options.i18n.changeStatus }}
</div> </gl-button>
</template>
</form>
</div>
</gl-collapse>
</template> </template>
...@@ -305,8 +305,8 @@ export default { ...@@ -305,8 +305,8 @@ export default {
<template> <template>
<div class="vulnerability-list"> <div class="vulnerability-list">
<selection-summary <selection-summary
v-if="shouldShowSelectionSummary"
:selected-vulnerabilities="Object.values(selectedVulnerabilities)" :selected-vulnerabilities="Object.values(selectedVulnerabilities)"
:visible="shouldShowSelectionSummary"
@cancel-selection="deselectAllVulnerabilities" @cancel-selection="deselectAllVulnerabilities"
@vulnerability-updated="deselectVulnerability" @vulnerability-updated="deselectVulnerability"
/> />
......
...@@ -95,7 +95,7 @@ $selection-summary-with-error-height: 118px; ...@@ -95,7 +95,7 @@ $selection-summary-with-error-height: 118px;
// Due to position: sticky not being supported on Chrome (https://caniuse.com/#feat=css-sticky), // Due to position: sticky not being supported on Chrome (https://caniuse.com/#feat=css-sticky),
// the property is assigned to the th element as a workaround // the property is assigned to the th element as a workaround
.card, .selection-summary,
thead th { thead th {
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
......
---
title: Adds an expand/collapse transition when showing/hiding the bulk action section in the vulnerability lists.
merge_request: 60944
author:
type: changed
...@@ -4,7 +4,6 @@ import Vuex from 'vuex'; ...@@ -4,7 +4,6 @@ import Vuex from 'vuex';
import SecurityDashboardTable from 'ee/security_dashboard/components/security_dashboard_table.vue'; import SecurityDashboardTable from 'ee/security_dashboard/components/security_dashboard_table.vue';
import SecurityDashboardTableRow from 'ee/security_dashboard/components/security_dashboard_table_row.vue'; import SecurityDashboardTableRow from 'ee/security_dashboard/components/security_dashboard_table_row.vue';
import SelectionSummary from 'ee/security_dashboard/components/selection_summary.vue';
import createStore from 'ee/security_dashboard/store'; import createStore from 'ee/security_dashboard/store';
import { import {
...@@ -12,6 +11,7 @@ import { ...@@ -12,6 +11,7 @@ import {
RECEIVE_VULNERABILITIES_SUCCESS, RECEIVE_VULNERABILITIES_SUCCESS,
REQUEST_VULNERABILITIES, REQUEST_VULNERABILITIES,
} from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types'; } from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import mockDataVulnerabilities from '../store/modules/vulnerabilities/data/mock_data_vulnerabilities'; import mockDataVulnerabilities from '../store/modules/vulnerabilities/data/mock_data_vulnerabilities';
...@@ -25,7 +25,7 @@ describe('Security Dashboard Table', () => { ...@@ -25,7 +25,7 @@ describe('Security Dashboard Table', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(); store = createStore();
wrapper = shallowMount(SecurityDashboardTable, { wrapper = shallowMountExtended(SecurityDashboardTable, {
localVue, localVue,
store, store,
}); });
...@@ -37,6 +37,7 @@ describe('Security Dashboard Table', () => { ...@@ -37,6 +37,7 @@ describe('Security Dashboard Table', () => {
}); });
const findCheckbox = () => wrapper.find(GlFormCheckbox); const findCheckbox = () => wrapper.find(GlFormCheckbox);
const findSelectionSummaryCollapse = () => wrapper.findByTestId('selection-summary-collapse');
describe('while loading', () => { describe('while loading', () => {
beforeEach(() => { beforeEach(() => {
...@@ -63,7 +64,7 @@ describe('Security Dashboard Table', () => { ...@@ -63,7 +64,7 @@ describe('Security Dashboard Table', () => {
}); });
it('should not show the multi select box', () => { it('should not show the multi select box', () => {
expect(wrapper.find(SelectionSummary).exists()).toBe(false); expect(findSelectionSummaryCollapse().attributes('visible')).toBeFalsy();
}); });
it('should show the select all as unchecked', () => { it('should show the select all as unchecked', () => {
...@@ -76,7 +77,7 @@ describe('Security Dashboard Table', () => { ...@@ -76,7 +77,7 @@ describe('Security Dashboard Table', () => {
}); });
it('should show the multi select box', () => { it('should show the multi select box', () => {
expect(wrapper.find(SelectionSummary).exists()).toBe(true); expect(findSelectionSummaryCollapse().attributes('visible')).toBe('true');
}); });
it('should show the select all as checked', () => { it('should show the select all as checked', () => {
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlTable, GlTruncate } from '@gitlab/ui'; import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlTable, GlTruncate } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/empty_states/dashboard_has_no_vulnerabilities.vue'; import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/empty_states/dashboard_has_no_vulnerabilities.vue';
import FiltersProducedNoResults from 'ee/security_dashboard/components/empty_states/filters_produced_no_results.vue'; import FiltersProducedNoResults from 'ee/security_dashboard/components/empty_states/filters_produced_no_results.vue';
...@@ -9,35 +8,33 @@ import VulnerabilityCommentIcon from 'ee/security_dashboard/components/vulnerabi ...@@ -9,35 +8,33 @@ import VulnerabilityCommentIcon from 'ee/security_dashboard/components/vulnerabi
import VulnerabilityList from 'ee/security_dashboard/components/vulnerability_list.vue'; import VulnerabilityList from 'ee/security_dashboard/components/vulnerability_list.vue';
import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue'; import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { generateVulnerabilities, vulnerabilities } from './mock_data'; import { generateVulnerabilities, vulnerabilities } from './mock_data';
describe('Vulnerability list component', () => { describe('Vulnerability list component', () => {
let wrapper; let wrapper;
const createWrapper = ({ props = {}, listeners, provide = {} } = {}) => { const createWrapper = ({ props = {}, listeners, provide = {} } = {}) => {
return extendedWrapper( return mountExtended(VulnerabilityList, {
mount(VulnerabilityList, { propsData: {
propsData: { vulnerabilities: [],
vulnerabilities: [], ...props,
...props, },
}, stubs: {
stubs: { GlPopover: true,
GlPopover: true, },
}, listeners,
listeners, provide: () => ({
provide: () => ({ noVulnerabilitiesSvgPath: '#',
noVulnerabilitiesSvgPath: '#', dashboardDocumentation: '#',
dashboardDocumentation: '#', emptyStateSvgPath: '#',
emptyStateSvgPath: '#', notEnabledScannersHelpPath: '#',
notEnabledScannersHelpPath: '#', noPipelineRunScannersHelpPath: '#',
noPipelineRunScannersHelpPath: '#', hasVulnerabilities: true,
hasVulnerabilities: true, hasJiraVulnerabilitiesIntegrationEnabled: false,
hasJiraVulnerabilitiesIntegrationEnabled: false, ...provide,
...provide,
}),
}), }),
); });
}; };
const locationText = ({ file, startLine }) => `${file}:${startLine}`; const locationText = ({ file, startLine }) => `${file}:${startLine}`;
...@@ -140,14 +137,14 @@ describe('Vulnerability list component', () => { ...@@ -140,14 +137,14 @@ describe('Vulnerability list component', () => {
}); });
it('should not show the selection summary if no vulnerabilities are selected', () => { it('should not show the selection summary if no vulnerabilities are selected', () => {
expect(findSelectionSummary().exists()).toBe(false); expect(findSelectionSummary().props('visible')).toBe(false);
}); });
it('should show the selection summary when a checkbox is selected', async () => { it('should show the selection summary when a checkbox is selected', async () => {
findDataCell('vulnerability-checkbox').setChecked(true); findDataCell('vulnerability-checkbox').setChecked(true);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(findSelectionSummary().exists()).toBe(true); expect(findSelectionSummary().props('visible')).toBe(true);
}); });
it('should sync selected vulnerabilities when the vulnerability list is updated', async () => { it('should sync selected vulnerabilities when the vulnerability list is updated', async () => {
...@@ -161,7 +158,7 @@ describe('Vulnerability list component', () => { ...@@ -161,7 +158,7 @@ describe('Vulnerability list component', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(findSelectionSummary().exists()).toBe(false); expect(findSelectionSummary().props('visible')).toBe(false);
}); });
it('should uncheck a selected vulnerability after the vulnerability is updated', async () => { it('should uncheck a selected vulnerability after the vulnerability is updated', async () => {
......
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