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