Commit dde9f6e4 authored by Daniel Tian's avatar Daniel Tian Committed by Savas Vedova

Add indeterminate state to select all checkbox on vulnerability report

Changelog: changed
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79398
EE: true
parent ba10dd4e
...@@ -98,6 +98,10 @@ export default { ...@@ -98,6 +98,10 @@ export default {
(v) => v.scanner?.vendor !== 'GitLab' && v.scanner?.vendor !== '', (v) => v.scanner?.vendor !== 'GitLab' && v.scanner?.vendor !== '',
); );
}, },
hasSelectedSomeVulnerabilities() {
// Whether the user has selected at least 1, but not all vulnerabilities.
return this.numOfSelectedVulnerabilities > 0 && !this.hasSelectedAllVulnerabilities;
},
hasSelectedAllVulnerabilities() { hasSelectedAllVulnerabilities() {
if (!this.vulnerabilities.length) { if (!this.vulnerabilities.length) {
return false; return false;
...@@ -330,6 +334,7 @@ export default { ...@@ -330,6 +334,7 @@ export default {
class="gl-m-0" class="gl-m-0"
data-testid="vulnerability-checkbox-all" data-testid="vulnerability-checkbox-all"
:checked="hasSelectedAllVulnerabilities" :checked="hasSelectedAllVulnerabilities"
:indeterminate="hasSelectedSomeVulnerabilities"
@change="toggleAllVulnerabilities" @change="toggleAllVulnerabilities"
/> />
</template> </template>
......
...@@ -100,6 +100,10 @@ export default { ...@@ -100,6 +100,10 @@ export default {
(v) => v.scanner?.vendor !== 'GitLab' && v.scanner?.vendor !== '', (v) => v.scanner?.vendor !== 'GitLab' && v.scanner?.vendor !== '',
); );
}, },
hasSelectedSomeVulnerabilities() {
// Whether the user has selected at least 1, but not all vulnerabilities.
return this.numOfSelectedVulnerabilities > 0 && !this.hasSelectedAllVulnerabilities;
},
hasSelectedAllVulnerabilities() { hasSelectedAllVulnerabilities() {
if (!this.vulnerabilities.length) { if (!this.vulnerabilities.length) {
return false; return false;
...@@ -282,6 +286,7 @@ export default { ...@@ -282,6 +286,7 @@ export default {
class="gl-display-table-cell" class="gl-display-table-cell"
data-testid="vulnerability-checkbox-all" data-testid="vulnerability-checkbox-all"
:checked="hasSelectedAllVulnerabilities" :checked="hasSelectedAllVulnerabilities"
:indeterminate="hasSelectedSomeVulnerabilities"
@change="toggleAllVulnerabilities" @change="toggleAllVulnerabilities"
/> />
</template> </template>
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlTable, GlTruncate } from '@gitlab/ui'; import {
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlFormCheckbox,
GlTable,
GlTruncate,
} from '@gitlab/ui';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue'; import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/shared/empty_states/dashboard_has_no_vulnerabilities.vue';
...@@ -12,6 +17,7 @@ import FalsePositiveBadge from 'ee/vulnerabilities/components/false_positive_bad ...@@ -12,6 +17,7 @@ import FalsePositiveBadge from 'ee/vulnerabilities/components/false_positive_bad
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 { mountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import { generateVulnerabilities, vulnerabilities } from '../mock_data'; import { generateVulnerabilities, vulnerabilities } from '../mock_data';
describe('Vulnerability list component', () => { describe('Vulnerability list component', () => {
...@@ -66,6 +72,8 @@ describe('Vulnerability list component', () => { ...@@ -66,6 +72,8 @@ describe('Vulnerability list component', () => {
const findDashboardHasNoVulnerabilities = () => const findDashboardHasNoVulnerabilities = () =>
wrapper.findComponent(DashboardHasNoVulnerabilities); wrapper.findComponent(DashboardHasNoVulnerabilities);
const findVendorNames = () => wrapper.findByTestId('vulnerability-vendor'); const findVendorNames = () => wrapper.findByTestId('vulnerability-vendor');
const findCheckAllCheckbox = () => wrapper.findByTestId('vulnerability-checkbox-all');
const findAllRowCheckboxes = () => wrapper.findAllByTestId('vulnerability-checkbox');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -636,6 +644,54 @@ describe('Vulnerability list component', () => { ...@@ -636,6 +644,54 @@ describe('Vulnerability list component', () => {
}); });
}); });
describe('select all checkbox', () => {
it('will toggle between selecting all and deselecting all vulnerabilities', async () => {
const getChecked = () => findAllRowCheckboxes().filter((x) => x.element.checked === true);
wrapper = createWrapper({ props: { vulnerabilities } });
// Sanity check to ensure that everything starts off unchecked.
expect(getChecked()).toHaveLength(0);
await findCheckAllCheckbox().trigger('click');
// First click should select all rows.
expect(getChecked()).toHaveLength(vulnerabilities.length);
await findCheckAllCheckbox().trigger('click');
// Second click should un-select all rows.
expect(getChecked()).toHaveLength(0);
});
it('will toggle the indeterminate state when some but not all vulnerabilities are selected', async () => {
const expectIndeterminateState = (state) =>
expect(findCheckAllCheckbox().props('indeterminate')).toBe(state);
wrapper = createWrapper({
props: { vulnerabilities },
stubs: { GlFormCheckbox: stubComponent(GlFormCheckbox, { props: ['indeterminate'] }) },
});
// We start off with no items selected, so no indeterminate state.
expectIndeterminateState(false);
await findRow(1).trigger('click');
// When we go from 0 to 1 item selected, indeterminate state should be true.
expectIndeterminateState(true);
await findRow(1).trigger('click');
// When we go from 1 to 0 items selected, indeterminate state should be false.
expectIndeterminateState(false);
// Check all items.
findCheckAllCheckbox().trigger('click');
// When all the items are selected, indeterminate state should be false.
expectIndeterminateState(false);
await findRow(1).trigger('click');
// When we uncheck an item when all items are selected, indeterminate state should be true.
expectIndeterminateState(true);
});
});
describe('when it is the pipeline dashboard', () => { describe('when it is the pipeline dashboard', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ wrapper = createWrapper({
......
import { GlSkeletonLoading, GlTable, GlTruncate } from '@gitlab/ui'; import { GlSkeletonLoading, GlTable, GlTruncate, GlFormCheckbox } from '@gitlab/ui';
import { capitalize } from 'lodash'; import { capitalize } from 'lodash';
import { Portal } from 'portal-vue'; import { Portal } from 'portal-vue';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
...@@ -14,6 +14,7 @@ import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue' ...@@ -14,6 +14,7 @@ import RemediatedBadge from 'ee/vulnerabilities/components/remediated_badge.vue'
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { FIELDS } from 'ee/security_dashboard/components/shared/vulnerability_report/constants'; import { FIELDS } from 'ee/security_dashboard/components/shared/vulnerability_report/constants';
import { stubComponent } from 'helpers/stub_component';
import { generateVulnerabilities, vulnerabilities } from '../../mock_data'; import { generateVulnerabilities, vulnerabilities } from '../../mock_data';
const { DETECTED, STATUS, SEVERITY, DESCRIPTION, IDENTIFIER, TOOL, ACTIVITY } = FIELDS; const { DETECTED, STATUS, SEVERITY, DESCRIPTION, IDENTIFIER, TOOL, ACTIVITY } = FIELDS;
...@@ -73,6 +74,8 @@ describe('Vulnerability list component', () => { ...@@ -73,6 +74,8 @@ describe('Vulnerability list component', () => {
const findDashboardHasNoVulnerabilities = () => const findDashboardHasNoVulnerabilities = () =>
wrapper.findComponent(DashboardHasNoVulnerabilities); wrapper.findComponent(DashboardHasNoVulnerabilities);
const findVendorNames = () => wrapper.findByTestId('vulnerability-vendor'); const findVendorNames = () => wrapper.findByTestId('vulnerability-vendor');
const findCheckAllCheckbox = () => wrapper.findByTestId('vulnerability-checkbox-all');
const findAllRowCheckboxes = () => wrapper.findAllByTestId('vulnerability-checkbox');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -612,6 +615,54 @@ describe('Vulnerability list component', () => { ...@@ -612,6 +615,54 @@ describe('Vulnerability list component', () => {
}); });
}); });
describe('select all checkbox', () => {
it('will toggle between selecting all and deselecting all vulnerabilities', async () => {
const getChecked = () => findAllRowCheckboxes().filter((x) => x.element.checked === true);
createWrapper({ props: { vulnerabilities } });
// Sanity check to ensure that everything starts off unchecked.
expect(getChecked()).toHaveLength(0);
await findCheckAllCheckbox().trigger('click');
// First click should select all rows.
expect(getChecked()).toHaveLength(vulnerabilities.length);
await findCheckAllCheckbox().trigger('click');
// Second click should un-select all rows.
expect(getChecked()).toHaveLength(0);
});
it('will toggle the indeterminate state when some but not all vulnerabilities are selected', async () => {
const expectIndeterminateState = (state) =>
expect(findCheckAllCheckbox().props('indeterminate')).toBe(state);
createWrapper({
props: { vulnerabilities },
stubs: { GlFormCheckbox: stubComponent(GlFormCheckbox, { props: ['indeterminate'] }) },
});
// We start off with no items selected, so no indeterminate state.
expectIndeterminateState(false);
await findRow(1).trigger('click');
// When we go from 0 to 1 item selected, indeterminate state should be true.
expectIndeterminateState(true);
await findRow(1).trigger('click');
// When we go from 1 to 0 items selected, indeterminate state should be false.
expectIndeterminateState(false);
// Check all items.
findCheckAllCheckbox().trigger('click');
// When all the items are selected, indeterminate state should be false.
expectIndeterminateState(false);
await findRow(1).trigger('click');
// When we uncheck an item when all items are selected, indeterminate state should be true.
expectIndeterminateState(true);
});
});
describe('fields prop', () => { describe('fields prop', () => {
it('shows the expected columns in the table', () => { it('shows the expected columns in the table', () => {
const fields = [STATUS, SEVERITY]; const fields = [STATUS, SEVERITY];
......
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