diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue index 66443a36b5c2e24af41daae16a581e8e8cef6f4e..7011b808f9ea5117ceedee038f798021cc214c01 100644 --- a/app/assets/javascripts/incidents/components/incidents_list.vue +++ b/app/assets/javascripts/incidents/components/incidents_list.vue @@ -72,11 +72,18 @@ export default { GlPagination, GlTabs, GlTab, + PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'), }, directives: { GlTooltip: GlTooltipDirective, }, - inject: ['projectPath', 'newIssuePath', 'incidentTemplateName', 'issuePath'], + inject: [ + 'projectPath', + 'newIssuePath', + 'incidentTemplateName', + 'issuePath', + 'publishedAvailable', + ], apollo: { incidents: { query: getIncidents, @@ -144,6 +151,20 @@ export default { newIncidentPath() { return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath); }, + availableFields() { + return this.publishedAvailable + ? [ + ...this.$options.fields, + ...[ + { + key: 'published', + label: s__('IncidentManagement|Published'), + thClass: 'gl-pointer-events-none', + }, + ], + ] + : this.$options.fields; + }, }, methods: { onInputChange: debounce(function debounceSearch(input) { @@ -230,7 +251,7 @@ export default { </h4> <gl-table :items="incidents.list || []" - :fields="$options.fields" + :fields="availableFields" :show-empty="true" :busy="loading" stacked="md" @@ -245,7 +266,7 @@ export default { <gl-icon v-if="item.state === 'closed'" name="issue-close" - class="gl-fill-blue-500" + class="gl-ml-1 gl-fill-blue-500" data-testid="incident-closed" /> </div> @@ -285,6 +306,12 @@ export default { </div> </template> + <template v-if="publishedAvailable" #cell(published)="{ item }"> + <published-cell + :status-page-published-incident="item.statusPagePublishedIncident" + :un-published="$options.i18n.unPublished" + /> + </template> <template #table-busy> <gl-loading-icon size="lg" color="dark" class="mt-3" /> </template> diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js index 3a3efa98f25c76649d3c5c2987495866f1a53679..9908a04f9b5e97d2a7a26bf7c43c66d81a231de5 100644 --- a/app/assets/javascripts/incidents/constants.js +++ b/app/assets/javascripts/incidents/constants.js @@ -5,7 +5,8 @@ export const I18N = { noIncidents: s__('IncidentManagement|No incidents to display.'), unassigned: s__('IncidentManagement|Unassigned'), createIncidentBtnLabel: s__('IncidentManagement|Create incident'), - searchPlaceholder: __('Search or filter results...'), + unPublished: s__('IncidentManagement|Unpublished'), + searchPlaceholder: __('Search results...'), }; export const INCIDENT_STATE_TABS = [ diff --git a/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql b/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql index d717d898ef0d6ffcd25fef62d3ab7ed688e42f43..c0942b629e5d67fe3cdcae4b429affe98eb8acd0 100644 --- a/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql +++ b/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql @@ -37,6 +37,7 @@ query getIncidents( webUrl } } + statusPagePublishedIncident } pageInfo { hasNextPage diff --git a/app/assets/javascripts/incidents/list.js b/app/assets/javascripts/incidents/list.js index 6e39e63181593ba09636c1df3086b88323d42b68..3809742e32b2e68770e75fc1a3f591f1876a7a72 100644 --- a/app/assets/javascripts/incidents/list.js +++ b/app/assets/javascripts/incidents/list.js @@ -8,7 +8,13 @@ export default () => { const selector = '#js-incidents'; const domEl = document.querySelector(selector); - const { projectPath, newIssuePath, incidentTemplateName, issuePath } = domEl.dataset; + const { + projectPath, + newIssuePath, + incidentTemplateName, + issuePath, + publishedAvailable, + } = domEl.dataset; const apolloProvider = new VueApollo({ defaultClient: createDefaultClient(), @@ -21,6 +27,7 @@ export default () => { incidentTemplateName, newIssuePath, issuePath, + publishedAvailable, }, apolloProvider, components: { diff --git a/app/assets/stylesheets/pages/incident_management_list.scss b/app/assets/stylesheets/pages/incident_management_list.scss index 341c5df6484e53889cb7dff473108ac0bc90da4d..5d1594ddf260c7cf6cc0ceb22aab0c5e7b8263dc 100644 --- a/app/assets/stylesheets/pages/incident_management_list.scss +++ b/app/assets/stylesheets/pages/incident_management_list.scss @@ -98,4 +98,9 @@ @include gl-w-full; } } + + // TODO: Abstract to `@gitlab/ui` utility set: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/921 + .gl-fill-green-500 { + fill: $green-500; + } } diff --git a/app/helpers/projects/incidents_helper.rb b/app/helpers/projects/incidents_helper.rb index fbadcef51847b9e5d399c491dc15d4fa72a1fd91..3b494ec247247c09818bbbaf310a6567735d384b 100644 --- a/app/helpers/projects/incidents_helper.rb +++ b/app/helpers/projects/incidents_helper.rb @@ -10,3 +10,5 @@ def incidents_data(project) } end end + +Projects::IncidentsHelper.prepend_if_ee('EE::Projects::IncidentsHelper') diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 56ac0c3eed87523ad434196330f987200e123d59..36cd717f2f3c33e7b5467b5ad715b84c165644d4 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -4575,6 +4575,11 @@ type EpicIssue implements Noteable { """ state: IssueState! + """ + Indicates whether an issue is published to the status page + """ + statusPagePublishedIncident: Boolean + """ Indicates the currently logged in user is subscribed to the issue """ @@ -6218,6 +6223,11 @@ type Issue implements Noteable { """ state: IssueState! + """ + Indicates whether an issue is published to the status page + """ + statusPagePublishedIncident: Boolean + """ Indicates the currently logged in user is subscribed to the issue """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index bfbc6e3dd53f552c6a4b056c34c5c6b8a2268e41..ae4da89237e4bd0c691e693fe502543e51be5592 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -12750,6 +12750,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "statusPagePublishedIncident", + "description": "Indicates whether an issue is published to the status page", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "subscribed", "description": "Indicates the currently logged in user is subscribed to the issue", @@ -17138,6 +17152,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "statusPagePublishedIncident", + "description": "Indicates whether an issue is published to the status page", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "subscribed", "description": "Indicates the currently logged in user is subscribed to the issue", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ae2643ff573ac3849a4a1f59ca755427b0267f26..d1aed6cba63436e59d6a6b07c4fa9307b677b58b 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -759,6 +759,7 @@ Relationship between an epic and an issue | `relationPath` | String | URI path of the epic-issue relation | | `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) | | `state` | IssueState! | State of the issue | +| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page | | `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue | | `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue | | `timeEstimate` | Int! | Time estimate of the issue | @@ -926,6 +927,7 @@ Represents a Group Member | `reference` | String! | Internal reference of the issue. Returned in shortened format by default | | `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) | | `state` | IssueState! | State of the issue | +| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page | | `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue | | `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue | | `timeEstimate` | Int! | Time estimate of the issue | diff --git a/ee/app/assets/javascripts/incidents/components/published_cell.vue b/ee/app/assets/javascripts/incidents/components/published_cell.vue new file mode 100644 index 0000000000000000000000000000000000000000..c18e814df8a80c00dc3552ed8d69f84822e6cced --- /dev/null +++ b/ee/app/assets/javascripts/incidents/components/published_cell.vue @@ -0,0 +1,35 @@ +<script> +import { GlIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + i18n: { + published: s__('IncidentManagement|Published to status page'), + }, + components: { + GlIcon, + }, + props: { + statusPagePublishedIncident: { + type: Boolean, + required: false, + default: null, + }, + unPublished: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div data-testid="published-cell"> + <gl-icon + v-if="statusPagePublishedIncident" + name="status_success" + class="gl-fill-green-500" + :aria-label="$options.i18n.published" + /> + <div v-else>{{ unPublished }}</div> + </div> +</template> diff --git a/ee/app/graphql/ee/types/issue_type.rb b/ee/app/graphql/ee/types/issue_type.rb index c51122b3d4cc95faf04d1bef68a625e68e8d0bb9..d316cc59008cdf598f88f3be2aa844ef97cb1f25 100644 --- a/ee/app/graphql/ee/types/issue_type.rb +++ b/ee/app/graphql/ee/types/issue_type.rb @@ -28,6 +28,9 @@ module IssueType null: true, description: 'Current health status. Returns null if `save_issuable_health_status` feature flag is disabled.', resolve: -> (obj, _, _) { obj.supports_health_status? ? obj.health_status : nil } + + field :status_page_published_incident, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates whether an issue is published to the status page' end end end diff --git a/ee/app/helpers/ee/projects/incidents_helper.rb b/ee/app/helpers/ee/projects/incidents_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..7ee07528422d214378071f26070336304f650e7d --- /dev/null +++ b/ee/app/helpers/ee/projects/incidents_helper.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module EE + module Projects + module IncidentsHelper + extend ::Gitlab::Utils::Override + + override :incidents_data + def incidents_data(project) + super.merge( + incidents_data_published_available(project) + ) + end + + private + + def incidents_data_published_available(project) + return {} unless project.feature_available?(:status_page) + + { + 'published-available' => 'true' + } + end + end + end +end diff --git a/ee/changelogs/unreleased/229532-status-page-publish-column.yml b/ee/changelogs/unreleased/229532-status-page-publish-column.yml new file mode 100644 index 0000000000000000000000000000000000000000..3085cfd27266dc75a62f9d8cef16cccc13871f4f --- /dev/null +++ b/ee/changelogs/unreleased/229532-status-page-publish-column.yml @@ -0,0 +1,5 @@ +--- +title: Add published column +merge_request: 38439 +author: +type: changed diff --git a/ee/spec/frontend/incidents/components/published_cell_spec.js b/ee/spec/frontend/incidents/components/published_cell_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..096c41ecc701231a883f7e2fcb794b808a757840 --- /dev/null +++ b/ee/spec/frontend/incidents/components/published_cell_spec.js @@ -0,0 +1,56 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; +import PublishedCell from 'ee/incidents/components/published_cell.vue'; + +describe('Incidents Published Cell', () => { + let wrapper; + + const findCell = () => wrapper.find("[data-testid='published-cell']"); + + function mountComponent({ + props = { statusPagePublishedIncident: null, unPublished: 'Unpublished' }, + }) { + wrapper = shallowMount(PublishedCell, { + propsData: { + ...props, + }, + stubs: { + GlIcon: true, + }, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + describe('Published cell', () => { + beforeEach(() => { + mountComponent({}); + }); + + it('render a cell with unpublished by default', () => { + expect( + findCell() + .find(GlIcon) + .exists(), + ).toBe(false); + expect(findCell().text()).toBe('Unpublished'); + }); + + it('render a status success icon if statusPagePublishedIncident returns true', () => { + wrapper.setProps({ statusPagePublishedIncident: true }); + + return wrapper.vm.$nextTick().then(() => { + expect( + findCell() + .find(GlIcon) + .exists(), + ).toBe(true); + }); + }); + }); +}); diff --git a/ee/spec/helpers/ee/projects/incidents_helper_spec.rb b/ee/spec/helpers/ee/projects/incidents_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2131dab448334df7a51b3b0676d9823704bc2247 --- /dev/null +++ b/ee/spec/helpers/ee/projects/incidents_helper_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::IncidentsHelper do + include Gitlab::Routing.url_helpers + + let_it_be(:project) { create(:project) } + let(:project_path) { project.full_path } + let(:new_issue_path) { new_project_issue_path(project) } + let(:issue_path) { project_issues_path(project) } + + describe '#incidents_data' do + let(:expected_incidents_data) do + { + 'project-path' => project_path, + 'new-issue-path' => new_issue_path, + 'incident-template-name' => 'incident', + 'issue-path' => issue_path + } + end + + subject { helper.incidents_data(project) } + + before do + allow(project).to receive(:feature_available?).with(:status_page).and_return(status_page_feature_available) + end + + context 'when status page feature is available' do + let(:status_page_feature_available) { true } + + it { is_expected.to eq(expected_incidents_data.merge('published-available' => 'true')) } + end + + context 'when status page issue is not available' do + let(:status_page_feature_available) { false } + + it { is_expected.to eq(expected_incidents_data) } + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9a07d0e80f437099cee1c7597887722d10e625bc..5ee29dd0904cc76581d9b2db9b8ed8fea0a78891 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12817,12 +12817,21 @@ msgstr "" msgid "IncidentManagement|Open" msgstr "" +msgid "IncidentManagement|Published" +msgstr "" + +msgid "IncidentManagement|Published to status page" +msgstr "" + msgid "IncidentManagement|There was an error displaying the incidents." msgstr "" msgid "IncidentManagement|Unassigned" msgstr "" +msgid "IncidentManagement|Unpublished" +msgstr "" + msgid "IncidentSettings|Alert integration" msgstr "" @@ -21045,6 +21054,9 @@ msgstr "" msgid "Search requirements" msgstr "" +msgid "Search results..." +msgstr "" + msgid "Search users" msgstr "" diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js index 4a17fb7393ae452d3f2afed06d21ca7f9679a6a8..7956a8e07a0ad42c9569ba4ad7f7bca5ca7a5208 100644 --- a/spec/frontend/incidents/components/incidents_list_spec.js +++ b/spec/frontend/incidents/components/incidents_list_spec.js @@ -56,6 +56,7 @@ describe('Incidents List', () => { newIssuePath, incidentTemplateName, issuePath: '/project/isssues', + publishedAvailable: true, }, stubs: { GlButton: true,