Implement loading and error state in DAST scans view

This implements a loading and an error state in the on-demand DAST scans
index page.
parent 4a4b80a6
<script>
import { GlTab, GlBadge, GlLink, GlTable, GlKeysetPagination } from '@gitlab/ui';
import {
GlTab,
GlBadge,
GlLink,
GlTable,
GlKeysetPagination,
GlAlert,
GlSkeletonLoader,
} from '@gitlab/ui';
import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { DAST_SHORT_NAME } from '~/security_configuration/components/constants';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { scrollToElement } from '~/lib/utils/common_utils';
import EmptyState from '../empty_state.vue';
......@@ -26,6 +34,8 @@ export default {
GlLink,
GlTable,
GlKeysetPagination,
GlAlert,
GlSkeletonLoader,
CiBadgeLink,
TimeAgoTooltip,
EmptyState,
......@@ -78,6 +88,9 @@ export default {
}
return pipelines;
},
error() {
this.hasError = true;
},
pollInterval: PIPELINES_POLL_INTERVAL,
},
},
......@@ -142,6 +155,9 @@ export default {
i18n: {
previousPage: __('Prev'),
nextPage: __('Next'),
errorMessage: s__(
'OnDemandScans|Could not fetch on-demand scans. Please refresh the page, or try again later.',
),
},
};
</script>
......@@ -152,7 +168,17 @@ export default {
{{ title }}
<gl-badge size="sm" class="gl-tab-counter-badge">{{ itemsCount }}</gl-badge>
</template>
<template v-if="hasPipelines">
<template v-if="$apollo.queries.pipelines.loading">
<gl-skeleton-loader v-for="i in 20" :key="i" :width="815" :height="50">
<rect width="85" height="20" x="15" y="15" rx="4" />
<rect width="155" height="20" x="125" y="15" rx="4" />
<rect width="60" height="20" x="350" y="15" rx="4" />
<rect width="150" height="20" x="450" y="15" rx="4" />
<rect width="70" height="20" x="640" y="15" rx="4" />
<rect width="25" height="20" x="740" y="15" rx="4" />
</gl-skeleton-loader>
</template>
<template v-else-if="hasPipelines">
<gl-table
thead-class="gl-border-b-solid gl-border-gray-100 gl-border-1"
:fields="tableFields"
......@@ -189,6 +215,11 @@ export default {
/>
</div>
</template>
<template v-else-if="hasError">
<gl-alert variant="danger" :dismissible="false" class="gl-my-4" data-testid="error-alert">
{{ $options.i18n.errorMessage }}
</gl-alert>
</template>
<empty-state v-else :title="emptyStateTitle" :text="emptyStateText" no-primary-button />
</gl-tab>
</template>
import { GlTab, GlTable } from '@gitlab/ui';
import { GlTab, GlTable, GlSkeletonLoader, GlAlert } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import allPipelinesWithPipelinesMock from 'test_fixtures/graphql/on_demand_scans/graphql/on_demand_scans.query.graphql.with_pipelines.json';
......@@ -32,6 +32,8 @@ describe('BaseTab', () => {
const findTable = () => wrapper.findComponent(GlTable);
const findEmptyState = () => wrapper.findComponent(EmptyState);
const findPagination = () => wrapper.findByTestId('pagination');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findErrorAlert = () => wrapper.findComponent(GlAlert);
// Helpers
const createMockApolloProvider = () => {
......@@ -95,6 +97,14 @@ describe('BaseTab', () => {
});
});
it('shows a loader until the request resolves', async () => {
createComponent();
expect(findSkeletonLoader().exists()).toBe(true);
await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
});
it('resets the route if no pipeline matches the cursor', async () => {
setWindowLocation('#?after=nothingToSeeHere');
requestHandler = jest.fn().mockResolvedValue(allPipelinesWithoutPipelinesMock);
......@@ -147,7 +157,7 @@ describe('BaseTab', () => {
it('when navigating back to the previous page, the route is updated and pipelines are fetched', async () => {
findPagination().vm.$emit('next');
await wrapper.vm.$nextTick();
await waitForPromises();
findPagination().vm.$emit('prev');
await wrapper.vm.$nextTick();
......@@ -167,4 +177,16 @@ describe('BaseTab', () => {
expect(findEmptyState().exists()).toBe(true);
});
});
describe('when the request errors out', () => {
beforeEach(async () => {
requestHandler = jest.fn().mockRejectedValue();
createComponent();
await waitForPromises();
});
it('show an error alert', () => {
expect(findErrorAlert().exists()).toBe(true);
});
});
});
......@@ -23847,6 +23847,9 @@ msgstr ""
msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the Add a rotation button. To enable notifications for this schedule, you must also create an %{linkStart}escalation policy%{linkEnd}."
msgstr ""
msgid "OnDemandScans|Could not fetch on-demand scans. Please refresh the page, or try again later."
msgstr ""
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr ""
......
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