diff options
16 files changed, 288 insertions, 64 deletions
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue index da14b1e8470..b996d3f15d1 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue @@ -3,11 +3,20 @@ import { GlLoadingIcon } from '@gitlab/ui'; import createFlash from '~/flash'; import { __ } from '~/locale'; import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import { + getQueryHeaders, + toggleQueryPollingByVisibility, +} from '~/pipelines/components/graph/utils'; +import { formatStages } from '../utils'; import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql'; +import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql'; +import { PIPELINE_STAGES_POLL_INTERVAL } from '../constants'; export default { i18n: { linkedPipelinesFetchError: __('There was a problem fetching linked pipelines.'), + stageConversionError: __('There was a problem handling the pipeline data.'), + stagesFetchError: __('There was a problem fetching the pipeline stages.'), }, components: { GlLoadingIcon, @@ -22,6 +31,9 @@ export default { iid: { default: '', }, + graphqlResourceEtag: { + default: '', + }, }, props: { stages: { @@ -48,10 +60,31 @@ export default { createFlash({ message: this.$options.i18n.linkedPipelinesFetchError }); }, }, + pipelineStages: { + context() { + return getQueryHeaders(this.graphqlResourceEtag); + }, + query: getPipelineStagesQuery, + pollInterval: PIPELINE_STAGES_POLL_INTERVAL, + variables() { + return { + fullPath: this.fullPath, + iid: this.iid, + }; + }, + update({ project }) { + return project?.pipeline?.stages?.nodes || []; + }, + error() { + createFlash({ message: this.$options.i18n.stagesFetchError }); + }, + }, }, data() { return { + formattedStages: [], pipeline: null, + pipelineStages: [], }; }, computed: { @@ -65,6 +98,25 @@ export default { return this.pipeline?.upstream; }, }, + watch: { + pipelineStages() { + // pipelineStages are from GraphQL + // stages are from REST + // we do this to use dropdown_path for fetching jobs on stage click + try { + this.formattedStages = formatStages(this.pipelineStages, this.stages); + } catch (error) { + createFlash({ + message: this.$options.i18n.stageConversionError, + captureError: true, + error, + }); + } + }, + }, + mounted() { + toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStages); + }, }; </script> @@ -79,7 +131,7 @@ export default { /> <pipeline-mini-graph - :stages="stages" + :stages="formattedStages" class="gl-display-inline" data-testid="commit-box-mini-graph" /> diff --git a/app/assets/javascripts/projects/commit_box/info/constants.js b/app/assets/javascripts/projects/commit_box/info/constants.js new file mode 100644 index 00000000000..fc4f03482e2 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/constants.js @@ -0,0 +1 @@ +export const PIPELINE_STAGES_POLL_INTERVAL = 10000; diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql new file mode 100644 index 00000000000..69a29947b16 --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql @@ -0,0 +1,19 @@ +query getPipelineStages($fullPath: ID!, $iid: ID!) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + stages { + nodes { + id + name + detailedStatus { + id + icon + group + } + } + } + } + } +} diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js index 1d4ec4c110b..c206e648561 100644 --- a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js +++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js @@ -5,7 +5,7 @@ import createDefaultClient from '~/lib/graphql'; Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient({}, { useGet: true }), }); export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipeline-mini-graph') => { @@ -15,7 +15,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin return; } - const { stages, fullPath, iid } = el.dataset; + const { stages, fullPath, iid, graphqlResourceEtag } = el.dataset; // Some commits have no pipeline, code splitting to load the pipeline optionally const { default: CommitBoxPipelineMiniGraph } = await import( @@ -30,6 +30,7 @@ export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipelin fullPath, iid, dataMethod: 'graphql', + graphqlResourceEtag, }, render(createElement) { return createElement(CommitBoxPipelineMiniGraph, { diff --git a/app/assets/javascripts/projects/commit_box/info/utils.js b/app/assets/javascripts/projects/commit_box/info/utils.js new file mode 100644 index 00000000000..ea7eb35cbaf --- /dev/null +++ b/app/assets/javascripts/projects/commit_box/info/utils.js @@ -0,0 +1,14 @@ +export const formatStages = (graphQLStages = [], restStages = []) => { + if (graphQLStages.length !== restStages.length) { + throw new Error('Rest stages and graphQl stages must be the same length'); + } + + return graphQLStages.map((stage, index) => { + return { + name: stage.name, + status: stage.detailedStatus, + dropdown_path: restStages[index]?.dropdown_path || '', + title: restStages[index].title || '', + }; + }); +}; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 78f3af49c83..75d1e4bf6a0 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -53,6 +53,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def service_usage_data + @service_ping_data_present = Rails.cache.exist?('usage_data') end def update diff --git a/app/views/admin/application_settings/service_usage_data.html.haml b/app/views/admin/application_settings/service_usage_data.html.haml index 394d8726ee8..d9fbc75e58f 100644 --- a/app/views/admin/application_settings/service_usage_data.html.haml +++ b/app/views/admin/application_settings/service_usage_data.html.haml @@ -7,10 +7,24 @@ %h3= name -%button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } } - = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') - .js-text.gl-display-inline= _('Preview payload') -%button.gl-button.btn.btn-default.js-payload-download-trigger{ type: 'button', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } - = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') - .js-text.d-inline= _('Download payload') -%pre.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } } +- if @service_ping_data_present + %button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } } + = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') + .js-text.gl-display-inline= _('Preview payload') + %button.gl-button.btn.btn-default.js-payload-download-trigger{ type: 'button', data: { endpoint: usage_data_admin_application_settings_path(format: :json) } } + = gl_loading_icon(css_class: 'js-spinner gl-display-none gl-mr-2') + .js-text.d-inline= _('Download payload') + %pre.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } } +- else + = render 'shared/global_alert', + variant: :warning, + dismissible: false, + title: 'Service Ping payload not found in the application cache' do + + .gl-alert-body + - enable_service_ping_link_url = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'enable-or-disable-usage-statistics') + - enable_service_ping_link = '<a href="%{url}">'.html_safe % { url: enable_service_ping_link_url } + - generate_manually_link_url = help_page_path('administration/troubleshooting/gitlab_rails_cheat_sheet', anchor: 'generate-service-ping') + - generate_manually_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_manually_link_url } + + = html_escape(s_('%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload.')) % { enable_service_ping_link_start: enable_service_ping_link, generate_manually_link_start: generate_manually_link, link_end: '</a>'.html_safe } diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index a3343aa4228..5e15500c556 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -57,7 +57,7 @@ #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), @last_pipeline.stages_count) } .mr-widget-pipeline-graph .stage-cell - .js-commit-pipeline-mini-graph{ data: { stages: @last_pipeline_stages.to_json.html_safe, full_path: @project.full_path, iid: @last_pipeline.iid } } + .js-commit-pipeline-mini-graph{ data: { stages: @last_pipeline_stages.to_json.html_safe, full_path: @project.full_path, iid: @last_pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@last_pipeline) } } - if @last_pipeline.duration in = time_interval_in_words @last_pipeline.duration diff --git a/doc/api/secure_files.md b/doc/api/secure_files.md index aa3e80a895d..2009b050bc0 100644 --- a/doc/api/secure_files.md +++ b/doc/api/secure_files.md @@ -11,7 +11,7 @@ type: reference, api FLAG: On self-managed GitLab, by default this feature is not available. To make it available, -ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `ci_secure_files`. Limited to 100 secure files per project. The feature is not ready for production use. +ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `ci_secure_files`. Limited to 100 secure files per project. Files must be smaller than 5 MB. The feature is not ready for production use. ## List project secure files @@ -104,7 +104,7 @@ Supported attributes: |-----------------|----------------|------------------------|-------------| | `project_id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | | `name` | string | **{check-circle}** Yes | The `name` of the file being uploaded. | -| `file` | file | **{check-circle}** Yes | The `file` being uploaded. | +| `file` | file | **{check-circle}** Yes | The `file` being uploaded (5 MB limit). | | `file_checksum` | file | **{dotted-circle}** No | An optional sha256 checksum of the file to be uploaded. If provided, the checksum must match the uploaded file, or the upload will fail to validate. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355653) in GitLab 14.10. | | `permissions` | string | **{dotted-circle}** No | The file is created with the specified permissions when created in the CI/CD job. Available types are: `read_only` (default), `read_write`, and `execute`. | diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md index ed29b33bc3a..56e8b96d11f 100644 --- a/doc/ci/review_apps/index.md +++ b/doc/ci/review_apps/index.md @@ -192,9 +192,12 @@ After you have the route mapping set up, it takes effect in the following locati > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10761) in GitLab 12.0. > - [Moved](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) to GitLab Premium in 13.9. -> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default. +> - It's [deployed behind a feature flag](../../user/feature_flags.md), `anonymous_visual_review_feedback`, disabled by default. > - It's enabled on GitLab.com. -> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-visual-reviews). **(PREMIUM SELF)** + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, +ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `anonymous_visual_review_feedback`. With Visual Reviews, members of any team (Product, Design, Quality, and so on) can provide feedback comments through a form in your review apps. The comments are added to the merge request that triggered the review app. @@ -284,30 +287,12 @@ can supply the ID by either: - Dynamically adding the `data-merge-request-id` value during the build of the app. - Supplying it manually through the visual review form in the app. -### Enable or disable Visual Reviews **(PREMIUM SELF)** - -Visual Reviews is deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) -can opt to disable it. - -To disable it: - -```ruby -Feature.disable(:anonymous_visual_review_feedback) -``` - -To enable it: - -```ruby -Feature.enable(:anonymous_visual_review_feedback) -``` - ### Authentication for Visual Reviews > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/42750#note_317271120) in GitLab 12.10. To enable visual reviews for private and internal projects, set the -[`data-require-auth` variable](#enable-or-disable-visual-reviews) to `true`. When enabled, +[`data-require-auth` variable](#configure-review-apps-for-visual-reviews) to `true`. When enabled, the user must enter a [personal access token](../../user/profile/personal_access_tokens.md) with `api` scope before submitting feedback. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index a4a345cb287..5971d88438f 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -262,6 +262,7 @@ More details about the permissions for some project-level features follow. | View pipelines page | ✓ (*1*) | ✓ (*2*) | ✓ | ✓ | ✓ | ✓ | | View pipelines tab in MR | ✓ (*3*) | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ | | [View vulnerabilities in a pipeline](application_security/security_dashboard/index.md#view-vulnerabilities-in-a-pipeline) | | ✓ (*2*) | ✓ | ✓ | ✓ | ✓ | +| View and download project-level [Secure Files](../api/secure_files.md) | | | | ✓ | ✓ | ✓ | | Cancel and retry jobs | | | | ✓ | ✓ | ✓ | | Create new [environments](../ci/environments/index.md) | | | | ✓ | ✓ | ✓ | | Delete job logs or job artifacts | | | | ✓ (*4*) | ✓ | ✓ | @@ -276,6 +277,7 @@ More details about the permissions for some project-level features follow. | Manage CI/CD settings | | | | | ✓ | ✓ | | Manage job triggers | | | | | ✓ | ✓ | | Manage project-level CI/CD variables | | | | | ✓ | ✓ | +| Manage project-level [Secure Files](../api/secure_files.md) | | | | | ✓ | ✓ | | Use [environment terminals](../ci/environments/index.md#web-terminals-deprecated) | | | | | ✓ | ✓ | | Delete pipelines | | | | | | ✓ | diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a7a6bbc9475..906cf5654f4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -652,6 +652,9 @@ msgstr "" msgid "%{emailPrefix}@company.com" msgstr "" +msgid "%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload." +msgstr "" + msgid "%{extra} more downstream pipelines" msgstr "" @@ -38008,12 +38011,18 @@ msgstr "" msgid "There was a problem fetching the keep latest artifacts setting." msgstr "" +msgid "There was a problem fetching the pipeline stages." +msgstr "" + msgid "There was a problem fetching the projects" msgstr "" msgid "There was a problem fetching users." msgstr "" +msgid "There was a problem handling the pipeline data." +msgstr "" + msgid "There was a problem sending the confirmation email" msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 5a2fc7fe523..a98ab1059b6 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -854,31 +854,45 @@ RSpec.describe 'Admin updates settings' do before do stub_usage_data_connections stub_database_flavor_check - - visit service_usage_data_admin_application_settings_path end - it 'loads usage ping payload on click', :js do - expected_payload_content = /(?=.*"uuid")(?=.*"hostname")/m + context 'when service data cached', :clean_gitlab_redis_cache do + before do + allow(Rails.cache).to receive(:exist?).with('usage_data').and_return(true) - expect(page).not_to have_content expected_payload_content + visit service_usage_data_admin_application_settings_path + end + + it 'loads usage ping payload on click', :js do + expected_payload_content = /(?=.*"uuid")(?=.*"hostname")/m - click_button('Preview payload') + expect(page).not_to have_content expected_payload_content - wait_for_requests + click_button('Preview payload') - expect(page).to have_button 'Hide payload' - expect(page).to have_content expected_payload_content - end + wait_for_requests - it 'generates usage ping payload on button click', :js do - expect_next_instance_of(Admin::ApplicationSettingsController) do |instance| - expect(instance).to receive(:usage_data).and_call_original + expect(page).to have_button 'Hide payload' + expect(page).to have_content expected_payload_content end - click_button('Download payload') + it 'generates usage ping payload on button click', :js do + expect_next_instance_of(Admin::ApplicationSettingsController) do |instance| + expect(instance).to receive(:usage_data).and_call_original + end + + click_button('Download payload') - wait_for_requests + wait_for_requests + end + end + + context 'when service data not cached' do + it 'renders missing cache information' do + visit service_usage_data_admin_application_settings_path + + expect(page).to have_text('Service Ping payload not found in the application cache') + end end end end diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js index 1a2e188e7ae..b1c8ba48475 100644 --- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js +++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js @@ -1,7 +1,18 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { shallowMount } from '@vue/test-utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue'; -import { mockStages } from './mock_data'; +import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; +import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql'; +import { mockPipelineStagesQueryResponse, mockStages } from './mock_data'; + +jest.mock('~/flash'); + +Vue.use(VueApollo); describe('Commit box pipeline mini graph', () => { let wrapper; @@ -10,34 +21,36 @@ describe('Commit box pipeline mini graph', () => { const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream'); const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream'); - const createComponent = () => { + const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse); + + const createComponent = ({ props = {} } = {}) => { + const handlers = [ + [getLinkedPipelinesQuery, {}], + [getPipelineStagesQuery, stagesHandler], + ]; + wrapper = extendedWrapper( shallowMount(CommitBoxPipelineMiniGraph, { propsData: { stages: mockStages, + ...props, }, - mocks: { - $apollo: { - queries: { - pipeline: { - loading: false, - }, - }, - }, - }, + apolloProvider: createMockApollo(handlers), }), ); - }; - beforeEach(() => { - createComponent(); - }); + return waitForPromises(); + }; afterEach(() => { wrapper.destroy(); }); describe('linked pipelines', () => { + beforeEach(async () => { + await createComponent(); + }); + it('should display the mini pipeine graph', () => { expect(findMiniGraph().exists()).toBe(true); }); @@ -47,4 +60,18 @@ describe('Commit box pipeline mini graph', () => { expect(findDownstream().exists()).toBe(false); }); }); + + describe('when data is mismatched', () => { + beforeEach(async () => { + await createComponent({ props: { stages: [] } }); + }); + + it('calls create flash with expected arguments', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: 'There was a problem handling the pipeline data.', + captureError: true, + error: new Error('Rest stages and graphQl stages must be the same length'), + }); + }); + }); }); diff --git a/spec/frontend/commit/mock_data.js b/spec/frontend/commit/mock_data.js index ef018a4fbd7..79631de974d 100644 --- a/spec/frontend/commit/mock_data.js +++ b/spec/frontend/commit/mock_data.js @@ -115,3 +115,29 @@ export const mockStages = [ dropdown_path: '/root/ci-project/-/pipelines/611/stage.json?stage=qa', }, ]; + +export const mockPipelineStagesQueryResponse = { + data: { + project: { + id: 'gid://gitlab/Project/20', + pipeline: { + id: 'gid://gitlab/Ci::Pipeline/320', + stages: { + nodes: [ + { + __typename: 'CiStage', + id: 'gid://gitlab/Ci::Stage/409', + name: 'build', + detailedStatus: { + id: 'success-409-409', + group: 'success', + icon: 'status_success', + __typename: 'DetailedStatus', + }, + }, + ], + }, + }, + }, + }, +}; diff --git a/spec/frontend/commit/pipelines/utils_spec.js b/spec/frontend/commit/pipelines/utils_spec.js new file mode 100644 index 00000000000..472e35a6eb3 --- /dev/null +++ b/spec/frontend/commit/pipelines/utils_spec.js @@ -0,0 +1,59 @@ +import { formatStages } from '~/projects/commit_box/info/utils'; + +const graphqlStage = [ + { + __typename: 'CiStage', + name: 'deploy', + detailedStatus: { + __typename: 'DetailedStatus', + icon: 'status_success', + group: 'success', + id: 'success-409-409', + }, + }, +]; + +const restStage = [ + { + name: 'deploy', + title: 'deploy: passed', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/root/ci-project/-/pipelines/318#deploy', + illustration: null, + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + }, + path: '/root/ci-project/-/pipelines/318#deploy', + dropdown_path: '/root/ci-project/-/pipelines/318/stage.json?stage=deploy', + }, +]; + +describe('Utils', () => { + it('combines REST and GraphQL stages correctly for component', () => { + expect(formatStages(graphqlStage, restStage)).toEqual([ + { + dropdown_path: '/root/ci-project/-/pipelines/318/stage.json?stage=deploy', + name: 'deploy', + status: { + __typename: 'DetailedStatus', + group: 'success', + icon: 'status_success', + id: 'success-409-409', + }, + title: 'deploy: passed', + }, + ]); + }); + + it('throws an error if arrays are not the same length', () => { + expect(() => { + formatStages(graphqlStage, []); + }).toThrow('Rest stages and graphQl stages must be the same length'); + }); +}); |