Commit 57aa7792 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '299977-devops-adoption-fe-allow-users-to-select-multiple-groups' into 'master'

DevOps Adoption[FE] Allow users to select multiple groups

See merge request gitlab-org/gitlab!56073
parents 5deced6c 1a1f854c
...@@ -178,7 +178,7 @@ export default { ...@@ -178,7 +178,7 @@ export default {
v-if="hasGroupData" v-if="hasGroupData"
:key="modalKey" :key="modalKey"
:groups="groups.nodes" :groups="groups.nodes"
:segment="selectedSegment" :enabled-groups="devopsAdoptionSegments.nodes"
@trackModalOpenState="trackModalOpenState" @trackModalOpenState="trackModalOpenState"
/> />
<div v-if="hasSegmentsData" class="gl-mt-3"> <div v-if="hasSegmentsData" class="gl-mt-3">
......
...@@ -3,7 +3,7 @@ import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui'; ...@@ -3,7 +3,7 @@ import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from '../constants'; import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID } from '../constants';
import deleteDevopsAdoptionSegmentMutation from '../graphql/mutations/delete_devops_adoption_segment.mutation.graphql'; import deleteDevopsAdoptionSegmentMutation from '../graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import { deleteSegmentFromCache } from '../utils/cache_updates'; import { deleteSegmentsFromCache } from '../utils/cache_updates';
export default { export default {
name: 'DevopsAdoptionDeleteModal', name: 'DevopsAdoptionDeleteModal',
...@@ -66,7 +66,7 @@ export default { ...@@ -66,7 +66,7 @@ export default {
id: [id], id: [id],
}, },
update(store) { update(store) {
deleteSegmentFromCache(store, id); deleteSegmentsFromCache(store, [id]);
}, },
}); });
......
<script> <script>
import { GlFormGroup, GlFormInput, GlFormRadioGroup, GlModal, GlAlert, GlIcon } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlFormCheckboxTree, GlModal, GlAlert, GlIcon } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { convertToGraphQLId, TYPE_GROUP } from '~/graphql_shared/utils'; import _ from 'lodash';
import { convertToGraphQLId, getIdFromGraphQLId, TYPE_GROUP } from '~/graphql_shared/utils';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from '../constants'; import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from '../constants';
import createDevopsAdoptionSegmentMutation from '../graphql/mutations/create_devops_adoption_segment.mutation.graphql'; import bulkFindOrCreateDevopsAdoptionSegmentsMutation from '../graphql/mutations/bulk_find_or_create_devops_adoption_segments.mutation.graphql';
import { addSegmentToCache } from '../utils/cache_updates'; import deleteDevopsAdoptionSegmentMutation from '../graphql/mutations/delete_devops_adoption_segment.mutation.graphql';
import { addSegmentsToCache, deleteSegmentsFromCache } from '../utils/cache_updates';
export default { export default {
name: 'DevopsAdoptionSegmentModal', name: 'DevopsAdoptionSegmentModal',
...@@ -12,7 +14,7 @@ export default { ...@@ -12,7 +14,7 @@ export default {
GlModal, GlModal,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlFormRadioGroup, GlFormCheckboxTree,
GlAlert, GlAlert,
GlIcon, GlIcon,
}, },
...@@ -21,19 +23,33 @@ export default { ...@@ -21,19 +23,33 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
enabledGroups: {
type: Array,
required: false,
default: () => [],
},
}, },
i18n: DEVOPS_ADOPTION_STRINGS.modal, i18n: DEVOPS_ADOPTION_STRINGS.modal,
data() { data() {
const checkboxValuesFromEnabledGroups = this.enabledGroups.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
return { return {
selectedGroupId: null, checkboxValuesFromEnabledGroups,
checkboxValues: checkboxValuesFromEnabledGroups,
filter: '', filter: '',
loading: false, loadingAdd: false,
loadingDelete: false,
errors: [], errors: [],
}; };
}, },
computed: { computed: {
loading() {
return this.loadingAdd || this.loadingDelete;
},
checkboxOptions() { checkboxOptions() {
return this.groups.map(({ id, full_name }) => ({ text: full_name, value: id })); return this.groups.map(({ id, full_name }) => ({ label: full_name, value: id }));
}, },
cancelOptions() { cancelOptions() {
return { return {
...@@ -41,7 +57,6 @@ export default { ...@@ -41,7 +57,6 @@ export default {
text: this.$options.i18n.cancel, text: this.$options.i18n.cancel,
attributes: [{ disabled: this.loading }], attributes: [{ disabled: this.loading }],
}, },
callback: this.resetForm,
}; };
}, },
primaryOptions() { primaryOptions() {
...@@ -56,11 +71,11 @@ export default { ...@@ -56,11 +71,11 @@ export default {
}, },
], ],
}, },
callback: this.createSegment, callback: this.saveChanges,
}; };
}, },
canSubmit() { canSubmit() {
return Boolean(this.selectedGroupId); return !this.anyChangesMade;
}, },
displayError() { displayError() {
return this.errors[0]; return this.errors[0];
...@@ -71,44 +86,101 @@ export default { ...@@ -71,44 +86,101 @@ export default {
filteredOptions() { filteredOptions() {
return this.filter return this.filter
? this.checkboxOptions.filter((option) => ? this.checkboxOptions.filter((option) =>
option.text.toLowerCase().includes(this.filter.toLowerCase()), option.label.toLowerCase().includes(this.filter.toLowerCase()),
) )
: this.checkboxOptions; : this.checkboxOptions;
}, },
anyChangesMade() {
return _.isEqual(
_.sortBy(this.checkboxValues),
_.sortBy(this.checkboxValuesFromEnabledGroups),
);
},
}, },
methods: { methods: {
async createSegment() { async saveChanges() {
await this.deleteMissingGroups();
await this.addNewGroups();
if (!this.errors.length) this.closeModal();
},
async addNewGroups() {
try {
const originalEnabledIds = this.enabledGroups.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
const namespaceIds = this.checkboxValues
.filter((id) => !originalEnabledIds.includes(id))
.map((id) => convertToGraphQLId(TYPE_GROUP, id));
if (namespaceIds.length) {
this.loadingAdd = true;
const {
data: {
bulkFindOrCreateDevopsAdoptionSegments: { errors },
},
} = await this.$apollo.mutate({
mutation: bulkFindOrCreateDevopsAdoptionSegmentsMutation,
variables: {
namespaceIds,
},
update: (store, { data }) => {
const {
bulkFindOrCreateDevopsAdoptionSegments: { segments, errors: requestErrors },
} = data;
if (!requestErrors.length) addSegmentsToCache(store, segments);
},
});
if (errors.length) {
this.errors = errors;
}
}
} catch (error) {
this.errors.push(this.$options.i18n.error);
Sentry.captureException(error);
} finally {
this.loadingAdd = false;
}
},
async deleteMissingGroups() {
try { try {
this.loading = true; const removedGroupGids = this.enabledGroups
.filter((group) => !this.checkboxValues.includes(getIdFromGraphQLId(group.namespace.id)))
.map((group) => group.id);
if (removedGroupGids.length) {
this.loadingDelete = true;
const { const {
data: { data: {
createDevopsAdoptionSegment: { errors }, deleteDevopsAdoptionSegment: { errors },
}, },
} = await this.$apollo.mutate({ } = await this.$apollo.mutate({
mutation: createDevopsAdoptionSegmentMutation, mutation: deleteDevopsAdoptionSegmentMutation,
variables: { variables: {
namespaceId: convertToGraphQLId(TYPE_GROUP, this.selectedGroupId), id: removedGroupGids,
}, },
update: (store, { data }) => { update: (store, { data }) => {
const { const {
createDevopsAdoptionSegment: { segment, errors: requestErrors }, deleteDevopsAdoptionSegment: { errors: requestErrors },
} = data; } = data;
if (!requestErrors.length) addSegmentToCache(store, segment); if (!requestErrors.length) deleteSegmentsFromCache(store, removedGroupGids);
}, },
}); });
if (errors.length) { if (errors.length) {
this.errors = errors; this.errors = errors;
} else { }
this.resetForm();
this.closeModal();
} }
} catch (error) { } catch (error) {
this.errors.push(this.$options.i18n.error); this.errors.push(this.$options.i18n.error);
Sentry.captureException(error); Sentry.captureException(error);
} finally { } finally {
this.loading = false; this.loadingDelete = false;
} }
}, },
clearErrors() { clearErrors() {
...@@ -118,7 +190,7 @@ export default { ...@@ -118,7 +190,7 @@ export default {
this.$refs.modal.hide(); this.$refs.modal.hide();
}, },
resetForm() { resetForm() {
this.selectedGroupId = null; this.checkboxValues = [];
this.filter = ''; this.filter = '';
this.$emit('trackModalOpenState', false); this.$emit('trackModalOpenState', false);
}, },
...@@ -136,8 +208,7 @@ export default { ...@@ -136,8 +208,7 @@ export default {
:action-primary="primaryOptions.button" :action-primary="primaryOptions.button"
:action-cancel="cancelOptions.button" :action-cancel="cancelOptions.button"
@primary.prevent="primaryOptions.callback" @primary.prevent="primaryOptions.callback"
@canceled="cancelOptions.callback" @hidden="resetForm"
@hide="resetForm"
@show="$emit('trackModalOpenState', true)" @show="$emit('trackModalOpenState', true)"
> >
<gl-alert v-if="errors.length" variant="danger" class="gl-mb-3" @dismiss="clearErrors"> <gl-alert v-if="errors.length" variant="danger" class="gl-mb-3" @dismiss="clearErrors">
...@@ -159,10 +230,10 @@ export default { ...@@ -159,10 +230,10 @@ export default {
/> />
</gl-form-group> </gl-form-group>
<gl-form-group class="gl-mb-0"> <gl-form-group class="gl-mb-0">
<gl-form-radio-group <gl-form-checkbox-tree
v-if="filteredOptions.length" v-if="filteredOptions.length"
:key="filteredOptions.length" :key="filteredOptions.length"
v-model="selectedGroupId" v-model="checkboxValues"
data-testid="groups" data-testid="groups"
:options="filteredOptions" :options="filteredOptions"
:hide-toggle-all="true" :hide-toggle-all="true"
......
...@@ -29,7 +29,7 @@ export const DEVOPS_ADOPTION_STRINGS = { ...@@ -29,7 +29,7 @@ export const DEVOPS_ADOPTION_STRINGS = {
text: s__( text: s__(
'DevopsAdoption|Feature adoption is based on usage in the last calendar month. Last updated: %{timestamp}.', 'DevopsAdoption|Feature adoption is based on usage in the last calendar month. Last updated: %{timestamp}.',
), ),
button: s__('DevopsAdoption|Add Group'), button: s__('DevopsAdoption|Add / remove groups'),
buttonTooltip: sprintf(s__('DevopsAdoption|Maximum %{maxSegments} groups allowed'), { buttonTooltip: sprintf(s__('DevopsAdoption|Maximum %{maxSegments} groups allowed'), {
maxSegments: MAX_SEGMENTS, maxSegments: MAX_SEGMENTS,
}), }),
...@@ -43,15 +43,12 @@ export const DEVOPS_ADOPTION_STRINGS = { ...@@ -43,15 +43,12 @@ export const DEVOPS_ADOPTION_STRINGS = {
button: s__('DevopsAdoption|Add Group'), button: s__('DevopsAdoption|Add Group'),
}, },
modal: { modal: {
addingTitle: s__('DevopsAdoption|Add Group'), addingTitle: s__('DevopsAdoption|Add / remove groups'),
addingButton: s__('DevopsAdoption|Add Group'), addingButton: s__('DevopsAdoption|Save changes'),
editingButton: s__('DevopsAdoption|Save changes'),
cancel: __('Cancel'), cancel: __('Cancel'),
namePlaceholder: s__('DevopsAdoption|My group'), namePlaceholder: s__('DevopsAdoption|My group'),
filterPlaceholder: s__('DevopsAdoption|Filter by name'), filterPlaceholder: s__('DevopsAdoption|Filter by name'),
selectedGroupsTextSingular: s__('DevopsAdoption|%{selectedCount} group selected'), error: s__('DevopsAdoption|An error occurred while saving changes. Please try again.'),
selectedGroupsTextPlural: s__('DevopsAdoption|%{selectedCount} groups selected'),
error: s__('DevopsAdoption|An error occurred while saving the group. Please try again.'),
noResults: s__('DevopsAdoption|No filter results.'), noResults: s__('DevopsAdoption|No filter results.'),
}, },
table: { table: {
......
mutation($namespaceId: NamespaceID!) { mutation($namespaceIds: [NamespaceID!]!) {
createDevopsAdoptionSegment(input: { namespaceId: $namespaceId }) { bulkFindOrCreateDevopsAdoptionSegments(input: { namespaceIds: $namespaceIds }) {
segment { segments {
id id
latestSnapshot { latestSnapshot {
issueOpened issueOpened
......
import produce from 'immer'; import produce from 'immer';
import devopsAdoptionSegmentsQuery from '../graphql/queries/devops_adoption_segments.query.graphql'; import devopsAdoptionSegmentsQuery from '../graphql/queries/devops_adoption_segments.query.graphql';
export const addSegmentToCache = (store, segment) => { export const addSegmentsToCache = (store, segments) => {
const sourceData = store.readQuery({ const sourceData = store.readQuery({
query: devopsAdoptionSegmentsQuery, query: devopsAdoptionSegmentsQuery,
}); });
const data = produce(sourceData, (draftData) => { const data = produce(sourceData, (draftData) => {
draftData.devopsAdoptionSegments.nodes = [...draftData.devopsAdoptionSegments.nodes, segment]; draftData.devopsAdoptionSegments.nodes = [
...draftData.devopsAdoptionSegments.nodes,
...segments,
];
}); });
store.writeQuery({ store.writeQuery({
...@@ -16,14 +19,14 @@ export const addSegmentToCache = (store, segment) => { ...@@ -16,14 +19,14 @@ export const addSegmentToCache = (store, segment) => {
}); });
}; };
export const deleteSegmentFromCache = (store, segmentId) => { export const deleteSegmentsFromCache = (store, segmentIds) => {
const sourceData = store.readQuery({ const sourceData = store.readQuery({
query: devopsAdoptionSegmentsQuery, query: devopsAdoptionSegmentsQuery,
}); });
const updatedData = produce(sourceData, (draftData) => { const updatedData = produce(sourceData, (draftData) => {
draftData.devopsAdoptionSegments.nodes = draftData.devopsAdoptionSegments.nodes.filter( draftData.devopsAdoptionSegments.nodes = draftData.devopsAdoptionSegments.nodes.filter(
({ id }) => id !== segmentId, ({ id }) => !segmentIds.includes(id),
); );
}); });
......
---
title: DevOps Adoption - Allow users to select multiple groups
merge_request: 56073
author:
type: changed
...@@ -13,31 +13,37 @@ import { ...@@ -13,31 +13,37 @@ import {
genericErrorMessage, genericErrorMessage,
dataErrorMessage, dataErrorMessage,
groupNodeLabelValues, groupNodeLabelValues,
devopsAdoptionSegmentsData,
} from '../mock_data'; } from '../mock_data';
const mockEvent = { preventDefault: jest.fn() }; const mockEvent = { preventDefault: jest.fn() };
const mutate = jest.fn().mockResolvedValue({ const mutate = jest.fn().mockResolvedValue({
data: { data: {
createDevopsAdoptionSegment: { bulkFindOrCreateDevopsAdoptionSegments: {
errors: [],
},
deleteDevopsAdoptionSegment: {
errors: [], errors: [],
}, },
}, },
}); });
const mutateWithDataErrors = () => const mutateWithDataErrors = jest.fn().mockResolvedValue({
jest.fn().mockResolvedValue({
data: { data: {
createDevopsAdoptionSegment: { bulkFindOrCreateDevopsAdoptionSegments: {
errors: [dataErrorMessage], errors: [dataErrorMessage],
}, },
deleteDevopsAdoptionSegment: {
errors: [],
},
}, },
}); });
const mutateLoading = jest.fn().mockResolvedValue(new Promise(() => {})); const mutateLoading = jest.fn().mockResolvedValue(new Promise(() => {}));
const mutateWithErrors = jest.fn().mockRejectedValue(genericErrorMessage); const mutateWithErrors = jest.fn().mockRejectedValue(genericErrorMessage);
describe('DevopsAdoptionSegmentModal', () => { describe('DevopsAdoptionSegmentModal', () => {
let wrapper; let wrapper;
const createComponent = ({ mutationMock = mutate } = {}) => { const createComponent = ({ mutationMock = mutate, props = {} } = {}) => {
const $apollo = { const $apollo = {
mutate: mutationMock, mutate: mutationMock,
}; };
...@@ -45,6 +51,7 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -45,6 +51,7 @@ describe('DevopsAdoptionSegmentModal', () => {
wrapper = shallowMount(DevopsAdoptionSegmentModal, { wrapper = shallowMount(DevopsAdoptionSegmentModal, {
propsData: { propsData: {
groups: groupNodes, groups: groupNodes,
...props,
}, },
stubs: { stubs: {
GlSprintf, GlSprintf,
...@@ -65,7 +72,6 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -65,7 +72,6 @@ describe('DevopsAdoptionSegmentModal', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('contains the corrrect id', () => { it('contains the corrrect id', () => {
...@@ -77,15 +83,32 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -77,15 +83,32 @@ describe('DevopsAdoptionSegmentModal', () => {
expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID); expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID);
}); });
it.each`
enabledGroups | checkboxValues | disabled | condition | state
${[]} | ${[]} | ${true} | ${'no changes'} | ${'disables'}
${[]} | ${[1]} | ${false} | ${'changes'} | ${'enables'}
`(
'$state the primary action if there are $condition',
async ({ enabledGroups, disabled, checkboxValues }) => {
createComponent({ props: { enabledGroups } });
wrapper.setData({ checkboxValues });
await nextTick();
expect(actionButtonDisabledState()).toBe(disabled);
},
);
describe('displays the correct content', () => { describe('displays the correct content', () => {
beforeEach(() => createComponent()); beforeEach(() => createComponent());
const isCorrectShape = (option) => { const isCorrectShape = (option) => {
const keys = Object.keys(option); const keys = Object.keys(option);
return keys.includes('text') && keys.includes('value'); return keys.includes('label') && keys.includes('value');
}; };
it('contains the radio group component', () => { it('contains the checkbox tree component', () => {
const checkboxes = findByTestId('groups'); const checkboxes = findByTestId('groups');
expect(checkboxes.exists()).toBe(true); expect(checkboxes.exists()).toBe(true);
...@@ -162,7 +185,7 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -162,7 +185,7 @@ describe('DevopsAdoptionSegmentModal', () => {
describe.each` describe.each`
state | action | expected state | action | expected
${'opening'} | ${'show'} | ${true} ${'opening'} | ${'show'} | ${true}
${'closing'} | ${'hide'} | ${false} ${'closing'} | ${'hidden'} | ${false}
`('$state the modal', ({ action, expected }) => { `('$state the modal', ({ action, expected }) => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -174,29 +197,21 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -174,29 +197,21 @@ describe('DevopsAdoptionSegmentModal', () => {
}); });
}); });
it.each` describe('handles the form submission correctly when saving changes', () => {
selectedGroupId | disabled | values | state const enableFirstGroup = { checkboxValues: [groupIds[0]] };
${null} | ${true} | ${'checkbox'} | ${'disables'} const enableSecondGroup = { checkboxValues: [groupIds[1]] };
${1} | ${false} | ${'nothing'} | ${'enables'} const noEnabledGroups = { checkboxValues: [] };
`('$state the primary action if $values is missing', async ({ selectedGroupId, disabled }) => { const firstGroupEnabledData = [devopsAdoptionSegmentsData.nodes[0]];
createComponent(); const firstGroupId = [groupIds[0]];
const firstGroupGid = [groupGids[0]];
wrapper.setData({ selectedGroupId }); const secondGroupGid = [groupGids[1]];
await nextTick();
expect(actionButtonDisabledState()).toBe(disabled);
});
describe('handles the form submission correctly when creating a new segment', () => {
const additionalData = { selectedGroupId: groupIds[0] };
describe('submitting the form', () => { describe('submitting the form', () => {
describe('while waiting for the mutation', () => { describe('while waiting for the mutation', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ mutationMock: mutateLoading }); createComponent({ mutationMock: mutateLoading });
wrapper.setData(additionalData); wrapper.setData(enableFirstGroup);
}); });
it('disables the form inputs', async () => { it('disables the form inputs', async () => {
...@@ -232,46 +247,72 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -232,46 +247,72 @@ describe('DevopsAdoptionSegmentModal', () => {
}); });
}); });
describe.each`
action | enabledGroups | newGroups | expectedAddGroupGids | expectedDeleteIds
${'adding'} | ${[]} | ${enableFirstGroup} | ${firstGroupGid} | ${[]}
${'removing'} | ${firstGroupEnabledData} | ${noEnabledGroups} | ${[]} | ${firstGroupId}
${'adding and removing'} | ${firstGroupEnabledData} | ${enableSecondGroup} | ${secondGroupGid} | ${firstGroupId}
`(
'$action groups',
({ enabledGroups, newGroups, expectedAddGroupGids, expectedDeleteIds }) => {
describe('successful submission', () => { describe('successful submission', () => {
beforeEach(() => { beforeEach(async () => {
createComponent(); createComponent({ props: { enabledGroups } });
wrapper.setData(additionalData); wrapper.setData(newGroups);
wrapper.vm.$refs.modal.hide = jest.fn(); wrapper.vm.$refs.modal.hide = jest.fn();
findModal().vm.$emit('primary', mockEvent); findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
});
if (expectedAddGroupGids.length) {
it('submits the correct add request variables', () => {
expect(mutate).toHaveBeenCalledWith(
expect.objectContaining({
variables: { namespaceIds: expectedAddGroupGids },
}),
);
}); });
}
it('submits the correct request variables', async () => { if (expectedDeleteIds.length) {
it('submits the correct delete request variables', () => {
expect(mutate).toHaveBeenCalledWith( expect(mutate).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { namespaceId: groupGids[0] }, variables: { id: expectedDeleteIds },
}), }),
); );
}); });
}
it('closes the modal after a successful mutation', async () => { it('closes the modal after a successful mutation', () => {
expect(wrapper.vm.$refs.modal.hide).toHaveBeenCalled(); expect(wrapper.vm.$refs.modal.hide).toHaveBeenCalled();
}); });
it('resets the form fields', async () => { it('resets the form fields', () => {
expect(wrapper.vm.selectedGroupId).toEqual(null); findModal().vm.$emit('hidden');
expect(wrapper.vm.checkboxValues).toEqual([]);
expect(wrapper.vm.filter).toBe(''); expect(wrapper.vm.filter).toBe('');
}); });
}); });
},
);
describe('error handling', () => { describe('error handling', () => {
it.each` it.each`
errorType | errorLocation | mutationSpy | message errorType | errorLocation | mutationSpy | message
${'generic'} | ${'top level'} | ${mutateWithErrors} | ${genericErrorMessage} ${'generic'} | ${'top level'} | ${mutateWithErrors} | ${genericErrorMessage}
${'specific'} | ${'data'} | ${mutateWithDataErrors()} | ${dataErrorMessage} ${'specific'} | ${'data'} | ${mutateWithDataErrors} | ${dataErrorMessage}
`( `(
'displays a $errorType error if the mutation has a $errorLocation error', 'displays a $errorType error if the mutation has a $errorLocation error',
async ({ mutationSpy, message }) => { async ({ mutationSpy, message }) => {
createComponent({ mutationMock: mutationSpy }); createComponent({ mutationMock: mutationSpy });
wrapper.setData(additionalData); wrapper.setData(enableFirstGroup);
findModal().vm.$emit('primary', mockEvent); findModal().vm.$emit('primary', mockEvent);
...@@ -290,7 +331,7 @@ describe('DevopsAdoptionSegmentModal', () => { ...@@ -290,7 +331,7 @@ describe('DevopsAdoptionSegmentModal', () => {
createComponent({ mutationMock: mutateWithErrors }); createComponent({ mutationMock: mutateWithErrors });
wrapper.setData(additionalData); wrapper.setData(enableFirstGroup);
findModal().vm.$emit('primary', mockEvent); findModal().vm.$emit('primary', mockEvent);
......
...@@ -21,11 +21,11 @@ export const groupNodes = [ ...@@ -21,11 +21,11 @@ export const groupNodes = [
]; ];
export const groupNodeLabelValues = [ export const groupNodeLabelValues = [
{ text: 'Foo', value: '1' }, { label: 'Foo', value: '1' },
{ text: 'Bar', value: '2' }, { label: 'Bar', value: '2' },
]; ];
export const groupIds = ['1', '2']; export const groupIds = [1, 2];
export const groupGids = ['gid://gitlab/Group/1', 'gid://gitlab/Group/2']; export const groupGids = ['gid://gitlab/Group/1', 'gid://gitlab/Group/2'];
...@@ -128,7 +128,7 @@ export const devopsAdoptionTableHeaders = [ ...@@ -128,7 +128,7 @@ export const devopsAdoptionTableHeaders = [
export const segmentName = 'Foooo'; export const segmentName = 'Foooo';
export const genericErrorMessage = 'An error occurred while saving the group. Please try again.'; export const genericErrorMessage = 'An error occurred while saving changes. Please try again.';
export const dataErrorMessage = 'Name already taken.'; export const dataErrorMessage = 'Name already taken.';
......
import { import {
deleteSegmentFromCache, deleteSegmentsFromCache,
addSegmentToCache, addSegmentsToCache,
} from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates'; } from 'ee/analytics/devops_report/devops_adoption/utils/cache_updates';
import { devopsAdoptionSegmentsData } from '../mock_data'; import { devopsAdoptionSegmentsData } from '../mock_data';
describe('addSegmentToCache', () => { describe('addSegmentsToCache', () => {
const store = { const store = {
readQuery: jest.fn(() => ({ devopsAdoptionSegments: { nodes: [] } })), readQuery: jest.fn(() => ({ devopsAdoptionSegments: { nodes: [] } })),
writeQuery: jest.fn(), writeQuery: jest.fn(),
}; };
it('calls writeQuery with the correct response', () => { it('calls writeQuery with the correct response', () => {
addSegmentToCache(store, devopsAdoptionSegmentsData.nodes[0]); addSegmentsToCache(store, devopsAdoptionSegmentsData.nodes);
expect(store.writeQuery).toHaveBeenCalledWith( expect(store.writeQuery).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
data: { data: {
devopsAdoptionSegments: { devopsAdoptionSegments: {
nodes: [devopsAdoptionSegmentsData.nodes[0]], nodes: devopsAdoptionSegmentsData.nodes,
}, },
}, },
}), }),
...@@ -25,7 +25,7 @@ describe('addSegmentToCache', () => { ...@@ -25,7 +25,7 @@ describe('addSegmentToCache', () => {
}); });
}); });
describe('deleteSegmentFromCache', () => { describe('deleteSegmentsFromCache', () => {
const store = { const store = {
readQuery: jest.fn(() => ({ devopsAdoptionSegments: devopsAdoptionSegmentsData })), readQuery: jest.fn(() => ({ devopsAdoptionSegments: devopsAdoptionSegmentsData })),
writeQuery: jest.fn(), writeQuery: jest.fn(),
...@@ -33,7 +33,7 @@ describe('deleteSegmentFromCache', () => { ...@@ -33,7 +33,7 @@ describe('deleteSegmentFromCache', () => {
it('calls writeQuery with the correct response', () => { it('calls writeQuery with the correct response', () => {
// Remove the item at the first index // Remove the item at the first index
deleteSegmentFromCache(store, devopsAdoptionSegmentsData.nodes[0].id); deleteSegmentsFromCache(store, [devopsAdoptionSegmentsData.nodes[0].id]);
expect(store.writeQuery).toHaveBeenCalledWith( expect(store.writeQuery).toHaveBeenCalledWith(
expect.not.objectContaining({ expect.not.objectContaining({
......
...@@ -10649,10 +10649,7 @@ msgstr "" ...@@ -10649,10 +10649,7 @@ msgstr ""
msgid "DevOps Report" msgid "DevOps Report"
msgstr "" msgstr ""
msgid "DevopsAdoption|%{selectedCount} group selected" msgid "DevopsAdoption|Add / remove groups"
msgstr ""
msgid "DevopsAdoption|%{selectedCount} groups selected"
msgstr "" msgstr ""
msgid "DevopsAdoption|Add Group" msgid "DevopsAdoption|Add Group"
...@@ -10667,7 +10664,7 @@ msgstr "" ...@@ -10667,7 +10664,7 @@ msgstr ""
msgid "DevopsAdoption|An error occurred while removing the group. Please try again." msgid "DevopsAdoption|An error occurred while removing the group. Please try again."
msgstr "" msgstr ""
msgid "DevopsAdoption|An error occurred while saving the group. Please try again." msgid "DevopsAdoption|An error occurred while saving changes. Please try again."
msgstr "" msgstr ""
msgid "DevopsAdoption|Approvals" msgid "DevopsAdoption|Approvals"
......
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