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