From 5abfcf0cabaa93ea84b743962984b45888336083 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Tue, 29 Mar 2022 11:42:21 +0000 Subject: [PATCH 1/2] Implement detailed github status reporting * expose github stats data to user Changelog: added --- .../components/import_status.vue | 133 +++++++++++++++- .../javascripts/import_entities/constants.js | 41 ----- .../components/provider_repo_table_row.vue | 14 +- .../import_projects/store/mutations.js | 6 +- .../stylesheets/page_bundles/import.scss | 10 +- locale/gitlab.pot | 18 +++ .../components/import_status_spec.js | 145 ++++++++++++++++++ .../provider_repo_table_row_spec.js | 7 + .../import_projects/store/mutations_spec.js | 29 ++++ 9 files changed, 349 insertions(+), 54 deletions(-) create mode 100644 spec/frontend/import_entities/components/import_status_spec.js diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue index cc6a057f58758d..fc993e846ec8e9 100644 --- a/app/assets/javascripts/import_entities/components/import_status.vue +++ b/app/assets/javascripts/import_entities/components/import_status.vue @@ -1,10 +1,66 @@ diff --git a/app/assets/javascripts/import_entities/constants.js b/app/assets/javascripts/import_entities/constants.js index 1ab5413a5cc2fe..20a4d2d84b4db9 100644 --- a/app/assets/javascripts/import_entities/constants.js +++ b/app/assets/javascripts/import_entities/constants.js @@ -1,5 +1,3 @@ -import { __ } from '~/locale'; - // The `scheduling` status is only present on the client-side, // it is used as the status when we are requesting to start an import. @@ -13,42 +11,3 @@ export const STATUSES = { SCHEDULING: 'scheduling', CANCELLED: 'cancelled', }; - -const SCHEDULED_STATUS = { - icon: 'status-scheduled', - text: __('Pending'), - iconClass: 'gl-text-orange-400', -}; - -const STATUS_MAP = { - [STATUSES.NONE]: { - icon: 'status-waiting', - text: __('Not started'), - iconClass: 'gl-text-gray-400', - }, - [STATUSES.SCHEDULING]: SCHEDULED_STATUS, - [STATUSES.SCHEDULED]: SCHEDULED_STATUS, - [STATUSES.CREATED]: SCHEDULED_STATUS, - [STATUSES.STARTED]: { - icon: 'status-running', - text: __('Importing...'), - iconClass: 'gl-text-blue-400', - }, - [STATUSES.FINISHED]: { - icon: 'status-success', - text: __('Complete'), - iconClass: 'gl-text-green-400', - }, - [STATUSES.FAILED]: { - icon: 'status-failed', - text: __('Failed'), - iconClass: 'gl-text-red-600', - }, - [STATUSES.CANCELLED]: { - icon: 'status-stopped', - text: __('Cancelled'), - iconClass: 'gl-text-red-600', - }, -}; - -export default STATUS_MAP; diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue index c3d0ca4ed8ca9b..e4090a378e1c5b 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue @@ -69,6 +69,10 @@ export default { return getImportStatus(this.repo); }, + stats() { + return this.repo.importedProject?.stats; + }, + importTarget() { return this.getImportTarget(this.repo.importSource.id); }, @@ -101,11 +105,11 @@ export default { - - + + - + { const repo = state.repositories.find((p) => p.importedProject?.id === updatedProject.id); if (repo?.importedProject) { - repo.importedProject.importStatus = updatedProject.importStatus; + repo.importedProject = { + ...repo.importedProject, + stats: updatedProject.stats, + importStatus: updatedProject.importStatus, + }; } }); }, diff --git a/app/assets/stylesheets/page_bundles/import.scss b/app/assets/stylesheets/page_bundles/import.scss index b7a4d9564fe155..cd5e6d32e4e72d 100644 --- a/app/assets/stylesheets/page_bundles/import.scss +++ b/app/assets/stylesheets/page_bundles/import.scss @@ -1,18 +1,22 @@ @import 'mixins_and_variables_and_functions'; +.import-jobs-from-col { + width: 37%; +} + + .import-jobs-to-col { - width: 39%; + width: 37%; } .import-jobs-status-col { - width: 15%; + width: 25%; } .import-jobs-cta-col { width: 1%; } - .import-entities-target-select { &.disabled { .import-entities-target-select-separator { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 632169b328941b..1c7fe5e3ba11a1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13016,6 +13016,9 @@ msgstr "" msgid "Diff limits" msgstr "" +msgid "Diff notes" +msgstr "" + msgid "Difference between start date and now" msgstr "" @@ -17085,6 +17088,15 @@ msgstr "" msgid "Gitea Import" msgstr "" +msgid "GithubImporter|PR mergers" +msgstr "" + +msgid "GithubImporter|PR reviews" +msgstr "" + +msgid "GithubImporter|Pull requests" +msgstr "" + msgid "GithubIntegration|Create a %{token_link_start}personal access token%{token_link_end} with %{status_html} access granted and paste it here." msgstr "" @@ -25642,6 +25654,9 @@ msgstr "" msgid "NoteForm|Note" msgstr "" +msgid "Notes" +msgstr "" + msgid "Notes rate limit" msgstr "" @@ -27010,6 +27025,9 @@ msgstr "" msgid "Part of merge request changes" msgstr "" +msgid "Partial import" +msgstr "" + msgid "Participants" msgstr "" diff --git a/spec/frontend/import_entities/components/import_status_spec.js b/spec/frontend/import_entities/components/import_status_spec.js new file mode 100644 index 00000000000000..686a21e3923bfb --- /dev/null +++ b/spec/frontend/import_entities/components/import_status_spec.js @@ -0,0 +1,145 @@ +import { GlAccordionItem, GlBadge, GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import ImportStatus from '~/import_entities/components/import_status.vue'; +import { STATUSES } from '~/import_entities/constants'; + +describe('Import entities status component', () => { + let wrapper; + + const createComponent = (propsData) => { + wrapper = shallowMount(ImportStatus, { + propsData, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('success status', () => { + const getStatusText = () => wrapper.findComponent(GlBadge).text(); + + it('displays finished status as complete when no stats are provided', () => { + createComponent({ + status: STATUSES.FINISHED, + }); + expect(getStatusText()).toBe('Complete'); + }); + + it('displays finished status as complete when all stats items were processed', () => { + const statItems = { label: 100, note: 200 }; + + createComponent({ + status: STATUSES.FINISHED, + stats: { + fetched: { ...statItems }, + imported: { ...statItems }, + }, + }); + + expect(getStatusText()).toBe('Complete'); + }); + + it('displays finished status as partial when all stats items were processed', () => { + const statItems = { label: 100, note: 200 }; + + createComponent({ + status: STATUSES.FINISHED, + stats: { + fetched: { ...statItems }, + imported: { ...statItems, label: 50 }, + }, + }); + + expect(getStatusText()).toBe('Partial import'); + }); + }); + + describe('details drawer', () => { + const findDetailsDrawer = () => wrapper.findComponent(GlAccordionItem); + + it('renders details drawer to be present when stats are provided', () => { + createComponent({ + status: 'created', + stats: { fetched: { label: 1 }, imported: { label: 0 } }, + }); + + expect(findDetailsDrawer().exists()).toBe(true); + }); + + it('does not render details drawer when no stats are provided', () => { + createComponent({ + status: 'created', + }); + + expect(findDetailsDrawer().exists()).toBe(false); + }); + + it('does not render details drawer when stats are empty', () => { + createComponent({ + status: 'created', + stats: { fetched: {}, imported: {} }, + }); + + expect(findDetailsDrawer().exists()).toBe(false); + }); + + it('does not render details drawer when no known stats are provided', () => { + createComponent({ + status: 'created', + stats: { + fetched: { + UNKNOWN_STAT: 100, + }, + imported: { + UNKNOWN_STAT: 0, + }, + }, + }); + + expect(findDetailsDrawer().exists()).toBe(false); + }); + }); + + describe('stats display', () => { + const getStatusIcon = () => + wrapper.findComponent(GlAccordionItem).findComponent(GlIcon).props().name; + + const createComponentWithStats = ({ fetched, imported }) => { + createComponent({ + status: 'created', + stats: { + fetched: { label: fetched }, + imported: { label: imported }, + }, + }); + }; + + it('displays scheduled status when imported is 0', () => { + createComponentWithStats({ + fetched: 100, + imported: 0, + }); + + expect(getStatusIcon()).toBe('status-scheduled'); + }); + + it('displays running status when imported is not equal to fetched', () => { + createComponentWithStats({ + fetched: 100, + imported: 10, + }); + + expect(getStatusIcon()).toBe('status-running'); + }); + + it('displays success status when imported is equal to fetched', () => { + createComponentWithStats({ + fetched: 100, + imported: 100, + }); + + expect(getStatusIcon()).toBe('status-success'); + }); + }); +}); diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js index c8afa9ea57d9a4..41a005199e12fe 100644 --- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js +++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js @@ -98,6 +98,8 @@ describe('ProviderRepoTableRow', () => { }); describe('when rendering imported project', () => { + const FAKE_STATS = {}; + const repo = { importSource: { id: 'remote-1', @@ -109,6 +111,7 @@ describe('ProviderRepoTableRow', () => { fullPath: 'fullPath', importSource: 'importSource', importStatus: STATUSES.FINISHED, + stats: FAKE_STATS, }, }; @@ -134,6 +137,10 @@ describe('ProviderRepoTableRow', () => { it('does not render import button', () => { expect(findImportButton().exists()).toBe(false); }); + + it('passes stats to import status component', () => { + expect(wrapper.find(ImportStatus).props().stats).toBe(FAKE_STATS); + }); }); describe('when rendering incompatible project', () => { diff --git a/spec/frontend/import_entities/import_projects/store/mutations_spec.js b/spec/frontend/import_entities/import_projects/store/mutations_spec.js index e062d889325c91..77fae951300e09 100644 --- a/spec/frontend/import_entities/import_projects/store/mutations_spec.js +++ b/spec/frontend/import_entities/import_projects/store/mutations_spec.js @@ -232,6 +232,35 @@ describe('import_projects store mutations', () => { updatedProjects[0].importStatus, ); }); + + it('updates import stats of project', () => { + const repoId = 1; + state = { + repositories: [ + { importedProject: { id: repoId, stats: {} }, importStatus: STATUSES.STARTED }, + ], + }; + const newStats = { + fetched: { + label: 10, + }, + imported: { + label: 1, + }, + }; + + const updatedProjects = [ + { + id: repoId, + importStatus: STATUSES.FINISHED, + stats: newStats, + }, + ]; + + mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects); + + expect(state.repositories[0].importedProject.stats).toStrictEqual(newStats); + }); }); describe(`${types.REQUEST_NAMESPACES}`, () => { -- GitLab From b07892069b355c26c836a6b59e54ecc153707f19 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Wed, 6 Apr 2022 21:00:24 +0300 Subject: [PATCH 2/2] Improve default value for stats prop --- .../javascripts/import_entities/components/import_status.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue index fc993e846ec8e9..9262a4e1e95705 100644 --- a/app/assets/javascripts/import_entities/components/import_status.vue +++ b/app/assets/javascripts/import_entities/components/import_status.vue @@ -71,7 +71,7 @@ export default { stats: { type: Object, required: false, - default: null, + default: () => ({ fetched: {}, imported: {} }), }, }, -- GitLab