Commit 064de929 authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Martin Wortschack

DevOps Adoption separate adoption and groups loading state

parent 712f99a7
......@@ -87,6 +87,9 @@ export default {
apollo: {
devopsAdoptionEnabledNamespaces: {
query: devopsAdoptionEnabledNamespacesQuery,
context: {
isSingleRequest: true,
},
variables() {
return this.segmentsQueryVariables;
},
......@@ -132,6 +135,11 @@ export default {
this.$apollo.queries.devopsAdoptionEnabledNamespaces.loading
);
},
isLoadingAdoptionData() {
return (
this.isLoadingEnableGroup || this.$apollo.queries.devopsAdoptionEnabledNamespaces.loading
);
},
segmentLimitReached() {
return this.devopsAdoptionEnabledNamespaces?.nodes?.length > this.$options.maxSegments;
},
......@@ -140,14 +148,17 @@ export default {
? this.$options.i18n.groupLevelLabel
: this.$options.i18n.tableHeader.button;
},
canRenderModal() {
return this.hasGroupData && !this.isLoading;
},
tabIndexValues() {
const tabs = this.$options.devopsAdoptionTableConfiguration.map((item) => item.tab);
return this.isGroup ? tabs : [...tabs, 'devops-score'];
},
availableGroups() {
return this.groups?.nodes || [];
},
enabledGroups() {
return this.devopsAdoptionEnabledNamespaces?.nodes || [];
},
},
created() {
this.fetchGroups();
......@@ -214,6 +225,9 @@ export default {
this.$apollo
.query({
query: getGroupsQuery,
context: {
isSingleRequest: true,
},
variables: {
nextPage,
},
......@@ -304,7 +318,7 @@ export default {
<devops-adoption-section
v-else
:is-loading="isLoading"
:is-loading="isLoadingAdoptionData"
:has-segments-data="hasSegmentsData"
:timestamp="timestamp"
:has-group-data="hasGroupData"
......@@ -324,10 +338,11 @@ export default {
</gl-tabs>
<devops-adoption-segment-modal
v-if="canRenderModal"
v-if="!hasLoadingError"
ref="addRemoveModal"
:groups="groups.nodes"
:enabled-groups="devopsAdoptionEnabledNamespaces.nodes"
:groups="availableGroups"
:enabled-groups="enabledGroups"
:is-loading="isLoading"
@segmentsAdded="addSegmentsToCache"
@segmentsRemoved="deleteSegmentsFromCache"
@trackModalOpenState="trackModalOpenState"
......
<script>
import { GlFormGroup, GlFormInput, GlFormCheckboxTree, GlModal, GlAlert, GlIcon } from '@gitlab/ui';
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';
......@@ -20,6 +28,7 @@ export default {
GlFormCheckboxTree,
GlAlert,
GlIcon,
GlLoadingIcon,
},
inject: {
isGroup: {
......@@ -39,6 +48,11 @@ export default {
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
i18n: DEVOPS_ADOPTION_STRINGS.modal,
data() {
......@@ -239,37 +253,40 @@ export default {
@hidden="resetForm"
@show="$emit('trackModalOpenState', true)"
>
<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-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>
<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>
......@@ -102,18 +102,6 @@ describe('DevopsAdoptionApp', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when loading', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider();
wrapper = createComponent({ mockApollo });
});
it('does not render the modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
});
describe('initial request', () => {
......@@ -123,19 +111,6 @@ describe('DevopsAdoptionApp', () => {
groupsSpy = null;
});
describe('when no group data is present', () => {
beforeEach(async () => {
groupsSpy = jest.fn().mockResolvedValueOnce({ __typename: 'Groups', nodes: [] });
const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo });
await waitForPromises();
});
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
});
describe('when group data is present', () => {
beforeEach(async () => {
groupsSpy = jest.fn().mockResolvedValueOnce({ ...initialResponse, pageInfo: null });
......@@ -164,10 +139,6 @@ describe('DevopsAdoptionApp', () => {
await waitForPromises();
});
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('should fetch data once', () => {
expect(groupsSpy).toHaveBeenCalledTimes(1);
});
......@@ -251,10 +222,6 @@ describe('DevopsAdoptionApp', () => {
await waitForPromises();
});
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('should fetch data twice', () => {
expect(groupsSpy).toHaveBeenCalledTimes(2);
});
......
import { GlModal, GlFormInput, GlSprintf, GlAlert, GlIcon } from '@gitlab/ui';
import { GlModal, GlFormInput, GlSprintf, GlAlert, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { createLocalVue } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionSegmentModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_segment_modal.vue';
......@@ -8,6 +8,7 @@ import { DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from 'ee/analytics/devops_report/dev
import bulkEnableDevopsAdoptionNamespacesMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
groupNodes,
......@@ -64,7 +65,7 @@ describe('DevopsAdoptionSegmentModal', () => {
[bulkEnableDevopsAdoptionNamespacesMutation, addSegmentsSpy],
]);
wrapper = shallowMount(DevopsAdoptionSegmentModal, {
wrapper = shallowMountExtended(DevopsAdoptionSegmentModal, {
localVue,
apolloProvider: mockApollo,
propsData: {
......@@ -81,7 +82,6 @@ describe('DevopsAdoptionSegmentModal', () => {
};
const findModal = () => wrapper.find(GlModal);
const findByTestId = (testId) => findModal().find(`[data-testid="${testId}"]`);
const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes[0].disabled;
const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes[0].disabled;
const actionButtonLoadingState = () => findModal().props('actionPrimary').attributes[0].loading;
......@@ -100,6 +100,26 @@ describe('DevopsAdoptionSegmentModal', () => {
expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID);
});
describe('while loading', () => {
beforeEach(() => {
createComponent({ props: { isLoading: true } });
});
it('displays the loading icon', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('does not display the form', () => {
expect(wrapper.findByTestId('groups').exists()).toBe(false);
});
});
it('does not display the loading icon', () => {
createComponent();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
});
describe('modal title', () => {
it('contains the correct admin level title', () => {
createComponent();
......@@ -144,7 +164,7 @@ describe('DevopsAdoptionSegmentModal', () => {
};
it('contains the checkbox tree component', () => {
const checkboxes = findByTestId('groups');
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.exists()).toBe(true);
......@@ -157,14 +177,14 @@ describe('DevopsAdoptionSegmentModal', () => {
describe('filtering', () => {
describe('filter input field', () => {
it('contains the filter input', () => {
const filter = findByTestId('filter');
const filter = wrapper.findByTestId('filter');
expect(filter.exists()).toBe(true);
expect(filter.find(GlFormInput).exists()).toBe(true);
});
it('contains the filter icon', () => {
const icon = findByTestId('filter').find(GlIcon);
const icon = wrapper.findByTestId('filter').find(GlIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('search');
......@@ -183,7 +203,7 @@ describe('DevopsAdoptionSegmentModal', () => {
await nextTick();
const checkboxes = findByTestId('groups');
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.props('options')).toStrictEqual(results);
},
......@@ -197,7 +217,7 @@ describe('DevopsAdoptionSegmentModal', () => {
});
it('displays a warning message when there are no results', async () => {
const warning = findByTestId('filter-warning');
const warning = wrapper.findByTestId('filter-warning');
expect(warning.exists()).toBe(true);
expect(warning.text()).toBe('No filter results.');
......@@ -205,7 +225,7 @@ describe('DevopsAdoptionSegmentModal', () => {
});
it('hides the checkboxes', () => {
const checkboxes = findByTestId('groups');
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.exists()).toBe(false);
});
......@@ -250,7 +270,7 @@ describe('DevopsAdoptionSegmentModal', () => {
});
it('disables the form inputs', async () => {
const checkboxes = findByTestId('groups');
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.attributes('disabled')).not.toBeDefined();
......
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