Commit c54a30c1 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'devops-adoption-remove-unused-code' into 'master'

Remove add button from Devops Adoption

See merge request gitlab-org/gitlab!64400
parents 224284bb 7c30991b
<script>
import { GlEmptyState, GlButton, GlModalDirective } from '@gitlab/ui';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from '../constants';
import { GlEmptyState, GlModalDirective } from '@gitlab/ui';
import { DEVOPS_ADOPTION_STRINGS } from '../constants';
export default {
name: 'DevopsAdoptionEmptyState',
components: {
GlEmptyState,
GlButton,
},
directives: {
GlModal: GlModalDirective,
},
inject: ['emptyStateSvgPath'],
i18n: DEVOPS_ADOPTION_STRINGS.emptyState,
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
props: {
hasGroupsData: {
type: Boolean,
......@@ -27,15 +25,5 @@ export default {
:title="$options.i18n.title"
:description="$options.i18n.description"
:svg-path="emptyStateSvgPath"
>
<template #actions>
<gl-button
v-gl-modal="$options.devopsSegmentModalId"
:disabled="!hasGroupsData"
variant="info"
@click="$emit('clear-selected-segment')"
>{{ $options.i18n.button }}</gl-button
>
</template>
</gl-empty-state>
/>
</template>
<script>
import {
GlFormGroup,
GlFormInput,
GlFormCheckboxTree,
GlModal,
GlAlert,
GlIcon,
GlLoadingIcon,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import _ from 'lodash';
import { convertToGraphQLId, getIdFromGraphQLId, TYPE_GROUP } from '~/graphql_shared/utils';
import {
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
DEVOPS_ADOPTION_GROUP_LEVEL_LABEL,
} from '../constants';
import bulkEnableDevopsAdoptionNamespacesMutation from '../graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from '../graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
export default {
name: 'DevopsAdoptionSegmentModal',
components: {
GlModal,
GlFormGroup,
GlFormInput,
GlFormCheckboxTree,
GlAlert,
GlIcon,
GlLoadingIcon,
},
inject: {
isGroup: {
default: false,
},
groupGid: {
default: null,
},
},
props: {
groups: {
type: Array,
required: true,
},
enabledGroups: {
type: Array,
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
i18n: DEVOPS_ADOPTION_STRINGS.modal,
data() {
const checkboxValuesFromEnabledGroups = this.enabledGroups.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
return {
checkboxValuesFromEnabledGroups,
checkboxValues: checkboxValuesFromEnabledGroups,
filter: '',
loadingAdd: false,
loadingDelete: false,
errors: [],
};
},
computed: {
loading() {
return this.loadingAdd || this.loadingDelete;
},
checkboxOptions() {
return this.groups.map(({ id, full_name }) => ({ label: full_name, value: id }));
},
cancelOptions() {
return {
button: {
text: this.$options.i18n.cancel,
attributes: [{ disabled: this.loading }],
},
};
},
primaryOptions() {
return {
button: {
text: this.$options.i18n.addingButton,
attributes: [
{
variant: 'info',
loading: this.loading,
disabled: !this.canSubmit,
},
],
},
callback: this.saveChanges,
};
},
canSubmit() {
return !this.anyChangesMade;
},
displayError() {
return this.errors[0];
},
modalTitle() {
return this.isGroup ? DEVOPS_ADOPTION_GROUP_LEVEL_LABEL : this.$options.i18n.addingTitle;
},
filteredOptions() {
return this.filter
? this.checkboxOptions.filter((option) =>
option.label.toLowerCase().includes(this.filter.toLowerCase()),
)
: this.checkboxOptions;
},
anyChangesMade() {
return _.isEqual(
_.sortBy(this.checkboxValues),
_.sortBy(this.checkboxValuesFromEnabledGroups),
);
},
},
watch: {
enabledGroups(newValues) {
if (!this.loading) {
this.checkboxValuesFromEnabledGroups = newValues.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
this.checkboxValues = this.checkboxValuesFromEnabledGroups;
}
},
},
methods: {
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: {
bulkEnableDevopsAdoptionNamespaces: { errors },
},
} = await this.$apollo.mutate({
mutation: bulkEnableDevopsAdoptionNamespacesMutation,
variables: {
namespaceIds,
displayNamespaceId: this.groupGid,
},
update: (store, { data }) => {
const {
bulkEnableDevopsAdoptionNamespaces: { enabledNamespaces, errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsAdded', enabledNamespaces);
},
});
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 {
const removedGroupGids = this.enabledGroups
.filter(
(group) =>
!this.checkboxValues.includes(getIdFromGraphQLId(group.namespace.id)) &&
group.namespace.id !== this.groupGid,
)
.map((group) => group.id);
if (removedGroupGids.length) {
this.loadingDelete = true;
const {
data: {
disableDevopsAdoptionNamespace: { errors },
},
} = await this.$apollo.mutate({
mutation: disableDevopsAdoptionNamespaceMutation,
variables: {
id: removedGroupGids,
},
update: (store, { data }) => {
const {
disableDevopsAdoptionNamespace: { errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsRemoved', removedGroupGids);
},
});
if (errors.length) {
this.errors = errors;
}
}
} catch (error) {
this.errors.push(this.$options.i18n.error);
Sentry.captureException(error);
} finally {
this.loadingDelete = false;
}
},
clearErrors() {
this.errors = [];
},
openModal() {
this.$refs.modal.show();
},
closeModal() {
this.$refs.modal.hide();
},
resetForm() {
this.filter = '';
this.$emit('trackModalOpenState', false);
},
},
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="$options.devopsSegmentModalId"
:title="modalTitle"
size="sm"
scrollable
:action-primary="primaryOptions.button"
:action-cancel="cancelOptions.button"
@primary.prevent="primaryOptions.callback"
@hidden="resetForm"
@show="$emit('trackModalOpenState', true)"
>
<gl-loading-icon v-if="isLoading" size="md" class="gl-mt-4" />
<div v-else>
<gl-alert v-if="errors.length" variant="danger" class="gl-mb-3" @dismiss="clearErrors">
{{ displayError }}
</gl-alert>
<gl-form-group class="gl-mb-3" data-testid="filter">
<gl-icon
name="search"
:size="18"
use-deprecated-sizes
class="gl-text-gray-300 gl-absolute gl-mt-3 gl-ml-3"
/>
<gl-form-input
v-model="filter"
class="gl-pl-7!"
type="text"
:placeholder="$options.i18n.filterPlaceholder"
:disabled="loading"
/>
</gl-form-group>
<gl-form-group class="gl-mb-0">
<gl-form-checkbox-tree
v-if="filteredOptions.length"
v-model="checkboxValues"
data-testid="groups"
:options="filteredOptions"
:hide-toggle-all="true"
:disabled="loading"
class="gl-p-3 gl-pb-0 gl-mb-2 gl-border-1 gl-border-solid gl-border-gray-100 gl-rounded-base"
/>
<gl-alert v-else variant="info" :dismissible="false" data-testid="filter-warning">
{{ $options.i18n.noResults }}
</gl-alert>
</gl-form-group>
</div>
</gl-modal>
</template>
......@@ -11,7 +11,6 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
DEVOPS_ADOPTION_TABLE_TEST_IDS,
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY,
......@@ -71,7 +70,6 @@ export default {
...i18n,
removeButtonDisabled: DEVOPS_ADOPTION_TABLE_REMOVE_BUTTON_DISABLED,
},
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
devopsSegmentDeleteModalId: DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
testids: DEVOPS_ADOPTION_TABLE_TEST_IDS,
sortByStorageKey: DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
......
......@@ -6,8 +6,6 @@ export const PER_PAGE = 20;
export const DEBOUNCE_DELAY = 500;
export const DEVOPS_ADOPTION_SEGMENT_MODAL_ID = 'devopsSegmentModal';
export const DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID = 'devopsSegmentDeleteModal';
export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM';
......
......@@ -6,7 +6,6 @@ import VueApollo from 'vue-apollo';
import DevopsAdoptionAddDropdown from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_add_dropdown.vue';
import DevopsAdoptionApp from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_app.vue';
import DevopsAdoptionSection from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_section.vue';
import DevopsAdoptionSegmentModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_segment_modal.vue';
import {
DEVOPS_ADOPTION_STRINGS,
DEFAULT_POLLING_INTERVAL,
......@@ -238,10 +237,6 @@ describe('DevopsAdoptionApp', () => {
await wrapper.vm.$nextTick();
});
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
});
......@@ -273,10 +268,6 @@ describe('DevopsAdoptionApp', () => {
await waitForPromises();
});
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
});
......
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DevopsAdoptionEmptyState from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_empty_state.vue';
import {
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
} from 'ee/analytics/devops_report/devops_adoption/constants';
import { DEVOPS_ADOPTION_STRINGS } from 'ee/analytics/devops_report/devops_adoption/constants';
const emptyStateSvgPath = 'illustrations/monitoring/getting_started.svg';
......@@ -12,9 +9,9 @@ describe('DevopsAdoptionEmptyState', () => {
let wrapper;
const createComponent = (options = {}) => {
const { stubs = {}, props = {}, func = shallowMount } = options;
const { stubs = {}, props = {} } = options;
return func(DevopsAdoptionEmptyState, {
return shallowMount(DevopsAdoptionEmptyState, {
provide: {
emptyStateSvgPath,
},
......@@ -27,7 +24,6 @@ describe('DevopsAdoptionEmptyState', () => {
};
const findEmptyState = () => wrapper.find(GlEmptyState);
const findEmptyStateAction = () => findEmptyState().find(GlButton);
afterEach(() => {
wrapper.destroy();
......@@ -48,43 +44,4 @@ describe('DevopsAdoptionEmptyState', () => {
expect(emptyState.props('title')).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.title);
expect(emptyState.props('description')).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.description);
});
describe('action button', () => {
it('displays an overridden action button', () => {
wrapper = createComponent({ stubs: { GlEmptyState } });
const actionButton = findEmptyStateAction();
expect(actionButton.exists()).toBe(true);
expect(actionButton.text()).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.button);
});
it('is enabled when there is group data', () => {
wrapper = createComponent({ stubs: { GlEmptyState } });
const actionButton = findEmptyStateAction();
expect(actionButton.props('disabled')).toBe(false);
});
it('is disabled when there is no group data', () => {
wrapper = createComponent({ stubs: { GlEmptyState }, props: { hasGroupsData: false } });
const actionButton = findEmptyStateAction();
expect(actionButton.props('disabled')).toBe(true);
});
it('calls the gl-modal show', async () => {
wrapper = createComponent({ func: mount });
const actionButton = findEmptyStateAction();
const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
actionButton.trigger('click');
expect(rootEmit.mock.calls[0][0]).toContain('show');
expect(rootEmit.mock.calls[0][1]).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID);
});
});
});
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