diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 29d37cd8c99..99b1a73b983 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -7,7 +7,10 @@ import { GlBadge, GlButton, GlTooltipDirective, + GlEmptyState, } from '@gitlab/ui'; +import noAccessSvg from '@gitlab/svgs/dist/illustrations/analytics/no-access.svg'; +import { s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue'; @@ -50,6 +53,7 @@ export default { GlLoadingIcon, GlSkeletonLoader, GlIcon, + GlEmptyState, WorkItemAssignees, WorkItemActions, WorkItemDescription, @@ -83,6 +87,7 @@ export default { data() { return { error: undefined, + updateError: undefined, workItem: {}, showInfoBanner: true, updateInProgress: false, @@ -101,9 +106,10 @@ export default { }, error() { this.error = this.$options.i18n.fetchError; + document.title = s__('404|Not found'); }, result() { - if (!this.isModal) { + if (!this.isModal && this.workItem.project) { const path = this.workItem.project?.fullPath ? ` ยท ${this.workItem.project.fullPath}` : ''; @@ -196,6 +202,9 @@ export default { workItemIconName() { return this.workItem?.workItemType?.iconName; }, + noAccessSvgPath() { + return `data:image/svg+xml;utf8,${encodeURIComponent(noAccessSvg)}`; + }, }, beforeDestroy() { /** make sure that if the user has not even dismissed the alert , @@ -248,7 +257,7 @@ export default { }, ) .catch((error) => { - this.error = error.message; + this.updateError = error.message; }) .finally(() => { this.updateInProgress = false; @@ -261,8 +270,13 @@ export default { diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 78219e62d01..5cd01de44bf 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -26,7 +26,10 @@ export const WORK_ITEM_TYPE_ENUM_TEST_CASE = 'TEST_CASE'; export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS'; export const i18n = { - fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'), + fetchErrorTitle: s__('WorkItem|Work item not found'), + fetchError: s__( + "WorkItem|This work item is not available. It either doesn't exist or you don't have permission to view it.", + ), updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'), confidentialTooltip: s__( 'WorkItem|Only project members with at least the Reporter role, the author, and assignees can view or be notified about this task.', diff --git a/doc/development/go_guide/go_upgrade.md b/doc/development/go_guide/go_upgrade.md index ae6aba5d975..d931140d9da 100644 --- a/doc/development/go_guide/go_upgrade.md +++ b/doc/development/go_guide/go_upgrade.md @@ -158,6 +158,8 @@ if you need help finding the correct person or labels: | GitLab Quality Images | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab-build-images/-/issues) | | GitLab Shell | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab-shell/-/issues) | | GitLab Workhorse | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues) | +| GitLab Browser-based DAST | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues) | +| GitLab Coverage Fuzzer | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues) | | LabKit | [Issue Tracker](https://gitlab.com/gitlab-org/labkit/-/issues) | | [Node Exporter](https://github.com/prometheus/node_exporter) | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues) | | [PgBouncer Exporter](https://github.com/prometheus-community/pgbouncer_exporter) | [Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues) | diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2c09d2ba880..60f76eaf2af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1565,6 +1565,9 @@ msgstr "" msgid "404|Make sure the address is correct and the page hasn't moved." msgstr "" +msgid "404|Not found" +msgstr "" + msgid "404|Page Not Found" msgstr "" @@ -45581,9 +45584,6 @@ msgstr "" msgid "WorkItem|Something went wrong when fetching tasks. Please refresh this page." msgstr "" -msgid "WorkItem|Something went wrong when fetching the work item. Please try again." -msgstr "" - msgid "WorkItem|Something went wrong when fetching work item types. Please try again" msgstr "" @@ -45614,6 +45614,9 @@ msgstr "" msgid "WorkItem|Test case" msgstr "" +msgid "WorkItem|This work item is not available. It either doesn't exist or you don't have permission to view it." +msgstr "" + msgid "WorkItem|Turn off confidentiality" msgstr "" @@ -45632,6 +45635,9 @@ msgstr "" msgid "WorkItem|Work item" msgstr "" +msgid "WorkItem|Work item not found" +msgstr "" + msgid "Would you like to create a new branch?" msgstr "" diff --git a/qa/qa/support/formatters/test_stats_formatter.rb b/qa/qa/support/formatters/test_stats_formatter.rb index 9d19c2e8bb5..2cde2d0928e 100644 --- a/qa/qa/support/formatters/test_stats_formatter.rb +++ b/qa/qa/support/formatters/test_stats_formatter.rb @@ -84,6 +84,7 @@ module QA job_url: QA::Runtime::Env.ci_job_url, pipeline_url: env('CI_PIPELINE_URL'), pipeline_id: env('CI_PIPELINE_ID'), + job_id: env('CI_JOB_ID'), merge_request_iid: merge_request_iid } } diff --git a/qa/spec/support/formatters/test_stats_formatter_spec.rb b/qa/spec/support/formatters/test_stats_formatter_spec.rb index ba59588d186..d0d89b5ee73 100644 --- a/qa/spec/support/formatters/test_stats_formatter_spec.rb +++ b/qa/spec/support/formatters/test_stats_formatter_spec.rb @@ -15,6 +15,7 @@ describe QA::Support::Formatters::TestStatsFormatter do let(:ci_job_url) { 'url' } let(:ci_pipeline_url) { 'url' } let(:ci_pipeline_id) { '123' } + let(:ci_job_id) { '321' } let(:run_type) { 'staging-full' } let(:smoke) { 'false' } let(:reliable) { 'false' } @@ -64,6 +65,7 @@ describe QA::Support::Formatters::TestStatsFormatter do job_url: ci_job_url, pipeline_url: ci_pipeline_url, pipeline_id: ci_pipeline_id, + job_id: ci_job_id, merge_request_iid: nil } } @@ -80,7 +82,7 @@ describe QA::Support::Formatters::TestStatsFormatter do around do |example| RSpec::Core::Sandbox.sandboxed do |config| - config.formatter = QA::Support::Formatters::TestStatsFormatter + config.formatter = described_class config.before(:context) { RSpec.current_example = nil } example.run @@ -125,6 +127,7 @@ describe QA::Support::Formatters::TestStatsFormatter do stub_env('CI_JOB_NAME', ci_job_name) stub_env('CI_PIPELINE_URL', ci_pipeline_url) stub_env('CI_PIPELINE_ID', ci_pipeline_id) + stub_env('CI_JOB_ID', ci_job_id) stub_env('CI_MERGE_REQUEST_IID', nil) stub_env('TOP_UPSTREAM_MERGE_REQUEST_IID', nil) stub_env('QA_RUN_TYPE', run_type) diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index b5111fdcae4..38e82d6a4ed 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -1,4 +1,11 @@ -import { GlAlert, GlBadge, GlLoadingIcon, GlSkeletonLoader, GlButton } from '@gitlab/ui'; +import { + GlAlert, + GlBadge, + GlLoadingIcon, + GlSkeletonLoader, + GlButton, + GlEmptyState, +} from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -54,6 +61,7 @@ describe('WorkItemDetail component', () => { const weightSubscriptionHandler = jest.fn().mockResolvedValue(workItemWeightSubscriptionResponse); const findAlert = () => wrapper.findComponent(GlAlert); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findWorkItemActions = () => wrapper.findComponent(WorkItemActions); @@ -390,13 +398,14 @@ describe('WorkItemDetail component', () => { }); }); - it('shows an error message when the work item query was unsuccessful', async () => { + it('shows empty state with an error message when the work item query was unsuccessful', async () => { const errorHandler = jest.fn().mockRejectedValue('Oops'); createComponent({ handler: errorHandler }); await waitForPromises(); expect(errorHandler).toHaveBeenCalled(); - expect(findAlert().text()).toBe(i18n.fetchError); + expect(findEmptyState().props('description')).toBe(i18n.fetchError); + expect(findWorkItemTitle().exists()).toBe(false); }); it('shows an error message when WorkItemTitle emits an `error` event', async () => {