Commit 13ffb66e authored by Justin Ho Tuan Duong's avatar Justin Ho Tuan Duong Committed by Sean McGivern

Allow users to edit the status of a Jira issue on details page

parent 719697bd
---
name: jira_issue_details_edit_status
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60092
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330628
milestone: '14.1'
type: development
group: group::ecosystem
default_enabled: false
...@@ -5,3 +5,22 @@ export const fetchIssue = async (issuePath) => { ...@@ -5,3 +5,22 @@ export const fetchIssue = async (issuePath) => {
return data; return data;
}); });
}; };
export const fetchIssueStatuses = () => {
// We are using mock data here which should come from the backend
return new Promise((resolve) => {
setTimeout(() => {
// eslint-disable-next-line @gitlab/require-i18n-strings
resolve([{ title: 'In Progress' }, { title: 'Done' }]);
}, 1000);
});
};
export const updateIssue = (issue, { status }) => {
// We are using mock call here which should become a backend call
return new Promise((resolve) => {
setTimeout(() => {
resolve({ ...issue, status });
}, 1000);
});
};
...@@ -7,9 +7,11 @@ import { ...@@ -7,9 +7,11 @@ import {
GlBadge, GlBadge,
GlTooltipDirective as GlTooltip, GlTooltipDirective as GlTooltip,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { fetchIssue } from 'ee/integrations/jira/issues_show/api'; import { fetchIssue, fetchIssueStatuses, updateIssue } 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 createFlash from '~/flash';
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 { s__ } from '~/locale';
...@@ -38,8 +40,11 @@ export default { ...@@ -38,8 +40,11 @@ export default {
data() { data() {
return { return {
isLoading: true, isLoading: true,
isLoadingStatus: false,
isUpdatingStatus: false,
errorMessage: null, errorMessage: null,
issue: {}, issue: {},
statuses: [],
}; };
}, },
computed: { computed: {
...@@ -78,6 +83,41 @@ export default { ...@@ -78,6 +83,41 @@ export default {
jiraIssueCommentId(id) { jiraIssueCommentId(id) {
return `jira_note_${id}`; return `jira_note_${id}`;
}, },
onIssueStatusFetch() {
this.isLoadingStatus = true;
fetchIssueStatuses()
.then((response) => {
this.statuses = response;
})
.catch(() => {
createFlash({
message: s__(
'JiraService|Failed to load Jira issue statuses. View the issue in Jira, or reload the page.',
),
});
})
.finally(() => {
this.isLoadingStatus = false;
});
},
onIssueStatusUpdated(status) {
this.isUpdatingStatus = true;
updateIssue(this.issue, { status })
.then(() => {
this.issue = { ...this.issue, status };
})
.catch(() => {
createFlash({
message: s__(
'JiraService|Failed to update Jira issue status. View the issue in Jira, or reload the page.',
),
});
})
.finally(() => {
this.isUpdatingStatus = false;
});
},
}, },
}; };
</script> </script>
...@@ -117,7 +157,15 @@ export default { ...@@ -117,7 +157,15 @@ export default {
<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"
:is-loading-status="isLoadingStatus"
:is-updating-status="isUpdatingStatus"
:statuses="statuses"
@issue-status-fetch="onIssueStatusFetch"
@issue-status-updated="onIssueStatusUpdated"
/>
</template> </template>
<template #discussion> <template #discussion>
......
<script> <script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import IssueFieldDropdown from './issue_field_dropdown.vue';
export default { export default {
directives: { directives: {
...@@ -8,12 +10,50 @@ export default { ...@@ -8,12 +10,50 @@ export default {
}, },
components: { components: {
GlIcon, GlIcon,
IssueFieldDropdown,
SidebarEditableItem,
},
provide() {
return {
isClassicSidebar: true,
canUpdate: this.canUpdate,
};
}, },
props: { props: {
canUpdate: {
type: Boolean,
required: false,
default: false,
},
dropdownEmpty: {
type: String,
required: false,
default: null,
},
dropdownTitle: {
type: String,
required: false,
default: null,
},
icon: { icon: {
type: String, type: String,
required: true, required: true,
}, },
items: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
updating: {
type: Boolean,
required: false,
default: false,
},
title: { title: {
type: String, type: String,
required: true, required: true,
...@@ -44,20 +84,58 @@ export default { ...@@ -44,20 +84,58 @@ export default {
i18n: { i18n: {
none: __('None'), none: __('None'),
}, },
methods: {
showDropdown() {
this.$refs.dropdown.showDropdown();
this.$emit('issue-field-fetch');
},
expandSidebarAndOpenDropdown() {
this.$emit('expand-sidebar', this.$refs.editableItem);
},
onIssueFieldUpdated(value) {
this.$emit('issue-field-updated', value);
},
},
}; };
</script> </script>
<template> <template>
<div class="block"> <div class="block">
<div v-gl-tooltip="tooltipProps" class="sidebar-collapsed-icon" data-testid="field-collapsed"> <sidebar-editable-item
<gl-icon :name="icon" /> ref="editableItem"
</div> :loading="updating"
:title="title"
@open="showDropdown"
>
<template #collapsed>
<div
v-gl-tooltip="tooltipProps"
class="sidebar-collapsed-icon"
data-testid="field-collapsed"
@click="expandSidebarAndOpenDropdown"
>
<gl-icon :name="icon" />
</div>
<div class="hide-collapsed">
<div class="value" data-testid="field-value">
<span :class="valueClass">{{ valueWithFallback }}</span>
</div>
</div>
</template>
<div class="hide-collapsed"> <template #default>
<div class="title" data-testid="field-title">{{ title }}</div> <issue-field-dropdown
<div class="value"> v-if="canUpdate"
<span :class="valueClass" data-testid="field-value">{{ valueWithFallback }}</span> ref="dropdown"
</div> :empty-text="dropdownEmpty"
</div> :items="items"
:loading="loading"
:text="valueWithFallback"
:title="dropdownTitle"
@issue-field-updated="onIssueFieldUpdated"
/>
</template>
</sidebar-editable-item>
</div> </div>
</template> </template>
<script>
import { GlDropdown, GlDropdownItem, GlDropdownText, GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlDropdownText,
GlLoadingIcon,
},
props: {
emptyText: {
type: String,
required: false,
default: null,
},
items: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: true,
},
text: {
type: String,
required: false,
default: null,
},
title: {
type: String,
required: false,
default: null,
},
},
computed: {
noItems() {
return this.items.length === 0;
},
},
methods: {
showDropdown() {
this.$refs.dropdown.show();
},
selectItem(item) {
this.$emit('issue-field-updated', item.title);
},
},
};
</script>
<template>
<gl-dropdown ref="dropdown" :text="text" :header-text="title" block lazy>
<div v-if="loading" class="gl-h-13">
<gl-loading-icon size="md" />
</div>
<div v-else>
<gl-dropdown-text v-if="noItems">{{ emptyText }}</gl-dropdown-text>
<gl-dropdown-item v-for="item in items" :key="item.title" @click="selectItem(item)">
{{ item.title }}
</gl-dropdown-item>
</div>
</gl-dropdown>
</template>
<script> <script>
import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants'; import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Assignee from './assignee.vue'; import Assignee from './assignee.vue';
import IssueDueDate from './issue_due_date.vue'; import IssueDueDate from './issue_due_date.vue';
import IssueField from './issue_field.vue'; import IssueField from './issue_field.vue';
...@@ -16,6 +17,7 @@ export default { ...@@ -16,6 +17,7 @@ export default {
CopyableField, CopyableField,
LabelsSelect, LabelsSelect,
}, },
mixins: [glFeatureFlagsMixin()],
inject: { inject: {
issuesListPath: { issuesListPath: {
default: null, default: null,
...@@ -30,6 +32,21 @@ export default { ...@@ -30,6 +32,21 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
isLoadingStatus: {
type: Boolean,
required: false,
default: false,
},
isUpdatingStatus: {
type: Boolean,
required: false,
default: false,
},
statuses: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
assignee() { assignee() {
...@@ -39,12 +56,50 @@ export default { ...@@ -39,12 +56,50 @@ export default {
reference() { reference() {
return this.issue.references?.relative; return this.issue.references?.relative;
}, },
canUpdateStatus() {
return this.glFeatures.jiraIssueDetailsEditStatus;
},
}, },
labelsFilterParam, labelsFilterParam,
i18n: { i18n: {
statusTitle: __('Status'), statusTitle: __('Status'),
statusDropdownEmpty: s__('JiraService|No available statuses'),
statusDropdownTitle: __('Change status'),
referenceName: __('Reference'), referenceName: __('Reference'),
}, },
mounted() {
this.sidebarEl = document.querySelector('aside.right-sidebar');
this.sidebarToggleEl = document.querySelector('.js-toggle-right-sidebar-button');
},
methods: {
toggleSidebar() {
this.sidebarToggleEl.dispatchEvent(new Event('click'));
},
expandSidebarAndOpenDropdown(dropdownRef = null) {
// Expand the sidebar if not already expanded.
if (!this.sidebarExpanded) {
this.toggleSidebar();
}
if (dropdownRef) {
// Wait for sidebar expand animation to complete
// before revealing the dropdown.
this.sidebarEl.addEventListener(
'transitionend',
() => {
dropdownRef.expand();
},
{ once: true },
);
}
},
onIssueStatusFetch() {
this.$emit('issue-status-fetch');
},
onIssueStatusUpdated(status) {
this.$emit('issue-status-updated', status);
},
},
}; };
</script> </script>
...@@ -52,7 +107,20 @@ export default { ...@@ -52,7 +107,20 @@ export default {
<div> <div>
<assignee class="block" :assignee="assignee" /> <assignee class="block" :assignee="assignee" />
<issue-due-date :due-date="issue.dueDate" /> <issue-due-date :due-date="issue.dueDate" />
<issue-field icon="progress" :title="$options.i18n.statusTitle" :value="issue.status" /> <issue-field
icon="progress"
:can-update="canUpdateStatus"
:dropdown-title="$options.i18n.statusDropdownTitle"
:dropdown-empty="$options.i18n.statusDropdownEmpty"
:items="statuses"
:loading="isLoadingStatus"
:title="$options.i18n.statusTitle"
:updating="isUpdatingStatus"
:value="issue.status"
@expand-sidebar="expandSidebarAndOpenDropdown"
@issue-field-fetch="onIssueStatusFetch"
@issue-field-updated="onIssueStatusUpdated"
/>
<labels-select <labels-select
:selected-labels="issue.labels" :selected-labels="issue.labels"
:labels-filter-base-path="issuesListPath" :labels-filter-base-path="issuesListPath"
......
...@@ -13,6 +13,9 @@ module Projects ...@@ -13,6 +13,9 @@ module Projects
name: 'i_ecosystem_jira_service_list_issues' name: 'i_ecosystem_jira_service_list_issues'
before_action :check_feature_enabled! before_action :check_feature_enabled!
before_action only: :show do
push_frontend_feature_flag(:jira_issue_details_edit_status, project, default_enabled: :yaml)
end
rescue_from ::Projects::Integrations::Jira::IssuesFinder::IntegrationError, with: :render_integration_error rescue_from ::Projects::Integrations::Jira::IssuesFinder::IntegrationError, with: :render_integration_error
rescue_from ::Projects::Integrations::Jira::IssuesFinder::RequestError, with: :render_request_error rescue_from ::Projects::Integrations::Jira::IssuesFinder::RequestError, with: :render_request_error
......
import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; 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 * as JiraIssuesShowApi from 'ee/integrations/jira/issues_show/api';
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';
import JiraIssueSidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue';
import { issueStates } from 'ee/integrations/jira/issues_show/constants'; import { issueStates } from 'ee/integrations/jira/issues_show/constants';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import IssuableHeader from '~/issuable_show/components/issuable_header.vue'; import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
import IssuableShow from '~/issuable_show/components/issuable_show_root.vue'; import IssuableShow from '~/issuable_show/components/issuable_show_root.vue';
import IssuableSidebar from '~/issuable_sidebar/components/issuable_sidebar_root.vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { mockJiraIssue } from '../mock_data'; import { mockJiraIssue } from '../mock_data';
...@@ -18,14 +22,16 @@ describe('JiraIssuesShow', () => { ...@@ -18,14 +22,16 @@ describe('JiraIssuesShow', () => {
const findGlAlert = () => wrapper.findComponent(GlAlert); const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findIssuableShow = () => wrapper.findComponent(IssuableShow); const findIssuableShow = () => wrapper.findComponent(IssuableShow);
const findJiraIssueSidebar = () => wrapper.findComponent(JiraIssueSidebar);
const findIssuableShowStatusBadge = () => const findIssuableShowStatusBadge = () =>
wrapper.findComponent(IssuableHeader).find('[data-testid="status"]'); wrapper.findComponent(IssuableHeader).find('[data-testid="status"]');
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(JiraIssuesShow, { wrapper = shallowMount(JiraIssuesShow, {
stubs: { stubs: {
IssuableShow,
IssuableHeader, IssuableHeader,
IssuableShow,
IssuableSidebar,
}, },
provide: { provide: {
issuesShowPath: mockJiraIssuesShowPath, issuesShowPath: mockJiraIssuesShowPath,
...@@ -39,11 +45,7 @@ describe('JiraIssuesShow', () => { ...@@ -39,11 +45,7 @@ describe('JiraIssuesShow', () => {
afterEach(() => { afterEach(() => {
mockAxios.restore(); mockAxios.restore();
wrapper.destroy();
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
}); });
describe('when issue is loading', () => { describe('when issue is loading', () => {
...@@ -109,4 +111,45 @@ describe('JiraIssuesShow', () => { ...@@ -109,4 +111,45 @@ describe('JiraIssuesShow', () => {
expect(findIssuableShowStatusBadge().text()).toBe(badgeText); expect(findIssuableShowStatusBadge().text()).toBe(badgeText);
}); });
}); });
describe('JiraIssueSidebar events', () => {
beforeEach(async () => {
mockAxios.onGet(mockJiraIssuesShowPath).replyOnce(200, mockJiraIssue);
createComponent();
await waitForPromises();
});
it('fetches issue statuses on issue-status-fetch', async () => {
const fetchIssueStatusesSpy = jest
.spyOn(JiraIssuesShowApi, 'fetchIssueStatuses')
.mockResolvedValue();
findJiraIssueSidebar().vm.$emit('issue-status-fetch');
await wrapper.vm.$nextTick();
expect(fetchIssueStatusesSpy).toHaveBeenCalled();
expect(findJiraIssueSidebar().props('isLoadingStatus')).toBe(true);
await waitForPromises();
expect(findJiraIssueSidebar().props('isLoadingStatus')).toBe(false);
});
it('updates issue status on issue-status-updated', async () => {
const updateIssueSpy = jest.spyOn(JiraIssuesShowApi, 'updateIssue').mockResolvedValue();
const status = 'In Review';
findJiraIssueSidebar().vm.$emit('issue-status-updated', status);
await wrapper.vm.$nextTick();
expect(updateIssueSpy).toHaveBeenCalledWith(expect.any(Object), { status });
expect(findJiraIssueSidebar().props('isUpdatingStatus')).toBe(true);
await waitForPromises();
expect(findJiraIssueSidebar().props('isUpdatingStatus')).toBe(false);
});
});
}); });
import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import IssueFieldDropdown from 'ee/integrations/jira/issues_show/components/sidebar/issue_field_dropdown.vue';
import { mockJiraIssueStatuses } from '../../mock_data';
describe('IssueFieldDropdown', () => {
let wrapper;
const emptyText = 'empty text';
const defaultProps = {
emptyText,
text: 'issue field text',
title: 'issue field header text',
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(IssueFieldDropdown, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
const findAllGlDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
it.each`
loading | items
${true} | ${[]}
${true} | ${mockJiraIssueStatuses}
${false} | ${[]}
${false} | ${mockJiraIssueStatuses}
`('with loading = $loading, items = $items', ({ loading, items }) => {
createComponent({
props: {
loading,
items,
},
});
expect(findGlLoadingIcon().exists()).toBe(loading);
if (!loading) {
if (items.length) {
findAllGlDropdownItems().wrappers.forEach((itemWrapper, index) => {
expect(itemWrapper.text()).toBe(mockJiraIssueStatuses[index].title);
});
} else {
expect(wrapper.text()).toBe(emptyText);
}
}
});
});
import { GlIcon } from '@gitlab/ui'; import { GlButton, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import IssueField from 'ee/integrations/jira/issues_show/components/sidebar/issue_field.vue'; import IssueField from 'ee/integrations/jira/issues_show/components/sidebar/issue_field.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
describe('IssueField', () => { describe('IssueField', () => {
let wrapper; let wrapper;
...@@ -15,37 +16,44 @@ describe('IssueField', () => { ...@@ -15,37 +16,44 @@ describe('IssueField', () => {
}; };
const createComponent = ({ props = {} } = {}) => { const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper( wrapper = shallowMountExtended(IssueField, {
shallowMount(IssueField, { directives: {
propsData: { ...defaultProps, ...props }, GlTooltip: createMockDirective(),
directives: { },
GlTooltip: createMockDirective(), propsData: { ...defaultProps, ...props },
}, stubs: {
}), SidebarEditableItem,
); },
});
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
const findFieldTitle = () => wrapper.findByTestId('field-title'); const findEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findFieldValue = () => wrapper.findByTestId('field-value'); const findEditButton = () => wrapper.findComponent(GlButton);
const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed'); const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed');
const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip'); const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip');
const findFieldValue = () => wrapper.findByTestId('field-value');
const findGlIcon = () => wrapper.findComponent(GlIcon); const findGlIcon = () => wrapper.findComponent(GlIcon);
it('renders title', () => { describe('template', () => {
createComponent(); beforeEach(() => {
createComponent();
});
expect(findFieldTitle().text()).toBe(defaultProps.title); it('renders title', () => {
}); expect(findEditableItem().props('title')).toBe(defaultProps.title);
});
it('renders GlIcon (when collapsed)', () => { it('renders GlIcon (when collapsed)', () => {
createComponent(); expect(findGlIcon().props('name')).toBe(defaultProps.icon);
});
expect(findGlIcon().props('name')).toBe(defaultProps.icon); it('does not render "Edit" button', () => {
expect(findEditButton().exists()).toBe(false);
});
}); });
describe('without value prop', () => { describe('without value prop', () => {
...@@ -53,7 +61,7 @@ describe('IssueField', () => { ...@@ -53,7 +61,7 @@ describe('IssueField', () => {
createComponent(); createComponent();
}); });
it('renders fallback value with "no-value" class', () => { it('falls back to "None"', () => {
expect(findFieldValue().text()).toBe('None'); expect(findFieldValue().text()).toBe('None');
}); });
...@@ -74,7 +82,7 @@ describe('IssueField', () => { ...@@ -74,7 +82,7 @@ describe('IssueField', () => {
}); });
}); });
it('renders value', () => { it('renders the value', () => {
expect(findFieldValue().text()).toBe(value); expect(findFieldValue().text()).toBe(value);
}); });
...@@ -85,4 +93,25 @@ describe('IssueField', () => { ...@@ -85,4 +93,25 @@ describe('IssueField', () => {
expect(tooltip.value.title).toBe(value); expect(tooltip.value.title).toBe(value);
}); });
}); });
describe('with canUpdate = true', () => {
beforeEach(() => {
createComponent({
props: { canUpdate: true },
});
});
it('renders "Edit" button', () => {
expect(findEditButton().text()).toBe('Edit');
});
it('emits "issue-field-fetch" when dropdown is opened', () => {
wrapper.vm.$refs.dropdown.showDropdown = jest.fn();
findEditableItem().vm.$emit('open');
expect(wrapper.vm.$refs.dropdown.showDropdown).toHaveBeenCalled();
expect(wrapper.emitted('issue-field-fetch')).toHaveLength(1);
});
});
}); });
...@@ -25,10 +25,7 @@ describe('JiraIssuesSidebar', () => { ...@@ -25,10 +25,7 @@ describe('JiraIssuesSidebar', () => {
}; };
afterEach(() => { afterEach(() => {
if (wrapper) { wrapper.destroy();
wrapper.destroy();
wrapper = null;
}
}); });
const findLabelsSelect = () => wrapper.findComponent(LabelsSelect); const findLabelsSelect = () => wrapper.findComponent(LabelsSelect);
......
...@@ -41,3 +41,5 @@ export const mockJiraIssueComment = { ...@@ -41,3 +41,5 @@ export const mockJiraIssueComment = {
}, },
id: 10000, id: 10000,
}; };
export const mockJiraIssueStatuses = [{ title: 'In Progress' }, { title: 'Done' }];
...@@ -18394,9 +18394,15 @@ msgstr "" ...@@ -18394,9 +18394,15 @@ 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 statuses. View the issue in Jira, or reload the page."
msgstr ""
msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page." msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page."
msgstr "" msgstr ""
msgid "JiraService|Failed to update Jira issue status. 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 ""
...@@ -18445,6 +18451,9 @@ msgstr "" ...@@ -18445,6 +18451,9 @@ msgstr ""
msgid "JiraService|Move to Done" msgid "JiraService|Move to Done"
msgstr "" msgstr ""
msgid "JiraService|No available statuses"
msgstr ""
msgid "JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}." msgid "JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}."
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