Commit d51ecaaa authored by Tom Quirk's avatar Tom Quirk Committed by Phil Hughes

Add error/loading state to Jira Issues detail

Adds basic error and loading states.
parent 195d5f87
......@@ -346,7 +346,7 @@ export default {
@columnMetricChange="setColumnMetric"
@pageChange="setPage"
/>
<gl-alert v-if="showMergeRequestTableNoData" variant="info" :dismissable="false">
<gl-alert v-if="showMergeRequestTableNoData" variant="info" :dismissible="false">
{{ __('There is no data available. Please change your selection.') }}
</gl-alert>
</div>
......
<script>
import { GlAlert, GlSprintf, GlLink, GlBadge, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import {
GlAlert,
GlSprintf,
GlLink,
GlLoadingIcon,
GlBadge,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { fetchIssue } from 'ee/integrations/jira/issues_show/api';
import JiraIssueSidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue';
import { issueStates, issueStateLabels } from 'ee/integrations/jira/issues_show/constants';
import IssuableShow from '~/issuable_show/components/issuable_show_root.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import Note from './note.vue';
export default {
......@@ -14,6 +22,7 @@ export default {
GlSprintf,
GlLink,
GlBadge,
GlLoadingIcon,
IssuableShow,
JiraIssueSidebar,
Note,
......@@ -29,6 +38,7 @@ export default {
data() {
return {
isLoading: true,
errorMessage: null,
issue: {},
};
},
......@@ -46,13 +56,25 @@ export default {
return this.isIssueOpen ? 'issue-open-m' : 'mobile-issue-close';
},
},
async mounted() {
this.issue = convertObjectPropsToCamelCase(await fetchIssue(this.issuesShowPath), {
deep: true,
});
this.isLoading = false;
mounted() {
this.loadIssue();
},
methods: {
loadIssue() {
fetchIssue(this.issuesShowPath)
.then((issue) => {
this.issue = convertObjectPropsToCamelCase(issue, { deep: true });
})
.catch(() => {
this.errorMessage = s__(
'JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page.',
);
})
.finally(() => {
this.isLoading = false;
});
},
jiraIssueCommentId(id) {
return `jira_note_${id}`;
},
......@@ -62,57 +84,62 @@ export default {
<template>
<div class="gl-mt-5">
<gl-alert
variant="info"
:dismissible="false"
:title="s__('JiraService|This issue is synchronized with Jira')"
class="gl-mb-2"
>
<gl-sprintf
:message="
s__(
`JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}.`,
)
"
>
<template #link="{ content }">
<gl-link :href="issue.webUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
<gl-loading-icon v-if="isLoading" size="lg" />
<gl-alert v-else-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<template v-else>
<gl-alert
variant="info"
:dismissible="false"
:title="s__('JiraService|This issue is synchronized with Jira')"
class="gl-mb-2"
>
<gl-sprintf
:message="
s__(
`JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}.`,
)
"
>
<template #link="{ content }">
<gl-link :href="issue.webUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<issuable-show
v-if="!isLoading"
:issuable="issue"
:enable-edit="false"
:status-badge-class="statusBadgeClass"
:status-icon="statusIcon"
>
<template #status-badge>{{ statusBadgeText }}</template>
<issuable-show
:issuable="issue"
:enable-edit="false"
:status-badge-class="statusBadgeClass"
:status-icon="statusIcon"
>
<template #status-badge>{{ statusBadgeText }}</template>
<template #right-sidebar-items="{ sidebarExpanded }">
<jira-issue-sidebar :sidebar-expanded="sidebarExpanded" :issue="issue" />
</template>
<template #right-sidebar-items="{ sidebarExpanded }">
<jira-issue-sidebar :sidebar-expanded="sidebarExpanded" :issue="issue" />
</template>
<template #discussion>
<note
v-for="comment in issue.comments"
:id="jiraIssueCommentId(comment.id)"
:key="comment.id"
:author-avatar-url="comment.author.avatarUrl"
:author-web-url="comment.author.webUrl"
:author-name="comment.author.name"
:author-username="comment.author.username"
:note-body-html="comment.bodyHtml"
:note-created-at="comment.createdAt"
>
<template #badges>
<gl-badge v-gl-tooltip="{ title: __('This is a Jira user.') }">
{{ __('Jira user') }}
</gl-badge>
</template>
</note>
</template>
</issuable-show>
<template #discussion>
<note
v-for="comment in issue.comments"
:id="jiraIssueCommentId(comment.id)"
:key="comment.id"
:author-avatar-url="comment.author.avatarUrl"
:author-web-url="comment.author.webUrl"
:author-name="comment.author.name"
:author-username="comment.author.username"
:note-body-html="comment.bodyHtml"
:note-created-at="comment.createdAt"
>
<template #badges>
<gl-badge v-gl-tooltip="{ title: __('This is a Jira user.') }">
{{ __('Jira user') }}
</gl-badge>
</template>
</note>
</template>
</issuable-show>
</template>
</div>
</template>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import JiraIssuesShow from 'ee/integrations/jira/issues_show/components/jira_issues_show_root.vue';
......@@ -14,6 +15,8 @@ describe('JiraIssuesShow', () => {
let wrapper;
let mockAxios;
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findIssuableShow = () => wrapper.findComponent(IssuableShow);
const findIssuableShowStatusBadge = () =>
wrapper.findComponent(IssuableHeader).find('[data-testid="status"]');
......@@ -43,13 +46,42 @@ describe('JiraIssuesShow', () => {
}
});
describe('when issue is loading', () => {
it('renders GlLoadingIcon', () => {
createComponent();
expect(findGlLoadingIcon().exists()).toBe(true);
expect(findGlAlert().exists()).toBe(false);
expect(findIssuableShow().exists()).toBe(false);
});
});
describe('when error occurs during fetch', () => {
it('renders error message', async () => {
mockAxios.onGet(mockJiraIssuesShowPath).replyOnce(500);
createComponent();
await waitForPromises();
const alert = findGlAlert();
expect(findGlLoadingIcon().exists()).toBe(false);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(
'Failed to load Jira issue. View the issue in Jira, or reload the page.',
);
expect(alert.props('variant')).toBe('danger');
expect(findIssuableShow().exists()).toBe(false);
});
});
it('renders IssuableShow', async () => {
mockAxios.onGet(mockJiraIssuesShowPath).replyOnce(200, mockJiraIssue);
createComponent();
await waitForPromises();
await wrapper.vm.$nextTick();
expect(findGlLoadingIcon().exists()).toBe(false);
expect(findIssuableShow().exists()).toBe(true);
});
......@@ -63,7 +95,6 @@ describe('JiraIssuesShow', () => {
createComponent();
await waitForPromises();
await wrapper.vm.$nextTick();
});
it('sets `statusIcon` prop correctly', () => {
......
......@@ -17229,6 +17229,9 @@ msgstr ""
msgid "JiraService|Events for %{noteable_model_name} are disabled."
msgstr ""
msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page."
msgstr ""
msgid "JiraService|Fetch issue types for this Jira project"
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