From 5ab405a202b621415eb3ce919ef8933f105a03f7 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 9 Sep 2021 10:34:53 +0800 Subject: [PATCH 01/17] Feature ZenTao integration: Extract Issue Detail Part Extract duplicate code for Issue Detail component --- .../external_issue/issues_show/api.js | 37 +++ .../components/external_issues_show_root.vue | 219 ++++++++++++++++++ .../issues_show/components/note.vue | 100 ++++++++ .../components/sidebar/assignee.vue | 86 +++++++ .../sidebar/external_issues_sidebar_root.vue | 170 ++++++++++++++ .../components/sidebar/issue_due_date.vue | 76 ++++++ .../components/sidebar/issue_field.vue | 141 +++++++++++ .../sidebar/issue_field_dropdown.vue | 66 ++++++ .../external_issue/issues_show/constants.js | 13 ++ .../external_issues_show_bundle.js | 43 ++++ 10 files changed, 951 insertions(+) create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/api.js create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/constants.js create mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/api.js b/app/assets/javascripts/integrations/external_issue/issues_show/api.js new file mode 100644 index 00000000000000..95fe28c90e2306 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/api.js @@ -0,0 +1,37 @@ +import axios from '~/lib/utils/axios_utils'; + +export const fetchIssue = async (issuePath) => { + return axios.get(issuePath).then(({ 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, { labels = [], status = undefined }) => { + // We are using mock call here which should become a backend call + return new Promise((resolve) => { + setTimeout(() => { + const addedLabels = labels.filter((label) => label.set); + const removedLabelsIds = labels.filter((label) => !label.set).map((label) => label.id); + + const finalLabels = [...issue.labels, ...addedLabels].filter( + (label) => !removedLabelsIds.includes(label.id), + ); + + resolve({ + ...issue, + ...(status ? { status } : {}), + labels: finalLabels, + }); + }, 1000); + }); +}; diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue new file mode 100644 index 00000000000000..77d736fcc2fb24 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue @@ -0,0 +1,219 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue new file mode 100644 index 00000000000000..ff7fbd75ad4b05 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue @@ -0,0 +1,100 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue new file mode 100644 index 00000000000000..81048183987d10 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue new file mode 100644 index 00000000000000..f255bc7468a82f --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue @@ -0,0 +1,170 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue new file mode 100644 index 00000000000000..66360feda1abfc --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue @@ -0,0 +1,76 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue new file mode 100644 index 00000000000000..2e24d77632f9a4 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue @@ -0,0 +1,141 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue new file mode 100644 index 00000000000000..37df42c80e5a52 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/constants.js b/app/assets/javascripts/integrations/external_issue/issues_show/constants.js new file mode 100644 index 00000000000000..29bb2dedfd2554 --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/constants.js @@ -0,0 +1,13 @@ +import { __ } from '~/locale'; + +export const issueStates = { + OPENED: 'opened', + CLOSED: 'closed', +}; + +export const issueStateLabels = { + [issueStates.OPENED]: __('Open'), + [issueStates.CLOSED]: __('Closed'), +}; + +export const labelsFilterParam = 'labels'; diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js b/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js new file mode 100644 index 00000000000000..d00307ebeb052c --- /dev/null +++ b/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; + +import ExternalIssueShowRoot from './components/external_issues_show_root.vue'; + +/** + * + * @param @required {Object} provides + * @type provides: { + * failFetchingIssueText: string + * failFetchingIssueStatusText: string + * failUpdatingIssueLabelsText: string + * failUpdatingIssueStatusText: string + * featureFlagCanEditLabelKey: string + * featureFlagCanEditStatusKey: string + * seeMoreDetailText: string + * statusDropdownEmptyText: string + * titleText: string + * userTypeText: string + * userTypeTooltipText: string + * } + */ + +export default function ExternalIssueShowFactory(provides) { + return function initZentaoIssueShow({ mountPointSelector }) { + const mountPointEl = document.querySelector(mountPointSelector); + + if (!mountPointEl) { + return null; + } + + const { issuesShowPath, issuesListPath } = mountPointEl.dataset; + + return new Vue({ + el: mountPointEl, + provide: { + issuesShowPath, + issuesListPath, + ...provides, + }, + render: (createElement) => createElement(ExternalIssueShowRoot), + }); + }; +} -- GitLab From ab2279da9e14ea2960a354adbede8e216e72a997 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 16 Sep 2021 11:14:17 +0800 Subject: [PATCH 02/17] Extract common sub components from Jira Integration Jira with External Issue Update test cases against the change Rebase upstream --- .../external_issue/issues_show/api.js | 37 --- .../components/external_issues_show_root.vue | 219 ------------------ .../sidebar/external_issues_sidebar_root.vue | 170 -------------- .../external_issues_show_bundle.js | 43 ---- .../issues_show/components/note.vue | 5 + .../components/sidebar/assignee.vue | 5 +- .../components/sidebar/issue_due_date.vue | 0 .../components/sidebar/issue_field.vue | 0 .../sidebar/issue_field_dropdown.vue | 0 .../external_issue/issues_show/constants.js | 0 .../__snapshots__/note_spec.js.snap | 32 +++ .../issues_show/components/note_spec.js | 70 ++++++ .../__snapshots__/assignee_spec.js.snap | 76 ++++++ .../components/sidebar/assignee_spec.js | 121 ++++++++++ .../components/sidebar/issue_due_date_spec.js | 69 ++++++ .../sidebar/issue_field_dropdown_spec.js | 57 +++++ .../components/sidebar/issue_field_spec.js | 117 ++++++++++ .../external_issue/issues_show/mock_data.js | 45 ++++ .../sidebar/jira_issues_sidebar_root_spec.js | 6 + 19 files changed, 600 insertions(+), 472 deletions(-) delete mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/api.js delete mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue delete mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue delete mode 100644 app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/components/note.vue (96%) rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue (94%) rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue (100%) rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue (100%) rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue (100%) rename {app => ee/app}/assets/javascripts/integrations/external_issue/issues_show/constants.js (100%) create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js create mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/api.js b/app/assets/javascripts/integrations/external_issue/issues_show/api.js deleted file mode 100644 index 95fe28c90e2306..00000000000000 --- a/app/assets/javascripts/integrations/external_issue/issues_show/api.js +++ /dev/null @@ -1,37 +0,0 @@ -import axios from '~/lib/utils/axios_utils'; - -export const fetchIssue = async (issuePath) => { - return axios.get(issuePath).then(({ 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, { labels = [], status = undefined }) => { - // We are using mock call here which should become a backend call - return new Promise((resolve) => { - setTimeout(() => { - const addedLabels = labels.filter((label) => label.set); - const removedLabelsIds = labels.filter((label) => !label.set).map((label) => label.id); - - const finalLabels = [...issue.labels, ...addedLabels].filter( - (label) => !removedLabelsIds.includes(label.id), - ); - - resolve({ - ...issue, - ...(status ? { status } : {}), - labels: finalLabels, - }); - }, 1000); - }); -}; diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue deleted file mode 100644 index 77d736fcc2fb24..00000000000000 --- a/app/assets/javascripts/integrations/external_issue/issues_show/components/external_issues_show_root.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue b/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue deleted file mode 100644 index f255bc7468a82f..00000000000000 --- a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js b/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js deleted file mode 100644 index d00307ebeb052c..00000000000000 --- a/app/assets/javascripts/integrations/external_issue/issues_show/external_issues_show_bundle.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; - -import ExternalIssueShowRoot from './components/external_issues_show_root.vue'; - -/** - * - * @param @required {Object} provides - * @type provides: { - * failFetchingIssueText: string - * failFetchingIssueStatusText: string - * failUpdatingIssueLabelsText: string - * failUpdatingIssueStatusText: string - * featureFlagCanEditLabelKey: string - * featureFlagCanEditStatusKey: string - * seeMoreDetailText: string - * statusDropdownEmptyText: string - * titleText: string - * userTypeText: string - * userTypeTooltipText: string - * } - */ - -export default function ExternalIssueShowFactory(provides) { - return function initZentaoIssueShow({ mountPointSelector }) { - const mountPointEl = document.querySelector(mountPointSelector); - - if (!mountPointEl) { - return null; - } - - const { issuesShowPath, issuesListPath } = mountPointEl.dataset; - - return new Vue({ - el: mountPointEl, - provide: { - issuesShowPath, - issuesListPath, - ...provides, - }, - render: (createElement) => createElement(ExternalIssueShowRoot), - }); - }; -} diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue similarity index 96% rename from app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue index ff7fbd75ad4b05..43a53febb5eb5f 100644 --- a/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue +++ b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue @@ -42,6 +42,11 @@ export default { required: false, default: undefined, }, + badgeText: { + type: String, + required: false, + default: '', + }, }, computed: { noteAnchor() { diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue similarity index 94% rename from app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue index 81048183987d10..5e2a22789c7f66 100644 --- a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue +++ b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue @@ -4,7 +4,7 @@ import { __ } from '~/locale'; import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; export default { - name: 'ExternalIssuesSidebarAssignee', + name: 'JiraIssuesSidebarAssignee', components: { GlAvatarLabeled, GlAvatarLink, @@ -15,7 +15,6 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - inject: ['userTypeText'], props: { assignee: { type: Object, @@ -61,7 +60,7 @@ export default { :alt="assignee.name" :entity-name="assignee.name" :label="assignee.name" - :sub-label="userTypeText" + :sub-label="__('Jira user')" /> {{ __('None') }} diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue similarity index 100% rename from app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue similarity index 100% rename from app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue similarity index 100% rename from app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue diff --git a/app/assets/javascripts/integrations/external_issue/issues_show/constants.js b/ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js similarity index 100% rename from app/assets/javascripts/integrations/external_issue/issues_show/constants.js rename to ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap b/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap new file mode 100644 index 00000000000000..e93474eaad6ac0 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`JiraIssuesNote template renders note 1`] = ` +" + + + + + +
+
+
+ + Justin Ho + + · + + + +
+
+
+
+
+
+

hi

+
+
+
+
+
" +`; diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js new file mode 100644 index 00000000000000..636d7179b91f13 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js @@ -0,0 +1,70 @@ +import { shallowMount } from '@vue/test-utils'; +import JiraIssueNote from 'ee/external_issues_show/components/note.vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; + +import { mockExternalIssueComment } from '../mock_data'; + +describe('JiraIssuesNote', () => { + let wrapper; + + const findTimeAgoLink = () => wrapper.findByTestId('time-ago-link'); + const findBadgesContainer = () => wrapper.findByTestId('badges-container'); + const findAuthorUsernameLink = () => wrapper.findByTestId('author-username'); + + const createComponent = ({ props, slots } = {}) => { + wrapper = extendedWrapper( + shallowMount(JiraIssueNote, { + propsData: { + authorName: mockExternalIssueComment.author.name, + authorWebUrl: mockExternalIssueComment.author.web_url, + authorAvatarUrl: mockExternalIssueComment.author.avatar_url, + noteCreatedAt: mockExternalIssueComment.created_at, + noteBodyHtml: mockExternalIssueComment.body_html, + ...props, + }, + slots, + }), + ); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('template', () => { + it('renders note', () => { + createComponent(); + + expect(wrapper.html()).toMatchSnapshot(); + }); + + it.each` + id | expectedTimeAgoHref + ${undefined} | ${'#'} + ${'1234'} | ${'#1234'} + `( + 'sets "time ago" link to $expectedTimeAgoHref when id is $id', + ({ id, expectedTimeAgoHref }) => { + createComponent({ props: { id } }); + + expect(findTimeAgoLink().attributes('href')).toBe(expectedTimeAgoHref); + }, + ); + + describe('with badge slot', () => { + it('renders slot content', () => { + createComponent({ slots: { badges: 'testing badges content' } }); + + expect(findBadgesContainer().html()).toContain('testing badges content'); + }); + }); + + describe('with author username', () => { + it('renders slot content', () => { + createComponent({ props: { authorUsername: 'testuser' } }); + + expect(findAuthorUsernameLink().html()).toContain('testuser'); + }); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap new file mode 100644 index 00000000000000..3864ef3db13fe7 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExternalIssuesSidebarAssignee with assignee template renders avatar components 1`] = ` +
+
+ + + + + +
+ + +
+`; + +exports[`ExternalIssuesSidebarAssignee with no assignee template renders template without avatar components (the "None" state) 1`] = ` +
+
+ + + + None + +
+ + +
+`; diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js new file mode 100644 index 00000000000000..6cb80536449874 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js @@ -0,0 +1,121 @@ +import { GlAvatarLabeled, GlAvatarLink, GlAvatar } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; + +import { mockExternalIssue } from '../../mock_data'; + +const mockAssignee = convertObjectPropsToCamelCase(mockExternalIssue.assignees[0], { deep: true }); + +describe('ExternalIssuesSidebarAssignee', () => { + let wrapper; + + const findNoAssigneeText = () => wrapper.findByTestId('no-assignee-text'); + const findNoAssigneeIcon = () => wrapper.findByTestId('no-assignee-text'); + const findAvatar = () => wrapper.find(GlAvatar); + const findAvatarLabeled = () => wrapper.find(GlAvatarLabeled); + const findAvatarLink = () => wrapper.find(GlAvatarLink); + const findSidebarCollapsedIconWrapper = () => + wrapper.findByTestId('sidebar-collapsed-icon-wrapper'); + + const createComponent = ({ assignee } = {}) => { + wrapper = extendedWrapper( + shallowMount(Assignee, { + propsData: { + assignee, + }, + }), + ); + }; + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('with assignee', () => { + beforeEach(() => { + createComponent({ assignee: mockAssignee }); + }); + + describe('template', () => { + it('renders avatar components', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('renders GlAvatarLink with correct props', () => { + const avatarLink = findAvatarLink(); + + expect(avatarLink.exists()).toBe(true); + expect(avatarLink.attributes()).toMatchObject({ + href: mockAssignee.webUrl, + title: mockAssignee.name, + }); + }); + + it('renders GlAvatarLabeled with correct props', () => { + const avatarLabeled = findAvatarLabeled(); + + expect(avatarLabeled.exists()).toBe(true); + expect(avatarLabeled.attributes()).toMatchObject({ + src: mockAssignee.avatarUrl, + alt: mockAssignee.name, + 'entity-name': mockAssignee.name, + }); + expect(avatarLabeled.props('label')).toBe(mockAssignee.name); + }); + + it('renders GlAvatar with correct props', () => { + const avatar = findAvatar(); + + expect(avatar.exists()).toBe(true); + expect(avatar.attributes()).toMatchObject({ + src: mockAssignee.avatarUrl, + alt: mockAssignee.name, + }); + expect(avatar.props('entityName')).toBe(mockAssignee.name); + }); + + it('renders AssigneeTitle with correct props', () => { + const title = wrapper.find(AssigneeTitle); + + expect(title.exists()).toBe(true); + expect(title.props('numberOfAssignees')).toBe(1); + }); + + it('does not render "No assignee" text', () => { + expect(findNoAssigneeText().exists()).toBe(false); + }); + + it('does not render "No assignee" icon', () => { + expect(findNoAssigneeIcon().exists()).toBe(false); + }); + + it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { + const iconWrapper = findSidebarCollapsedIconWrapper(); + expect(iconWrapper.attributes('title')).toBe(mockAssignee.name); + }); + }); + }); + + describe('with no assignee', () => { + beforeEach(() => { + createComponent({ assignee: undefined }); + }); + + describe('template', () => { + it('renders template without avatar components (the "None" state)', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { + const iconWrapper = findSidebarCollapsedIconWrapper(); + expect(iconWrapper.attributes('title')).toBe('No assignee'); + }); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js new file mode 100644 index 00000000000000..3c86b080dcf2f3 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js @@ -0,0 +1,69 @@ +import { shallowMount } from '@vue/test-utils'; + +import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; + +import { useFakeDate } from 'helpers/fake_date'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; + +describe('IssueDueDate', () => { + let wrapper; + + const createComponent = ({ props = {} } = {}) => { + wrapper = extendedWrapper( + shallowMount(IssueDueDate, { + propsData: props, + }), + ); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findDueDateCollapsed = () => wrapper.findByTestId('due-date-collapsed'); + const findDueDateValue = () => wrapper.findByTestId('due-date-value'); + + describe('when dueDate is null', () => { + it('renders "None" as value', () => { + createComponent(); + + expect(findDueDateCollapsed().text()).toBe('None'); + expect(findDueDateValue().text()).toBe('None'); + }); + }); + + describe('when dueDate is in the past', () => { + const dueDate = '2021-02-14T00:00:00.000Z'; + + useFakeDate(2021, 2, 18); + + it('renders formatted dueDate', () => { + createComponent({ + props: { + dueDate, + }, + }); + + expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); + expect(findDueDateValue().text()).toBe('Feb 14, 2021 (Past due)'); + }); + }); + + describe('when dueDate is in the future', () => { + const dueDate = '2021-02-14T00:00:00.000Z'; + + useFakeDate(2020, 12, 20); + + it('renders formatted dueDate', () => { + createComponent({ + props: { + dueDate, + }, + }); + + expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); + expect(findDueDateValue().text()).toBe('Feb 14, 2021'); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js new file mode 100644 index 00000000000000..c9be0498261104 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js @@ -0,0 +1,57 @@ +import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; + +import IssueFieldDropdown from 'ee/external_issues_show/components/sidebar/issue_field_dropdown.vue'; + +import { mockExternalIssueStatuses } 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} | ${mockExternalIssueStatuses} + ${false} | ${[]} + ${false} | ${mockExternalIssueStatuses} + `('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(mockExternalIssueStatuses[index].title); + }); + } else { + expect(wrapper.text()).toBe(emptyText); + } + } + }); +}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js new file mode 100644 index 00000000000000..4efa0e173fd685 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js @@ -0,0 +1,117 @@ +import { GlButton, GlIcon } from '@gitlab/ui'; + +import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; + +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; + +describe('IssueField', () => { + let wrapper; + + const defaultProps = { + icon: 'calendar', + title: 'Field Title', + }; + + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMountExtended(IssueField, { + directives: { + GlTooltip: createMockDirective(), + }, + propsData: { ...defaultProps, ...props }, + stubs: { + SidebarEditableItem, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); + const findEditButton = () => wrapper.findComponent(GlButton); + const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed'); + const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip'); + const findFieldValue = () => wrapper.findByTestId('field-value'); + const findGlIcon = () => wrapper.findComponent(GlIcon); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders title', () => { + expect(findEditableItem().props('title')).toBe(defaultProps.title); + }); + + it('renders GlIcon (when collapsed)', () => { + expect(findGlIcon().props('name')).toBe(defaultProps.icon); + }); + + it('does not render "Edit" button', () => { + expect(findEditButton().exists()).toBe(false); + }); + }); + + describe('without value prop', () => { + beforeEach(() => { + createComponent(); + }); + + it('falls back to "None"', () => { + expect(findFieldValue().text()).toBe('None'); + }); + + it('renders tooltip (when collapsed) with "value" = title', () => { + const tooltip = findFieldCollapsedTooltip(); + + expect(tooltip).toBeDefined(); + expect(tooltip.value.title).toBe(defaultProps.title); + }); + }); + + describe('with value prop', () => { + const value = 'field value'; + + beforeEach(() => { + createComponent({ + props: { value }, + }); + }); + + it('renders the value', () => { + expect(findFieldValue().text()).toBe(value); + }); + + it('renders tooltip (when collapsed) with "value" = value', () => { + const tooltip = findFieldCollapsedTooltip(); + + expect(tooltip).toBeDefined(); + 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); + }); + }); +}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js b/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js new file mode 100644 index 00000000000000..79c0c622e6de64 --- /dev/null +++ b/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js @@ -0,0 +1,45 @@ +export const mockExternalIssue = { + title: 'FE-2 The second FE issue on Jira', + description_html: + 'FE-2 The second FE issue on Jira', + created_at: '"2021-02-01T04:04:40.833Z"', + author: { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + assignees: [ + { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + ], + due_date: '2021-02-14T00:00:00.000Z', + labels: [ + { + title: 'In Progress', + description: 'Work that is still in progress', + color: '#0052CC', + text_color: '#FFFFFF', + }, + ], + references: { + relative: 'FE-2', + }, + state: 'opened', + status: 'In Progress', +}; + +export const mockExternalIssueComment = { + body_html: '

hi

', + created_at: '"2021-02-01T04:04:40.833Z"', + author: { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + id: 10000, +}; + +export const mockExternalIssueStatuses = [{ title: 'In Progress' }, { title: 'Done' }]; diff --git a/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js b/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js index f3c9e1c887a9da..c561583f001162 100644 --- a/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js +++ b/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js @@ -1,7 +1,13 @@ import { shallowMount } from '@vue/test-utils'; +<<<<<<< HEAD import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; +======= +import Assignee from 'ee/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; +import IssueDueDate from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; +import IssueField from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; +>>>>>>> 25008f18f53 (Extract common sub components from Jira) import Sidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; -- GitLab From a598794ccf9934ab419301c49713b13dbee09fe0 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Tue, 28 Sep 2021 14:12:49 +0800 Subject: [PATCH 03/17] Move external issues out from integrations --- .../issues_show/components/note.vue | 105 ------------- .../components/sidebar/assignee.vue | 85 ----------- .../components/sidebar/issue_due_date.vue | 76 ---------- .../components/sidebar/issue_field.vue | 141 ------------------ .../sidebar/issue_field_dropdown.vue | 66 -------- .../external_issue/issues_show/constants.js | 13 -- .../__snapshots__/note_spec.js.snap | 32 ---- .../issues_show/components/note_spec.js | 70 --------- .../__snapshots__/assignee_spec.js.snap | 76 ---------- .../components/sidebar/assignee_spec.js | 121 --------------- .../components/sidebar/issue_due_date_spec.js | 69 --------- .../sidebar/issue_field_dropdown_spec.js | 57 ------- .../components/sidebar/issue_field_spec.js | 117 --------------- .../external_issue/issues_show/mock_data.js | 45 ------ .../sidebar/jira_issues_sidebar_root_spec.js | 7 +- 15 files changed, 1 insertion(+), 1079 deletions(-) delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue delete mode 100644 ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js delete mode 100644 ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue deleted file mode 100644 index 43a53febb5eb5f..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/note.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue deleted file mode 100644 index 5e2a22789c7f66..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/assignee.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue deleted file mode 100644 index 66360feda1abfc..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue deleted file mode 100644 index 2e24d77632f9a4..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue b/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue deleted file mode 100644 index 37df42c80e5a52..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js b/ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js deleted file mode 100644 index 29bb2dedfd2554..00000000000000 --- a/ee/app/assets/javascripts/integrations/external_issue/issues_show/constants.js +++ /dev/null @@ -1,13 +0,0 @@ -import { __ } from '~/locale'; - -export const issueStates = { - OPENED: 'opened', - CLOSED: 'closed', -}; - -export const issueStateLabels = { - [issueStates.OPENED]: __('Open'), - [issueStates.CLOSED]: __('Closed'), -}; - -export const labelsFilterParam = 'labels'; diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap b/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap deleted file mode 100644 index e93474eaad6ac0..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/__snapshots__/note_spec.js.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`JiraIssuesNote template renders note 1`] = ` -" - - - - - -
-
-
- - Justin Ho - - · - - - -
-
-
-
-
-
-

hi

-
-
-
-
-
" -`; diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js deleted file mode 100644 index 636d7179b91f13..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/note_spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import JiraIssueNote from 'ee/external_issues_show/components/note.vue'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; - -import { mockExternalIssueComment } from '../mock_data'; - -describe('JiraIssuesNote', () => { - let wrapper; - - const findTimeAgoLink = () => wrapper.findByTestId('time-ago-link'); - const findBadgesContainer = () => wrapper.findByTestId('badges-container'); - const findAuthorUsernameLink = () => wrapper.findByTestId('author-username'); - - const createComponent = ({ props, slots } = {}) => { - wrapper = extendedWrapper( - shallowMount(JiraIssueNote, { - propsData: { - authorName: mockExternalIssueComment.author.name, - authorWebUrl: mockExternalIssueComment.author.web_url, - authorAvatarUrl: mockExternalIssueComment.author.avatar_url, - noteCreatedAt: mockExternalIssueComment.created_at, - noteBodyHtml: mockExternalIssueComment.body_html, - ...props, - }, - slots, - }), - ); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('template', () => { - it('renders note', () => { - createComponent(); - - expect(wrapper.html()).toMatchSnapshot(); - }); - - it.each` - id | expectedTimeAgoHref - ${undefined} | ${'#'} - ${'1234'} | ${'#1234'} - `( - 'sets "time ago" link to $expectedTimeAgoHref when id is $id', - ({ id, expectedTimeAgoHref }) => { - createComponent({ props: { id } }); - - expect(findTimeAgoLink().attributes('href')).toBe(expectedTimeAgoHref); - }, - ); - - describe('with badge slot', () => { - it('renders slot content', () => { - createComponent({ slots: { badges: 'testing badges content' } }); - - expect(findBadgesContainer().html()).toContain('testing badges content'); - }); - }); - - describe('with author username', () => { - it('renders slot content', () => { - createComponent({ props: { authorUsername: 'testuser' } }); - - expect(findAuthorUsernameLink().html()).toContain('testuser'); - }); - }); - }); -}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap deleted file mode 100644 index 3864ef3db13fe7..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ExternalIssuesSidebarAssignee with assignee template renders avatar components 1`] = ` -
-
- - - - - -
- - -
-`; - -exports[`ExternalIssuesSidebarAssignee with no assignee template renders template without avatar components (the "None" state) 1`] = ` -
-
- - - - None - -
- - -
-`; diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js deleted file mode 100644 index 6cb80536449874..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/assignee_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import { GlAvatarLabeled, GlAvatarLink, GlAvatar } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; - -import { mockExternalIssue } from '../../mock_data'; - -const mockAssignee = convertObjectPropsToCamelCase(mockExternalIssue.assignees[0], { deep: true }); - -describe('ExternalIssuesSidebarAssignee', () => { - let wrapper; - - const findNoAssigneeText = () => wrapper.findByTestId('no-assignee-text'); - const findNoAssigneeIcon = () => wrapper.findByTestId('no-assignee-text'); - const findAvatar = () => wrapper.find(GlAvatar); - const findAvatarLabeled = () => wrapper.find(GlAvatarLabeled); - const findAvatarLink = () => wrapper.find(GlAvatarLink); - const findSidebarCollapsedIconWrapper = () => - wrapper.findByTestId('sidebar-collapsed-icon-wrapper'); - - const createComponent = ({ assignee } = {}) => { - wrapper = extendedWrapper( - shallowMount(Assignee, { - propsData: { - assignee, - }, - }), - ); - }; - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - wrapper = null; - } - }); - - describe('with assignee', () => { - beforeEach(() => { - createComponent({ assignee: mockAssignee }); - }); - - describe('template', () => { - it('renders avatar components', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('renders GlAvatarLink with correct props', () => { - const avatarLink = findAvatarLink(); - - expect(avatarLink.exists()).toBe(true); - expect(avatarLink.attributes()).toMatchObject({ - href: mockAssignee.webUrl, - title: mockAssignee.name, - }); - }); - - it('renders GlAvatarLabeled with correct props', () => { - const avatarLabeled = findAvatarLabeled(); - - expect(avatarLabeled.exists()).toBe(true); - expect(avatarLabeled.attributes()).toMatchObject({ - src: mockAssignee.avatarUrl, - alt: mockAssignee.name, - 'entity-name': mockAssignee.name, - }); - expect(avatarLabeled.props('label')).toBe(mockAssignee.name); - }); - - it('renders GlAvatar with correct props', () => { - const avatar = findAvatar(); - - expect(avatar.exists()).toBe(true); - expect(avatar.attributes()).toMatchObject({ - src: mockAssignee.avatarUrl, - alt: mockAssignee.name, - }); - expect(avatar.props('entityName')).toBe(mockAssignee.name); - }); - - it('renders AssigneeTitle with correct props', () => { - const title = wrapper.find(AssigneeTitle); - - expect(title.exists()).toBe(true); - expect(title.props('numberOfAssignees')).toBe(1); - }); - - it('does not render "No assignee" text', () => { - expect(findNoAssigneeText().exists()).toBe(false); - }); - - it('does not render "No assignee" icon', () => { - expect(findNoAssigneeIcon().exists()).toBe(false); - }); - - it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { - const iconWrapper = findSidebarCollapsedIconWrapper(); - expect(iconWrapper.attributes('title')).toBe(mockAssignee.name); - }); - }); - }); - - describe('with no assignee', () => { - beforeEach(() => { - createComponent({ assignee: undefined }); - }); - - describe('template', () => { - it('renders template without avatar components (the "None" state)', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { - const iconWrapper = findSidebarCollapsedIconWrapper(); - expect(iconWrapper.attributes('title')).toBe('No assignee'); - }); - }); - }); -}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js deleted file mode 100644 index 3c86b080dcf2f3..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_due_date_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; - -import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; - -import { useFakeDate } from 'helpers/fake_date'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; - -describe('IssueDueDate', () => { - let wrapper; - - const createComponent = ({ props = {} } = {}) => { - wrapper = extendedWrapper( - shallowMount(IssueDueDate, { - propsData: props, - }), - ); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findDueDateCollapsed = () => wrapper.findByTestId('due-date-collapsed'); - const findDueDateValue = () => wrapper.findByTestId('due-date-value'); - - describe('when dueDate is null', () => { - it('renders "None" as value', () => { - createComponent(); - - expect(findDueDateCollapsed().text()).toBe('None'); - expect(findDueDateValue().text()).toBe('None'); - }); - }); - - describe('when dueDate is in the past', () => { - const dueDate = '2021-02-14T00:00:00.000Z'; - - useFakeDate(2021, 2, 18); - - it('renders formatted dueDate', () => { - createComponent({ - props: { - dueDate, - }, - }); - - expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); - expect(findDueDateValue().text()).toBe('Feb 14, 2021 (Past due)'); - }); - }); - - describe('when dueDate is in the future', () => { - const dueDate = '2021-02-14T00:00:00.000Z'; - - useFakeDate(2020, 12, 20); - - it('renders formatted dueDate', () => { - createComponent({ - props: { - dueDate, - }, - }); - - expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); - expect(findDueDateValue().text()).toBe('Feb 14, 2021'); - }); - }); -}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js deleted file mode 100644 index c9be0498261104..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; - -import IssueFieldDropdown from 'ee/external_issues_show/components/sidebar/issue_field_dropdown.vue'; - -import { mockExternalIssueStatuses } 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} | ${mockExternalIssueStatuses} - ${false} | ${[]} - ${false} | ${mockExternalIssueStatuses} - `('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(mockExternalIssueStatuses[index].title); - }); - } else { - expect(wrapper.text()).toBe(emptyText); - } - } - }); -}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js b/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js deleted file mode 100644 index 4efa0e173fd685..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/components/sidebar/issue_field_spec.js +++ /dev/null @@ -1,117 +0,0 @@ -import { GlButton, GlIcon } from '@gitlab/ui'; - -import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; - -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; - -import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; - -describe('IssueField', () => { - let wrapper; - - const defaultProps = { - icon: 'calendar', - title: 'Field Title', - }; - - const createComponent = ({ props = {} } = {}) => { - wrapper = shallowMountExtended(IssueField, { - directives: { - GlTooltip: createMockDirective(), - }, - propsData: { ...defaultProps, ...props }, - stubs: { - SidebarEditableItem, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); - const findEditButton = () => wrapper.findComponent(GlButton); - const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed'); - const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip'); - const findFieldValue = () => wrapper.findByTestId('field-value'); - const findGlIcon = () => wrapper.findComponent(GlIcon); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders title', () => { - expect(findEditableItem().props('title')).toBe(defaultProps.title); - }); - - it('renders GlIcon (when collapsed)', () => { - expect(findGlIcon().props('name')).toBe(defaultProps.icon); - }); - - it('does not render "Edit" button', () => { - expect(findEditButton().exists()).toBe(false); - }); - }); - - describe('without value prop', () => { - beforeEach(() => { - createComponent(); - }); - - it('falls back to "None"', () => { - expect(findFieldValue().text()).toBe('None'); - }); - - it('renders tooltip (when collapsed) with "value" = title', () => { - const tooltip = findFieldCollapsedTooltip(); - - expect(tooltip).toBeDefined(); - expect(tooltip.value.title).toBe(defaultProps.title); - }); - }); - - describe('with value prop', () => { - const value = 'field value'; - - beforeEach(() => { - createComponent({ - props: { value }, - }); - }); - - it('renders the value', () => { - expect(findFieldValue().text()).toBe(value); - }); - - it('renders tooltip (when collapsed) with "value" = value', () => { - const tooltip = findFieldCollapsedTooltip(); - - expect(tooltip).toBeDefined(); - 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); - }); - }); -}); diff --git a/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js b/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js deleted file mode 100644 index 79c0c622e6de64..00000000000000 --- a/ee/spec/frontend/integrations/external_issue/issues_show/mock_data.js +++ /dev/null @@ -1,45 +0,0 @@ -export const mockExternalIssue = { - title: 'FE-2 The second FE issue on Jira', - description_html: - 'FE-2 The second FE issue on Jira', - created_at: '"2021-02-01T04:04:40.833Z"', - author: { - name: 'Justin Ho', - web_url: 'http://127.0.0.1:3000/root', - avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', - }, - assignees: [ - { - name: 'Justin Ho', - web_url: 'http://127.0.0.1:3000/root', - avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', - }, - ], - due_date: '2021-02-14T00:00:00.000Z', - labels: [ - { - title: 'In Progress', - description: 'Work that is still in progress', - color: '#0052CC', - text_color: '#FFFFFF', - }, - ], - references: { - relative: 'FE-2', - }, - state: 'opened', - status: 'In Progress', -}; - -export const mockExternalIssueComment = { - body_html: '

hi

', - created_at: '"2021-02-01T04:04:40.833Z"', - author: { - name: 'Justin Ho', - web_url: 'http://127.0.0.1:3000/root', - avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', - }, - id: 10000, -}; - -export const mockExternalIssueStatuses = [{ title: 'In Progress' }, { title: 'Done' }]; diff --git a/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js b/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js index c561583f001162..654ae5362256cb 100644 --- a/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js +++ b/ee/spec/frontend/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root_spec.js @@ -1,13 +1,8 @@ import { shallowMount } from '@vue/test-utils'; -<<<<<<< HEAD + import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; -======= -import Assignee from 'ee/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; -import IssueDueDate from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; -import IssueField from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; ->>>>>>> 25008f18f53 (Extract common sub components from Jira) import Sidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; -- GitLab From 00efc677fc6ae0ac5cf633a9eb8a88615ac52dd1 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 9 Sep 2021 10:43:41 +0800 Subject: [PATCH 04/17] Feature ZenTao integration with external issue detail Integrate ZenTao detail with External Issue detail component Add test accordingly --- .../issues_show/zentao_issues_show_bundle.js | 26 +++ locale/gitlab.pot | 24 +++ .../__snapshots__/assignee_spec.js.snap | 76 +++++++ .../components/sidebar/assignee_spec.js | 124 ++++++++++++ .../components/sidebar/issue_due_date_spec.js | 69 +++++++ .../sidebar/issue_field_dropdown_spec.js | 57 ++++++ .../components/sidebar/issue_field_spec.js | 117 +++++++++++ .../zentao_issues_sidebar_root_spec.js | 64 ++++++ .../zentao_issues_show_root_spec.js | 185 ++++++++++++++++++ .../zentao/issues_show/mock_data.js | 71 +++++++ 10 files changed, 813 insertions(+) create mode 100644 app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js create mode 100644 spec/frontend/integrations/zentao/issues_show/mock_data.js diff --git a/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js b/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js new file mode 100644 index 00000000000000..836f5c11c78701 --- /dev/null +++ b/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js @@ -0,0 +1,26 @@ +import ExternalIssueShowFactory from '~/integrations/external_issue/issues_show/external_issues_show_bundle'; +import { __, s__ } from '~/locale'; + +export default ExternalIssueShowFactory({ + failFetchingIssueText: s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + failFetchingIssueStatusText: s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + failUpdatingIssueLabelsText: s__( + 'ZenTaoIntegration|Failed to update ZenTao issue labels. View the issue in ZenTao, or reload the page.', + ), + failUpdatingIssueStatusText: s__( + 'ZenTaoIntegration|Failed to update ZenTao issue status. View the issue in ZenTao, or reload the page.', + ), + featureFlagCanEditLabelKey: 'zentaoIssueDetailsEditLabels', + featureFlagCanEditStatusKey: 'zentaoIssueDetailsEditStatus', + seeMoreDetailText: s__( + `ZenTaoIntegration|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}ZenTao%{linkEnd}.`, + ), + statusDropdownEmptyText: s__('ZenTaoIntegration|No available statuses'), + titleText: s__('ZenTaoIntegration|This issue is synchronized with ZenTao'), + userTypeText: __('ZenTao user'), + userTypeTooltipText: __('This is a ZenTao user.'), +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1603365d0af43c..2e1092ded404d7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34724,6 +34724,9 @@ msgstr "" msgid "This is a Jira user." msgstr "" +msgid "This is a ZenTao user." +msgstr "" + msgid "This is a confidential %{noteableTypeText}." msgstr "" @@ -39477,6 +39480,27 @@ msgstr[1] "" msgid "Your username is %{username}." msgstr "" +msgid "ZenTao user" +msgstr "" + +msgid "ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page." +msgstr "" + +msgid "ZenTaoIntegration|Failed to update ZenTao issue labels. View the issue in ZenTao, or reload the page." +msgstr "" + +msgid "ZenTaoIntegration|Failed to update ZenTao issue status. View the issue in ZenTao, or reload the page." +msgstr "" + +msgid "ZenTaoIntegration|No available statuses" +msgstr "" + +msgid "ZenTaoIntegration|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}ZenTao%{linkEnd}." +msgstr "" + +msgid "ZenTaoIntegration|This issue is synchronized with ZenTao" +msgstr "" + msgid "ZentaoIntegration|Base URL of the Zentao instance." msgstr "" diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap b/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap new file mode 100644 index 00000000000000..0c3b6bc14c977c --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap @@ -0,0 +1,76 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ZentaoIssuesSidebarAssignee with assignee template renders avatar components 1`] = ` +
+
+ + + + + +
+ + +
+`; + +exports[`ZentaoIssuesSidebarAssignee with no assignee template renders template without avatar components (the "None" state) 1`] = ` +
+
+ + + + None + +
+ + +
+`; diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js new file mode 100644 index 00000000000000..1013cbbf025f81 --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js @@ -0,0 +1,124 @@ +import { GlAvatarLabeled, GlAvatarLink, GlAvatar } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import Assignee from '~/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; + +import { mockZentaoIssue, mockZentaoIssuesProvides } from '../../mock_data'; + +const mockAssignee = convertObjectPropsToCamelCase(mockZentaoIssue.assignees[0], { deep: true }); + +describe('ZentaoIssuesSidebarAssignee', () => { + let wrapper; + + const findNoAssigneeText = () => wrapper.findByTestId('no-assignee-text'); + const findNoAssigneeIcon = () => wrapper.findByTestId('no-assignee-text'); + const findAvatar = () => wrapper.find(GlAvatar); + const findAvatarLabeled = () => wrapper.find(GlAvatarLabeled); + const findAvatarLink = () => wrapper.find(GlAvatarLink); + const findSidebarCollapsedIconWrapper = () => + wrapper.findByTestId('sidebar-collapsed-icon-wrapper'); + + const createComponent = ({ assignee } = {}) => { + wrapper = extendedWrapper( + shallowMount(Assignee, { + propsData: { + assignee, + }, + provide: { + userTypeText: mockZentaoIssuesProvides.userTypeText, + }, + }), + ); + }; + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('with assignee', () => { + beforeEach(() => { + createComponent({ assignee: mockAssignee }); + }); + + describe('template', () => { + it('renders avatar components', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('renders GlAvatarLink with correct props', () => { + const avatarLink = findAvatarLink(); + + expect(avatarLink.exists()).toBe(true); + expect(avatarLink.attributes()).toMatchObject({ + href: mockAssignee.webUrl, + title: mockAssignee.name, + }); + }); + + it('renders GlAvatarLabeled with correct props', () => { + const avatarLabeled = findAvatarLabeled(); + + expect(avatarLabeled.exists()).toBe(true); + expect(avatarLabeled.attributes()).toMatchObject({ + src: mockAssignee.avatarUrl, + alt: mockAssignee.name, + 'entity-name': mockAssignee.name, + }); + expect(avatarLabeled.props('label')).toBe(mockAssignee.name); + }); + + it('renders GlAvatar with correct props', () => { + const avatar = findAvatar(); + + expect(avatar.exists()).toBe(true); + expect(avatar.attributes()).toMatchObject({ + src: mockAssignee.avatarUrl, + alt: mockAssignee.name, + }); + expect(avatar.props('entityName')).toBe(mockAssignee.name); + }); + + it('renders AssigneeTitle with correct props', () => { + const title = wrapper.find(AssigneeTitle); + + expect(title.exists()).toBe(true); + expect(title.props('numberOfAssignees')).toBe(1); + }); + + it('does not render "No assignee" text', () => { + expect(findNoAssigneeText().exists()).toBe(false); + }); + + it('does not render "No assignee" icon', () => { + expect(findNoAssigneeIcon().exists()).toBe(false); + }); + + it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { + const iconWrapper = findSidebarCollapsedIconWrapper(); + expect(iconWrapper.attributes('title')).toBe(mockAssignee.name); + }); + }); + }); + + describe('with no assignee', () => { + beforeEach(() => { + createComponent({ assignee: undefined }); + }); + + describe('template', () => { + it('renders template without avatar components (the "None" state)', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { + const iconWrapper = findSidebarCollapsedIconWrapper(); + expect(iconWrapper.attributes('title')).toBe('No assignee'); + }); + }); + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js new file mode 100644 index 00000000000000..6281972c1f245a --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js @@ -0,0 +1,69 @@ +import { shallowMount } from '@vue/test-utils'; + +import { useFakeDate } from 'helpers/fake_date'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; + +import IssueDueDate from '~/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; + +describe('IssueDueDate', () => { + let wrapper; + + const createComponent = ({ props = {} } = {}) => { + wrapper = extendedWrapper( + shallowMount(IssueDueDate, { + propsData: props, + }), + ); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findDueDateCollapsed = () => wrapper.findByTestId('due-date-collapsed'); + const findDueDateValue = () => wrapper.findByTestId('due-date-value'); + + describe('when dueDate is null', () => { + it('renders "None" as value', () => { + createComponent(); + + expect(findDueDateCollapsed().text()).toBe('None'); + expect(findDueDateValue().text()).toBe('None'); + }); + }); + + describe('when dueDate is in the past', () => { + const dueDate = '2021-02-14T00:00:00.000Z'; + + useFakeDate(2021, 2, 18); + + it('renders formatted dueDate', () => { + createComponent({ + props: { + dueDate, + }, + }); + + expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); + expect(findDueDateValue().text()).toBe('Feb 14, 2021 (Past due)'); + }); + }); + + describe('when dueDate is in the future', () => { + const dueDate = '2021-02-14T00:00:00.000Z'; + + useFakeDate(2020, 12, 20); + + it('renders formatted dueDate', () => { + createComponent({ + props: { + dueDate, + }, + }); + + expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); + expect(findDueDateValue().text()).toBe('Feb 14, 2021'); + }); + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js new file mode 100644 index 00000000000000..c1fbeb062aa36d --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js @@ -0,0 +1,57 @@ +import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; + +import IssueFieldDropdown from '~/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue'; + +import { mockZentaoIssueStatuses } 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} | ${mockZentaoIssueStatuses} + ${false} | ${[]} + ${false} | ${mockZentaoIssueStatuses} + `('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(mockZentaoIssueStatuses[index].title); + }); + } else { + expect(wrapper.text()).toBe(emptyText); + } + } + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js new file mode 100644 index 00000000000000..99a300ecd6af2a --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js @@ -0,0 +1,117 @@ +import { GlButton, GlIcon } from '@gitlab/ui'; + +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; + +import IssueField from '~/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; + +import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; + +describe('IssueField', () => { + let wrapper; + + const defaultProps = { + icon: 'calendar', + title: 'Field Title', + }; + + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMountExtended(IssueField, { + directives: { + GlTooltip: createMockDirective(), + }, + propsData: { ...defaultProps, ...props }, + stubs: { + SidebarEditableItem, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); + const findEditButton = () => wrapper.findComponent(GlButton); + const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed'); + const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip'); + const findFieldValue = () => wrapper.findByTestId('field-value'); + const findGlIcon = () => wrapper.findComponent(GlIcon); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders title', () => { + expect(findEditableItem().props('title')).toBe(defaultProps.title); + }); + + it('renders GlIcon (when collapsed)', () => { + expect(findGlIcon().props('name')).toBe(defaultProps.icon); + }); + + it('does not render "Edit" button', () => { + expect(findEditButton().exists()).toBe(false); + }); + }); + + describe('without value prop', () => { + beforeEach(() => { + createComponent(); + }); + + it('falls back to "None"', () => { + expect(findFieldValue().text()).toBe('None'); + }); + + it('renders tooltip (when collapsed) with "value" = title', () => { + const tooltip = findFieldCollapsedTooltip(); + + expect(tooltip).toBeDefined(); + expect(tooltip.value.title).toBe(defaultProps.title); + }); + }); + + describe('with value prop', () => { + const value = 'field value'; + + beforeEach(() => { + createComponent({ + props: { value }, + }); + }); + + it('renders the value', () => { + expect(findFieldValue().text()).toBe(value); + }); + + it('renders tooltip (when collapsed) with "value" = value', () => { + const tooltip = findFieldCollapsedTooltip(); + + expect(tooltip).toBeDefined(); + 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); + }); + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js new file mode 100644 index 00000000000000..04ddf9922b0c18 --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js @@ -0,0 +1,64 @@ +import { shallowMount } from '@vue/test-utils'; +import Assignee from '~/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; +import Sidebar from '~/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue'; +import IssueDueDate from '~/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; +import IssueField from '~/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; +import { mockZentaoIssue as mockZentaoIssueData, mockZentaoIssuesProvides } from '../../mock_data'; + +const mockZentaoIssue = convertObjectPropsToCamelCase(mockZentaoIssueData, { deep: true }); + +describe('ZentaoIssuesSidebar', () => { + let wrapper; + + const defaultProps = { + sidebarExpanded: false, + issue: mockZentaoIssue, + }; + + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMount(Sidebar, { + propsData: { ...defaultProps, ...props }, + provide: mockZentaoIssuesProvides, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findLabelsSelect = () => wrapper.findComponent(LabelsSelect); + const findAssignee = () => wrapper.findComponent(Assignee); + const findIssueDueDate = () => wrapper.findComponent(IssueDueDate); + const findIssueField = () => wrapper.findComponent(IssueField); + + it('renders Labels block', () => { + createComponent(); + + expect(findLabelsSelect().props('selectedLabels')).toBe(mockZentaoIssue.labels); + }); + + it('renders Assignee block', () => { + createComponent(); + const assignee = findAssignee(); + + expect(assignee.props('assignee')).toBe(mockZentaoIssue.assignees[0]); + }); + + it('renders IssueDueDate', () => { + createComponent(); + const dueDate = findIssueDueDate(); + + expect(dueDate.props('dueDate')).toBe(mockZentaoIssue.dueDate); + }); + + it('renders IssueField', () => { + createComponent(); + const field = findIssueField(); + + expect(field.props('icon')).toBe('progress'); + expect(field.props('title')).toBe('Status'); + expect(field.props('value')).toBe(mockZentaoIssue.status); + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js b/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js new file mode 100644 index 00000000000000..b542a3f5ae0628 --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js @@ -0,0 +1,185 @@ +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; + +import waitForPromises from 'helpers/wait_for_promises'; +import * as ZentaoIssuesShowApi from '~/integrations/external_issue/issues_show/api'; +import ZentaoIssuesShow from '~/integrations/external_issue/issues_show/components/external_issues_show_root.vue'; +import ZentaoIssueSidebar from '~/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue'; +import { issueStates } from '~/integrations/external_issue/issues_show/constants'; +import IssuableHeader from '~/issuable_show/components/issuable_header.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 { s__ } from '~/locale'; +import { mockZentaoIssue, mockZentaoIssuesProvides } from '../mock_data'; + +const mockZentaoIssuesShowPath = 'zentao_issues_show_path'; + +describe('ZentaoIssuesShow', () => { + let wrapper; + let mockAxios; + + const findGlAlert = () => wrapper.findComponent(GlAlert); + const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findIssuableShow = () => wrapper.findComponent(IssuableShow); + const findZentaoIssueSidebar = () => wrapper.findComponent(ZentaoIssueSidebar); + const findIssuableShowStatusBadge = () => + wrapper.findComponent(IssuableHeader).find('[data-testid="status"]'); + + const createComponent = () => { + wrapper = shallowMount(ZentaoIssuesShow, { + stubs: { + IssuableHeader, + IssuableShow, + IssuableSidebar, + }, + provide: { + issuesShowPath: mockZentaoIssuesShowPath, + ...mockZentaoIssuesProvides, + }, + }); + }; + + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); + + afterEach(() => { + mockAxios.restore(); + wrapper.destroy(); + }); + + 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(mockZentaoIssuesShowPath).replyOnce(500); + createComponent(); + + await waitForPromises(); + + const alert = findGlAlert(); + + expect(findGlLoadingIcon().exists()).toBe(false); + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe( + s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + ); + expect(alert.props('variant')).toBe('danger'); + expect(findIssuableShow().exists()).toBe(false); + }); + }); + + it('renders IssuableShow', async () => { + mockAxios.onGet(mockZentaoIssuesShowPath).replyOnce(200, mockZentaoIssue); + createComponent(); + + await waitForPromises(); + + expect(findGlLoadingIcon().exists()).toBe(false); + expect(findIssuableShow().exists()).toBe(true); + }); + + describe.each` + state | statusIcon | statusBadgeClass | badgeText + ${issueStates.OPENED} | ${'issue-open-m'} | ${'status-box-open'} | ${'Open'} + ${issueStates.CLOSED} | ${'mobile-issue-close'} | ${'status-box-issue-closed'} | ${'Closed'} + `('when issue state is `$state`', ({ state, statusIcon, statusBadgeClass, badgeText }) => { + beforeEach(async () => { + mockAxios.onGet(mockZentaoIssuesShowPath).replyOnce(200, { ...mockZentaoIssue, state }); + createComponent(); + + await waitForPromises(); + }); + + it('sets `statusIcon` prop correctly', () => { + expect(findIssuableShow().props('statusIcon')).toBe(statusIcon); + }); + + it('sets `statusBadgeClass` prop correctly', () => { + expect(findIssuableShow().props('statusBadgeClass')).toBe(statusBadgeClass); + }); + + it('renders correct status badge text', () => { + expect(findIssuableShowStatusBadge().text()).toBe(badgeText); + }); + }); + + describe('ZentaoIssueSidebar events', () => { + beforeEach(async () => { + mockAxios.onGet(mockZentaoIssuesShowPath).replyOnce(200, mockZentaoIssue); + createComponent(); + + await waitForPromises(); + }); + + it('updates issue labels on issue-labels-updated', async () => { + const updateIssueSpy = jest.spyOn(ZentaoIssuesShowApi, 'updateIssue').mockResolvedValue(); + + const labels = [{ id: 'ecosystem' }]; + + findZentaoIssueSidebar().vm.$emit('issue-labels-updated', labels); + await wrapper.vm.$nextTick(); + + expect(updateIssueSpy).toHaveBeenCalledWith(expect.any(Object), { labels }); + expect(findZentaoIssueSidebar().props('isUpdatingLabels')).toBe(true); + + await waitForPromises(); + + expect(findZentaoIssueSidebar().props('isUpdatingLabels')).toBe(false); + }); + + it('fetches issue statuses on issue-status-fetch', async () => { + const fetchIssueStatusesSpy = jest + .spyOn(ZentaoIssuesShowApi, 'fetchIssueStatuses') + .mockResolvedValue(); + + findZentaoIssueSidebar().vm.$emit('issue-status-fetch'); + await wrapper.vm.$nextTick(); + + expect(fetchIssueStatusesSpy).toHaveBeenCalled(); + expect(findZentaoIssueSidebar().props('isLoadingStatus')).toBe(true); + + await waitForPromises(); + + expect(findZentaoIssueSidebar().props('isLoadingStatus')).toBe(false); + }); + + it('updates issue status on issue-status-updated', async () => { + const updateIssueSpy = jest.spyOn(ZentaoIssuesShowApi, 'updateIssue').mockResolvedValue(); + + const status = 'In Review'; + + findZentaoIssueSidebar().vm.$emit('issue-status-updated', status); + await wrapper.vm.$nextTick(); + + expect(updateIssueSpy).toHaveBeenCalledWith(expect.any(Object), { status }); + expect(findZentaoIssueSidebar().props('isUpdatingStatus')).toBe(true); + + await waitForPromises(); + + expect(findZentaoIssueSidebar().props('isUpdatingStatus')).toBe(false); + }); + + it('updates `sidebarExpanded` prop on `sidebar-toggle` event', async () => { + const zentaoIssueSidebar = findZentaoIssueSidebar(); + expect(zentaoIssueSidebar.props('sidebarExpanded')).toBe(true); + + zentaoIssueSidebar.vm.$emit('sidebar-toggle'); + await wrapper.vm.$nextTick(); + + expect(zentaoIssueSidebar.props('sidebarExpanded')).toBe(false); + }); + }); +}); diff --git a/spec/frontend/integrations/zentao/issues_show/mock_data.js b/spec/frontend/integrations/zentao/issues_show/mock_data.js new file mode 100644 index 00000000000000..394dcc39a44374 --- /dev/null +++ b/spec/frontend/integrations/zentao/issues_show/mock_data.js @@ -0,0 +1,71 @@ +import { s__, __ } from '~/locale'; + +export const mockZentaoIssue = { + title: 'FE-2 The second FE issue on Zentao', + description_html: + 'FE-2 The second FE issue on Zentao', + created_at: '"2021-02-01T04:04:40.833Z"', + author: { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + assignees: [ + { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + ], + due_date: '2021-02-14T00:00:00.000Z', + labels: [ + { + title: 'In Progress', + description: 'Work that is still in progress', + color: '#0052CC', + text_color: '#FFFFFF', + }, + ], + references: { + relative: 'FE-2', + }, + state: 'opened', + status: 'In Progress', +}; + +export const mockZentaoIssueComment = { + body_html: '

hi

', + created_at: '"2021-02-01T04:04:40.833Z"', + author: { + name: 'Justin Ho', + web_url: 'http://127.0.0.1:3000/root', + avatar_url: 'http://127.0.0.1:3000/uploads/-/system/user/avatar/1/avatar.png?width=90', + }, + id: 10000, +}; + +export const mockZentaoIssueStatuses = [{ title: 'In Progress' }, { title: 'Done' }]; + +export const mockZentaoIssuesProvides = { + failFetchingIssueText: s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + failFetchingIssueStatusText: s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + failUpdatingIssueLabelsText: s__( + 'ZenTaoIntegration|Failed to update ZenTao issue labels. View the issue in ZenTao, or reload the page.', + ), + failUpdatingIssueStatusText: s__( + 'ZenTaoIntegration|Failed to update ZenTao issue status. View the issue in ZenTao, or reload the page.', + ), + featureFlagCanEditLabelKey: 'zentaoIssueDetailsEditLabels', + featureFlagCanEditStatusKey: 'zentaoIssueDetailsEditStatus', + seeMoreDetailText: s__( + `ZenTaoIntegration|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}ZenTao%{linkEnd}.`, + ), + statusDropdownEmptyText: s__('ZenTaoIntegration|No available statuses'), + titleText: s__('ZenTaoIntegration|This issue is synchronized with ZenTao'), + userTypeText: __('ZenTao user'), + userTypeTooltipText: __('This is a ZenTao user.'), +}; -- GitLab From 2d2ad7d8700388374ea17214571e9ab61f661d37 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 16 Sep 2021 15:26:03 +0800 Subject: [PATCH 05/17] Integration with abstracted external detail Update integration code with abstracted external detail Update test against changes --- .../issues_show/zentao_issues_show_bundle.js | 26 --- .../integrations/zentao/issues_show/api.js | 37 +++ .../sidebar/zentao_issues_sidebar_root.vue | 166 +++++++++++++ .../components/zentao_issues_show_root.vue | 219 ++++++++++++++++++ .../zentao/issues_show/constants.js | 13 ++ .../issues_show/zentao_issues_show_bundle.js | 23 ++ .../zentao_issues_sidebar_root_spec.js | 8 +- .../zentao_issues_show_root_spec.js | 8 +- .../zentao/issues_show/mock_data.js | 0 .../__snapshots__/assignee_spec.js.snap | 76 ------ .../components/sidebar/assignee_spec.js | 124 ---------- .../components/sidebar/issue_due_date_spec.js | 69 ------ .../sidebar/issue_field_dropdown_spec.js | 57 ----- .../components/sidebar/issue_field_spec.js | 117 ---------- 14 files changed, 466 insertions(+), 477 deletions(-) delete mode 100644 app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js create mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/api.js create mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue create mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue create mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/constants.js create mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js rename {spec => ee/spec}/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js (81%) rename {spec => ee/spec}/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js (93%) rename {spec => ee/spec}/frontend/integrations/zentao/issues_show/mock_data.js (100%) delete mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap delete mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js delete mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js delete mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js delete mode 100644 spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js diff --git a/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js b/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js deleted file mode 100644 index 836f5c11c78701..00000000000000 --- a/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js +++ /dev/null @@ -1,26 +0,0 @@ -import ExternalIssueShowFactory from '~/integrations/external_issue/issues_show/external_issues_show_bundle'; -import { __, s__ } from '~/locale'; - -export default ExternalIssueShowFactory({ - failFetchingIssueText: s__( - 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', - ), - failFetchingIssueStatusText: s__( - 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', - ), - failUpdatingIssueLabelsText: s__( - 'ZenTaoIntegration|Failed to update ZenTao issue labels. View the issue in ZenTao, or reload the page.', - ), - failUpdatingIssueStatusText: s__( - 'ZenTaoIntegration|Failed to update ZenTao issue status. View the issue in ZenTao, or reload the page.', - ), - featureFlagCanEditLabelKey: 'zentaoIssueDetailsEditLabels', - featureFlagCanEditStatusKey: 'zentaoIssueDetailsEditStatus', - seeMoreDetailText: s__( - `ZenTaoIntegration|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}ZenTao%{linkEnd}.`, - ), - statusDropdownEmptyText: s__('ZenTaoIntegration|No available statuses'), - titleText: s__('ZenTaoIntegration|This issue is synchronized with ZenTao'), - userTypeText: __('ZenTao user'), - userTypeTooltipText: __('This is a ZenTao user.'), -}); diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js new file mode 100644 index 00000000000000..95fe28c90e2306 --- /dev/null +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js @@ -0,0 +1,37 @@ +import axios from '~/lib/utils/axios_utils'; + +export const fetchIssue = async (issuePath) => { + return axios.get(issuePath).then(({ 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, { labels = [], status = undefined }) => { + // We are using mock call here which should become a backend call + return new Promise((resolve) => { + setTimeout(() => { + const addedLabels = labels.filter((label) => label.set); + const removedLabelsIds = labels.filter((label) => !label.set).map((label) => label.id); + + const finalLabels = [...issue.labels, ...addedLabels].filter( + (label) => !removedLabelsIds.includes(label.id), + ); + + resolve({ + ...issue, + ...(status ? { status } : {}), + labels: finalLabels, + }); + }, 1000); + }); +}; diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue new file mode 100644 index 00000000000000..ef7c1f96ae177d --- /dev/null +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue @@ -0,0 +1,166 @@ + + + diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue new file mode 100644 index 00000000000000..d9ff755d98a84b --- /dev/null +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue @@ -0,0 +1,219 @@ + + + diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/constants.js b/ee/app/assets/javascripts/integrations/zentao/issues_show/constants.js new file mode 100644 index 00000000000000..29bb2dedfd2554 --- /dev/null +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/constants.js @@ -0,0 +1,13 @@ +import { __ } from '~/locale'; + +export const issueStates = { + OPENED: 'opened', + CLOSED: 'closed', +}; + +export const issueStateLabels = { + [issueStates.OPENED]: __('Open'), + [issueStates.CLOSED]: __('Closed'), +}; + +export const labelsFilterParam = 'labels'; diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js b/ee/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js new file mode 100644 index 00000000000000..54bdc4676b6d8a --- /dev/null +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/zentao_issues_show_bundle.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; + +import ZentaoIssuesShowApp from './components/zentao_issues_show_root.vue'; + +export default function initZentaoIssueShow({ mountPointSelector }) { + const mountPointEl = document.querySelector(mountPointSelector); + + if (!mountPointEl) { + return null; + } + + const { issueLabelsPath, issuesShowPath, issuesListPath } = mountPointEl.dataset; + + return new Vue({ + el: mountPointEl, + provide: { + issueLabelsPath, + issuesShowPath, + issuesListPath, + }, + render: (createElement) => createElement(ZentaoIssuesShowApp), + }); +} diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js b/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js similarity index 81% rename from spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js rename to ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js index 04ddf9922b0c18..ce17d22dca5391 100644 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js +++ b/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js @@ -1,8 +1,8 @@ import { shallowMount } from '@vue/test-utils'; -import Assignee from '~/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; -import Sidebar from '~/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue'; -import IssueDueDate from '~/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; -import IssueField from '~/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; +import Assignee from 'ee/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; +import IssueDueDate from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; +import IssueField from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; +import Sidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; import { mockZentaoIssue as mockZentaoIssueData, mockZentaoIssuesProvides } from '../../mock_data'; diff --git a/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js similarity index 93% rename from spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js rename to ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js index b542a3f5ae0628..e138e20dea6c3c 100644 --- a/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js +++ b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js @@ -1,12 +1,12 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import { issueStates } from 'ee/integrations/external_issue/issues_show/constants'; +import * as ZentaoIssuesShowApi from 'ee/integrations/zentao/issues_show/api'; +import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; +import ZentaoIssuesShow from 'ee/integrations/zentao/issues_show/components/zentao_issues_show_root.vue'; import waitForPromises from 'helpers/wait_for_promises'; -import * as ZentaoIssuesShowApi from '~/integrations/external_issue/issues_show/api'; -import ZentaoIssuesShow from '~/integrations/external_issue/issues_show/components/external_issues_show_root.vue'; -import ZentaoIssueSidebar from '~/integrations/external_issue/issues_show/components/sidebar/external_issues_sidebar_root.vue'; -import { issueStates } from '~/integrations/external_issue/issues_show/constants'; import IssuableHeader from '~/issuable_show/components/issuable_header.vue'; import IssuableShow from '~/issuable_show/components/issuable_show_root.vue'; import IssuableSidebar from '~/issuable_sidebar/components/issuable_sidebar_root.vue'; diff --git a/spec/frontend/integrations/zentao/issues_show/mock_data.js b/ee/spec/frontend/integrations/zentao/issues_show/mock_data.js similarity index 100% rename from spec/frontend/integrations/zentao/issues_show/mock_data.js rename to ee/spec/frontend/integrations/zentao/issues_show/mock_data.js diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap b/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap deleted file mode 100644 index 0c3b6bc14c977c..00000000000000 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/__snapshots__/assignee_spec.js.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ZentaoIssuesSidebarAssignee with assignee template renders avatar components 1`] = ` -
-
- - - - - -
- - -
-`; - -exports[`ZentaoIssuesSidebarAssignee with no assignee template renders template without avatar components (the "None" state) 1`] = ` -
-
- - - - None - -
- - -
-`; diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js deleted file mode 100644 index 1013cbbf025f81..00000000000000 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/assignee_spec.js +++ /dev/null @@ -1,124 +0,0 @@ -import { GlAvatarLabeled, GlAvatarLink, GlAvatar } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import Assignee from '~/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; - -import { mockZentaoIssue, mockZentaoIssuesProvides } from '../../mock_data'; - -const mockAssignee = convertObjectPropsToCamelCase(mockZentaoIssue.assignees[0], { deep: true }); - -describe('ZentaoIssuesSidebarAssignee', () => { - let wrapper; - - const findNoAssigneeText = () => wrapper.findByTestId('no-assignee-text'); - const findNoAssigneeIcon = () => wrapper.findByTestId('no-assignee-text'); - const findAvatar = () => wrapper.find(GlAvatar); - const findAvatarLabeled = () => wrapper.find(GlAvatarLabeled); - const findAvatarLink = () => wrapper.find(GlAvatarLink); - const findSidebarCollapsedIconWrapper = () => - wrapper.findByTestId('sidebar-collapsed-icon-wrapper'); - - const createComponent = ({ assignee } = {}) => { - wrapper = extendedWrapper( - shallowMount(Assignee, { - propsData: { - assignee, - }, - provide: { - userTypeText: mockZentaoIssuesProvides.userTypeText, - }, - }), - ); - }; - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - wrapper = null; - } - }); - - describe('with assignee', () => { - beforeEach(() => { - createComponent({ assignee: mockAssignee }); - }); - - describe('template', () => { - it('renders avatar components', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('renders GlAvatarLink with correct props', () => { - const avatarLink = findAvatarLink(); - - expect(avatarLink.exists()).toBe(true); - expect(avatarLink.attributes()).toMatchObject({ - href: mockAssignee.webUrl, - title: mockAssignee.name, - }); - }); - - it('renders GlAvatarLabeled with correct props', () => { - const avatarLabeled = findAvatarLabeled(); - - expect(avatarLabeled.exists()).toBe(true); - expect(avatarLabeled.attributes()).toMatchObject({ - src: mockAssignee.avatarUrl, - alt: mockAssignee.name, - 'entity-name': mockAssignee.name, - }); - expect(avatarLabeled.props('label')).toBe(mockAssignee.name); - }); - - it('renders GlAvatar with correct props', () => { - const avatar = findAvatar(); - - expect(avatar.exists()).toBe(true); - expect(avatar.attributes()).toMatchObject({ - src: mockAssignee.avatarUrl, - alt: mockAssignee.name, - }); - expect(avatar.props('entityName')).toBe(mockAssignee.name); - }); - - it('renders AssigneeTitle with correct props', () => { - const title = wrapper.find(AssigneeTitle); - - expect(title.exists()).toBe(true); - expect(title.props('numberOfAssignees')).toBe(1); - }); - - it('does not render "No assignee" text', () => { - expect(findNoAssigneeText().exists()).toBe(false); - }); - - it('does not render "No assignee" icon', () => { - expect(findNoAssigneeIcon().exists()).toBe(false); - }); - - it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { - const iconWrapper = findSidebarCollapsedIconWrapper(); - expect(iconWrapper.attributes('title')).toBe(mockAssignee.name); - }); - }); - }); - - describe('with no assignee', () => { - beforeEach(() => { - createComponent({ assignee: undefined }); - }); - - describe('template', () => { - it('renders template without avatar components (the "None" state)', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('sets `title` attribute of collapsed sidebar wrapper correctly', () => { - const iconWrapper = findSidebarCollapsedIconWrapper(); - expect(iconWrapper.attributes('title')).toBe('No assignee'); - }); - }); - }); -}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js deleted file mode 100644 index 6281972c1f245a..00000000000000 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_due_date_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; - -import { useFakeDate } from 'helpers/fake_date'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; - -import IssueDueDate from '~/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; - -describe('IssueDueDate', () => { - let wrapper; - - const createComponent = ({ props = {} } = {}) => { - wrapper = extendedWrapper( - shallowMount(IssueDueDate, { - propsData: props, - }), - ); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findDueDateCollapsed = () => wrapper.findByTestId('due-date-collapsed'); - const findDueDateValue = () => wrapper.findByTestId('due-date-value'); - - describe('when dueDate is null', () => { - it('renders "None" as value', () => { - createComponent(); - - expect(findDueDateCollapsed().text()).toBe('None'); - expect(findDueDateValue().text()).toBe('None'); - }); - }); - - describe('when dueDate is in the past', () => { - const dueDate = '2021-02-14T00:00:00.000Z'; - - useFakeDate(2021, 2, 18); - - it('renders formatted dueDate', () => { - createComponent({ - props: { - dueDate, - }, - }); - - expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); - expect(findDueDateValue().text()).toBe('Feb 14, 2021 (Past due)'); - }); - }); - - describe('when dueDate is in the future', () => { - const dueDate = '2021-02-14T00:00:00.000Z'; - - useFakeDate(2020, 12, 20); - - it('renders formatted dueDate', () => { - createComponent({ - props: { - dueDate, - }, - }); - - expect(findDueDateCollapsed().text()).toBe('Feb 14, 2021'); - expect(findDueDateValue().text()).toBe('Feb 14, 2021'); - }); - }); -}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js deleted file mode 100644 index c1fbeb062aa36d..00000000000000 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_dropdown_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; - -import IssueFieldDropdown from '~/integrations/external_issue/issues_show/components/sidebar/issue_field_dropdown.vue'; - -import { mockZentaoIssueStatuses } 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} | ${mockZentaoIssueStatuses} - ${false} | ${[]} - ${false} | ${mockZentaoIssueStatuses} - `('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(mockZentaoIssueStatuses[index].title); - }); - } else { - expect(wrapper.text()).toBe(emptyText); - } - } - }); -}); diff --git a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js b/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js deleted file mode 100644 index 99a300ecd6af2a..00000000000000 --- a/spec/frontend/integrations/zentao/issues_show/components/sidebar/issue_field_spec.js +++ /dev/null @@ -1,117 +0,0 @@ -import { GlButton, GlIcon } from '@gitlab/ui'; - -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; - -import IssueField from '~/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; - -import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; - -describe('IssueField', () => { - let wrapper; - - const defaultProps = { - icon: 'calendar', - title: 'Field Title', - }; - - const createComponent = ({ props = {} } = {}) => { - wrapper = shallowMountExtended(IssueField, { - directives: { - GlTooltip: createMockDirective(), - }, - propsData: { ...defaultProps, ...props }, - stubs: { - SidebarEditableItem, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); - const findEditButton = () => wrapper.findComponent(GlButton); - const findFieldCollapsed = () => wrapper.findByTestId('field-collapsed'); - const findFieldCollapsedTooltip = () => getBinding(findFieldCollapsed().element, 'gl-tooltip'); - const findFieldValue = () => wrapper.findByTestId('field-value'); - const findGlIcon = () => wrapper.findComponent(GlIcon); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders title', () => { - expect(findEditableItem().props('title')).toBe(defaultProps.title); - }); - - it('renders GlIcon (when collapsed)', () => { - expect(findGlIcon().props('name')).toBe(defaultProps.icon); - }); - - it('does not render "Edit" button', () => { - expect(findEditButton().exists()).toBe(false); - }); - }); - - describe('without value prop', () => { - beforeEach(() => { - createComponent(); - }); - - it('falls back to "None"', () => { - expect(findFieldValue().text()).toBe('None'); - }); - - it('renders tooltip (when collapsed) with "value" = title', () => { - const tooltip = findFieldCollapsedTooltip(); - - expect(tooltip).toBeDefined(); - expect(tooltip.value.title).toBe(defaultProps.title); - }); - }); - - describe('with value prop', () => { - const value = 'field value'; - - beforeEach(() => { - createComponent({ - props: { value }, - }); - }); - - it('renders the value', () => { - expect(findFieldValue().text()).toBe(value); - }); - - it('renders tooltip (when collapsed) with "value" = value', () => { - const tooltip = findFieldCollapsedTooltip(); - - expect(tooltip).toBeDefined(); - 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); - }); - }); -}); -- GitLab From d238c6c0d2c77a034a77d9744b70da95588d9afc Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Tue, 28 Sep 2021 14:15:38 +0800 Subject: [PATCH 06/17] Add ZenTao init entry --- .../pages/projects/integrations/zentao/issues/show/index.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ee/app/assets/javascripts/pages/projects/integrations/zentao/issues/show/index.js diff --git a/ee/app/assets/javascripts/pages/projects/integrations/zentao/issues/show/index.js b/ee/app/assets/javascripts/pages/projects/integrations/zentao/issues/show/index.js new file mode 100644 index 00000000000000..cf7aad55f0654a --- /dev/null +++ b/ee/app/assets/javascripts/pages/projects/integrations/zentao/issues/show/index.js @@ -0,0 +1,4 @@ +import initZentaoIssueShow from 'ee/integrations/zentao/issues_show/zentao_issues_show_bundle'; +// import initJiraIssueShow from 'ee/integrations/jira/issues_show/jira_issues_show_bundle'; + +initZentaoIssueShow({ mountPointSelector: '.js-zentao-issues-show-app' }); -- GitLab From 85169c6cea0c49991d86450a6dc4178f7ae0fe14 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 30 Sep 2021 10:40:51 +0800 Subject: [PATCH 07/17] Remove unnecessary update issue part --- .../integrations/zentao/issues_show/api.js | 20 --------- .../sidebar/zentao_issues_sidebar_root.vue | 22 ++-------- .../components/zentao_issues_show_root.vue | 44 +------------------ .../zentao_issues_sidebar_root_spec.js | 6 +-- .../zentao_issues_show_root_spec.js | 34 +------------- 5 files changed, 9 insertions(+), 117 deletions(-) diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js index 95fe28c90e2306..b7382ee6c8f36f 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js @@ -15,23 +15,3 @@ export const fetchIssueStatuses = () => { }, 1000); }); }; - -export const updateIssue = (issue, { labels = [], status = undefined }) => { - // We are using mock call here which should become a backend call - return new Promise((resolve) => { - setTimeout(() => { - const addedLabels = labels.filter((label) => label.set); - const removedLabelsIds = labels.filter((label) => !label.set).map((label) => label.id); - - const finalLabels = [...issue.labels, ...addedLabels].filter( - (label) => !removedLabelsIds.includes(label.id), - ); - - resolve({ - ...issue, - ...(status ? { status } : {}), - labels: finalLabels, - }); - }, 1000); - }); -}; diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue index ef7c1f96ae177d..8adfa53946a0d0 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue @@ -1,7 +1,7 @@ @@ -132,7 +120,6 @@ export default { {{ __('None') }} diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue index d9ff755d98a84b..dec9ee7d49d23b 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue @@ -8,12 +8,8 @@ import { GlTooltipDirective as GlTooltip, } from '@gitlab/ui'; -import Note from 'ee/integrations/external_issue/issues_show/components/note.vue'; -import { - fetchIssue, - fetchIssueStatuses, - updateIssue, -} from 'ee/integrations/zentao/issues_show/api'; +import Note from 'ee/external_issues_show/components/note.vue'; +import { fetchIssue, fetchIssueStatuses } from 'ee/integrations/zentao/issues_show/api'; import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import { issueStates, issueStateLabels } from 'ee/integrations/zentao/issues_show/constants'; import createFlash from '~/flash'; @@ -90,23 +86,6 @@ export default { return `external_note_${id}`; }, - onIssueLabelsUpdated(labels) { - this.isUpdatingLabels = true; - updateIssue(this.issue, { labels }) - .then((response) => { - this.issue.labels = response.labels; - }) - .catch(() => { - createFlash({ - message: s__( - 'ZenTaoIntegration|Failed to update ZenTao issue labels. View the issue in ZenTao, or reload the page.', - ), - }); - }) - .finally(() => { - this.isUpdatingLabels = false; - }); - }, onIssueStatusFetch() { this.isLoadingStatus = true; fetchIssueStatuses() @@ -124,23 +103,6 @@ export default { this.isLoadingStatus = false; }); }, - onIssueStatusUpdated(status) { - this.isUpdatingStatus = true; - updateIssue(this.issue, { status }) - .then((response) => { - this.issue.status = response.status; - }) - .catch(() => { - createFlash({ - message: s__( - 'ZenTaoIntegration|Failed to update ZenTao issue status. View the issue in ZenTao, or reload the page.', - ), - }); - }) - .finally(() => { - this.isUpdatingStatus = false; - }); - }, }, }; @@ -187,9 +149,7 @@ export default { :is-updating-labels="isUpdatingLabels" :is-updating-status="isUpdatingStatus" :statuses="statuses" - @issue-labels-updated="onIssueLabelsUpdated" @issue-status-fetch="onIssueStatusFetch" - @issue-status-updated="onIssueStatusUpdated" @sidebar-toggle="toggleSidebar" /> diff --git a/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js b/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js index ce17d22dca5391..8ffef958c8fc5f 100644 --- a/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js +++ b/ee/spec/frontend/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; -import Assignee from 'ee/integrations/external_issue/issues_show/components/sidebar/assignee.vue'; -import IssueDueDate from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_due_date.vue'; -import IssueField from 'ee/integrations/external_issue/issues_show/components/sidebar/issue_field.vue'; +import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; +import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; +import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; import Sidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; diff --git a/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js index e138e20dea6c3c..bc2745f1f3bed9 100644 --- a/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js +++ b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js @@ -1,7 +1,7 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { issueStates } from 'ee/integrations/external_issue/issues_show/constants'; +import { issueStates } from 'ee/external_issues_show/constants'; import * as ZentaoIssuesShowApi from 'ee/integrations/zentao/issues_show/api'; import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import ZentaoIssuesShow from 'ee/integrations/zentao/issues_show/components/zentao_issues_show_root.vue'; @@ -124,22 +124,6 @@ describe('ZentaoIssuesShow', () => { await waitForPromises(); }); - it('updates issue labels on issue-labels-updated', async () => { - const updateIssueSpy = jest.spyOn(ZentaoIssuesShowApi, 'updateIssue').mockResolvedValue(); - - const labels = [{ id: 'ecosystem' }]; - - findZentaoIssueSidebar().vm.$emit('issue-labels-updated', labels); - await wrapper.vm.$nextTick(); - - expect(updateIssueSpy).toHaveBeenCalledWith(expect.any(Object), { labels }); - expect(findZentaoIssueSidebar().props('isUpdatingLabels')).toBe(true); - - await waitForPromises(); - - expect(findZentaoIssueSidebar().props('isUpdatingLabels')).toBe(false); - }); - it('fetches issue statuses on issue-status-fetch', async () => { const fetchIssueStatusesSpy = jest .spyOn(ZentaoIssuesShowApi, 'fetchIssueStatuses') @@ -156,22 +140,6 @@ describe('ZentaoIssuesShow', () => { expect(findZentaoIssueSidebar().props('isLoadingStatus')).toBe(false); }); - it('updates issue status on issue-status-updated', async () => { - const updateIssueSpy = jest.spyOn(ZentaoIssuesShowApi, 'updateIssue').mockResolvedValue(); - - const status = 'In Review'; - - findZentaoIssueSidebar().vm.$emit('issue-status-updated', status); - await wrapper.vm.$nextTick(); - - expect(updateIssueSpy).toHaveBeenCalledWith(expect.any(Object), { status }); - expect(findZentaoIssueSidebar().props('isUpdatingStatus')).toBe(true); - - await waitForPromises(); - - expect(findZentaoIssueSidebar().props('isUpdatingStatus')).toBe(false); - }); - it('updates `sidebarExpanded` prop on `sidebar-toggle` event', async () => { const zentaoIssueSidebar = findZentaoIssueSidebar(); expect(zentaoIssueSidebar.props('sidebarExpanded')).toBe(true); -- GitLab From 5adf432d6121090a2da827d55295f4c8970c8e62 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Thu, 30 Sep 2021 14:35:26 +0800 Subject: [PATCH 08/17] Remove unused code --- .../sidebar/zentao_issues_sidebar_root.vue | 35 ------------------- .../components/zentao_issues_show_root.vue | 26 +------------- .../zentao_issues_show_root_spec.js | 17 --------- 3 files changed, 1 insertion(+), 77 deletions(-) diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue index 8adfa53946a0d0..af3c54dc7454cf 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue @@ -82,34 +82,6 @@ export default { toggleSidebar() { this.$emit('sidebar-toggle'); }, - afterSidebarTransitioned(callback) { - // Wait for sidebar expand animation to complete - this.sidebarEl.addEventListener('transitionend', callback, { once: true }); - }, - expandSidebarAndOpenDropdown(dropdownRef = null) { - // Expand the sidebar if not already expanded. - if (!this.sidebarExpanded) { - this.toggleSidebar(); - } - - if (dropdownRef) { - this.afterSidebarTransitioned(() => { - dropdownRef.expand(); - }); - } - }, - onIssueLabelsClose() { - this.isEditingLabels = false; - }, - onIssueLabelsToggle() { - this.expandSidebarAndOpenDropdown(); - this.afterSidebarTransitioned(() => { - this.isEditingLabels = true; - }); - }, - onIssueStatusFetch() { - this.$emit('issue-status-fetch'); - }, }, }; @@ -125,10 +97,7 @@ export default { :items="statuses" :loading="isLoadingStatus" :title="$options.i18n.statusTitle" - :updating="isUpdatingStatus" :value="issue.status" - @expand-sidebar="expandSidebarAndOpenDropdown" - @issue-field-fetch="onIssueStatusFetch" /> {{ __('None') }} diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue index dec9ee7d49d23b..dd0ca8f198ac8c 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue @@ -9,10 +9,9 @@ import { } from '@gitlab/ui'; import Note from 'ee/external_issues_show/components/note.vue'; -import { fetchIssue, fetchIssueStatuses } from 'ee/integrations/zentao/issues_show/api'; +import { fetchIssue } from 'ee/integrations/zentao/issues_show/api'; import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import { issueStates, issueStateLabels } from 'ee/integrations/zentao/issues_show/constants'; -import createFlash from '~/flash'; import IssuableShow from '~/issuable_show/components/issuable_show_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -42,8 +41,6 @@ export default { return { isLoading: true, isLoadingStatus: false, - isUpdatingLabels: false, - isUpdatingStatus: false, errorMessage: null, issue: {}, statuses: [], @@ -85,24 +82,6 @@ export default { externalIssueCommentId(id) { return `external_note_${id}`; }, - - onIssueStatusFetch() { - this.isLoadingStatus = true; - fetchIssueStatuses() - .then((response) => { - this.statuses = response; - }) - .catch(() => { - createFlash({ - message: s__( - 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', - ), - }); - }) - .finally(() => { - this.isLoadingStatus = false; - }); - }, }, }; @@ -146,10 +125,7 @@ export default { :sidebar-expanded="sidebarExpanded" :issue="issue" :is-loading-status="isLoadingStatus" - :is-updating-labels="isUpdatingLabels" - :is-updating-status="isUpdatingStatus" :statuses="statuses" - @issue-status-fetch="onIssueStatusFetch" @sidebar-toggle="toggleSidebar" /> diff --git a/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js index bc2745f1f3bed9..3f15c5defbc749 100644 --- a/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js +++ b/ee/spec/frontend/integrations/zentao/issues_show/components/zentao_issues_show_root_spec.js @@ -2,7 +2,6 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { issueStates } from 'ee/external_issues_show/constants'; -import * as ZentaoIssuesShowApi from 'ee/integrations/zentao/issues_show/api'; import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; import ZentaoIssuesShow from 'ee/integrations/zentao/issues_show/components/zentao_issues_show_root.vue'; @@ -124,22 +123,6 @@ describe('ZentaoIssuesShow', () => { await waitForPromises(); }); - it('fetches issue statuses on issue-status-fetch', async () => { - const fetchIssueStatusesSpy = jest - .spyOn(ZentaoIssuesShowApi, 'fetchIssueStatuses') - .mockResolvedValue(); - - findZentaoIssueSidebar().vm.$emit('issue-status-fetch'); - await wrapper.vm.$nextTick(); - - expect(fetchIssueStatusesSpy).toHaveBeenCalled(); - expect(findZentaoIssueSidebar().props('isLoadingStatus')).toBe(true); - - await waitForPromises(); - - expect(findZentaoIssueSidebar().props('isLoadingStatus')).toBe(false); - }); - it('updates `sidebarExpanded` prop on `sidebar-toggle` event', async () => { const zentaoIssueSidebar = findZentaoIssueSidebar(); expect(zentaoIssueSidebar.props('sidebarExpanded')).toBe(true); -- GitLab From 088f92d7c4dff32b5b48e86af93064cbd7271746 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Fri, 8 Oct 2021 09:38:32 +0800 Subject: [PATCH 09/17] Clean up ZenTao integration --- .../components/jira_issues_show_root.vue | 2 +- .../sidebar/jira_issues_sidebar_root.vue | 3 +- .../jira/issues_show/constants.js | 13 ---- .../integrations/zentao/issues_show/api.js | 10 --- .../sidebar/zentao_issues_sidebar_root.vue | 67 +------------------ .../components/zentao_issues_show_root.vue | 28 +++----- .../zentao/issues_show/constants.js | 13 ---- .../issues_show/zentao_issues_show_bundle.js | 3 +- .../components/jira_issues_show_root_spec.js | 2 +- .../zentao_issues_sidebar_root_spec.js | 3 +- .../zentao_issues_show_root_spec.js | 31 +-------- .../zentao/issues_show/mock_data.js | 28 -------- locale/gitlab.pot | 17 ++--- 13 files changed, 25 insertions(+), 195 deletions(-) delete mode 100644 ee/app/assets/javascripts/integrations/jira/issues_show/constants.js delete mode 100644 ee/app/assets/javascripts/integrations/zentao/issues_show/constants.js diff --git a/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue b/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue index bec8bd9a5153ee..ba78ba0ba1e0ce 100644 --- a/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue +++ b/ee/app/assets/javascripts/integrations/jira/issues_show/components/jira_issues_show_root.vue @@ -11,7 +11,7 @@ import Note from 'ee/external_issues_show/components/note.vue'; 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 { issueStates, issueStateLabels } from 'ee/integrations/jira/issues_show/constants'; +import { issueStates, issueStateLabels } from 'ee/external_issues_show/constants'; import createFlash from '~/flash'; import IssuableShow from '~/issuable_show/components/issuable_show_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; diff --git a/ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue b/ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue index bdc2b28e1a70dd..f2604d34c5ebda 100644 --- a/ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue +++ b/ee/app/assets/javascripts/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue @@ -2,7 +2,8 @@ import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; -import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants'; +import { labelsFilterParam } from 'ee/external_issues_show/constants'; + import { __, s__ } from '~/locale'; import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; diff --git a/ee/app/assets/javascripts/integrations/jira/issues_show/constants.js b/ee/app/assets/javascripts/integrations/jira/issues_show/constants.js deleted file mode 100644 index 29bb2dedfd2554..00000000000000 --- a/ee/app/assets/javascripts/integrations/jira/issues_show/constants.js +++ /dev/null @@ -1,13 +0,0 @@ -import { __ } from '~/locale'; - -export const issueStates = { - OPENED: 'opened', - CLOSED: 'closed', -}; - -export const issueStateLabels = { - [issueStates.OPENED]: __('Open'), - [issueStates.CLOSED]: __('Closed'), -}; - -export const labelsFilterParam = 'labels'; diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js index b7382ee6c8f36f..e49de73c06f326 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/api.js @@ -5,13 +5,3 @@ export const fetchIssue = async (issuePath) => { 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); - }); -}; diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue index af3c54dc7454cf..b90f243c2cb900 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue @@ -2,10 +2,8 @@ import Assignee from 'ee/external_issues_show/components/sidebar/assignee.vue'; import IssueDueDate from 'ee/external_issues_show/components/sidebar/issue_due_date.vue'; import IssueField from 'ee/external_issues_show/components/sidebar/issue_field.vue'; -import { labelsFilterParam } from 'ee/integrations/zentao/issues_show/constants'; -import { __, s__ } from '~/locale'; +import { __ } from '~/locale'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { name: 'ZentaoIssuesSidebar', @@ -15,49 +13,11 @@ export default { IssueField, LabelsSelect, }, - mixins: [glFeatureFlagsMixin()], - inject: { - issueLabelsPath: { - default: null, - }, - issuesListPath: { - default: null, - }, - }, props: { - sidebarExpanded: { - type: Boolean, - required: true, - }, issue: { type: Object, required: true, }, - isLoadingStatus: { - type: Boolean, - required: false, - default: false, - }, - isUpdatingLabels: { - type: Boolean, - required: false, - default: false, - }, - isUpdatingStatus: { - type: Boolean, - required: false, - default: false, - }, - statuses: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - return { - isEditingLabels: false, - }; }, computed: { assignee() { @@ -68,21 +28,10 @@ export default { return this.issue.references?.relative; }, }, - labelsFilterParam, i18n: { statusTitle: __('Status'), - statusDropdownEmpty: s__('ZenTaoIntegration|No available statuses'), - statusDropdownTitle: __('Change status'), referenceName: __('Reference'), }, - mounted() { - this.sidebarEl = document.querySelector('aside.right-sidebar'); - }, - methods: { - toggleSidebar() { - this.$emit('sidebar-toggle'); - }, - }, }; @@ -90,22 +39,10 @@ export default {
- + diff --git a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue index dd0ca8f198ac8c..4927877df3a5f2 100644 --- a/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue +++ b/ee/app/assets/javascripts/integrations/zentao/issues_show/components/zentao_issues_show_root.vue @@ -11,7 +11,7 @@ import { import Note from 'ee/external_issues_show/components/note.vue'; import { fetchIssue } from 'ee/integrations/zentao/issues_show/api'; import ZentaoIssueSidebar from 'ee/integrations/zentao/issues_show/components/sidebar/zentao_issues_sidebar_root.vue'; -import { issueStates, issueStateLabels } from 'ee/integrations/zentao/issues_show/constants'; +import { issueStates, issueStateLabels } from 'ee/external_issues_show/constants'; import IssuableShow from '~/issuable_show/components/issuable_show_root.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -40,10 +40,8 @@ export default { data() { return { isLoading: true, - isLoadingStatus: false, errorMessage: null, issue: {}, - statuses: [], }; }, computed: { @@ -70,19 +68,21 @@ export default { this.issue = convertObjectPropsToCamelCase(issue, { deep: true }); }) .catch(() => { - this.errorMessage = s__( - 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', - ); + this.errorMessage = this.$options.i18n.defaultErrorMessage; }) .finally(() => { this.isLoading = false; }); }, - externalIssueCommentId(id) { return `external_note_${id}`; }, }, + i18n: { + defaultErrorMessage: s__( + 'ZenTaoIntegration|Failed to load ZenTao issue. View the issue in ZenTao, or reload the page.', + ), + }, }; @@ -120,14 +120,8 @@ export default { > -