From 26a50872e9da9509c52c70f74dc21698fec906db Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 14 Feb 2020 18:08:45 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../blob/components/blob_content.vue | 51 ++++++++ .../blob/components/blob_content_error.vue | 15 +++ .../blob_header_default_actions.vue | 7 +- .../cycle_analytics/cycle_analytics_bundle.js | 3 +- .../fragments/blobviewer.fragment.graphql | 3 +- .../snippets/components/snippet_blob_view.vue | 44 ++++++- .../snippet.blob.content.query.graphql | 13 ++ .../components/blob_viewers/constants.js | 3 + .../components/blob_viewers/index.js | 4 + .../components/blob_viewers/mixins.js | 8 ++ .../components/blob_viewers/rich_viewer.vue | 10 ++ .../components/blob_viewers/simple_viewer.vue | 68 ++++++++++ .../stylesheets/framework/highlight.scss | 15 ++- .../stylesheets/pages/cycle_analytics.scss | 8 -- app/controllers/concerns/metrics_dashboard.rb | 12 +- app/helpers/environments_helper.rb | 3 +- .../projects/cycle_analytics/show.html.haml | 5 +- app/views/projects/snippets/show.html.haml | 6 +- app/views/snippets/show.html.haml | 6 +- .../196184-convert-insights-to-echarts.yml | 5 + changelogs/unreleased/196797-blob-content.yml | 5 + ...r-within-app-views-projects-cycle_anal.yml | 5 + .../defect-safari-line-numbers-height.yml | 5 + doc/api/epics.md | 2 +- .../img/enable_review_app_v12_8.png | Bin 0 -> 46424 bytes doc/ci/review_apps/index.md | 24 +++- doc/user/permissions.md | 1 + .../img/insights_example_pie_chart.png | Bin 6985 -> 0 bytes doc/user/project/insights/index.md | 3 +- .../ci_variable/add_ci_variable_spec.rb | 38 ------ .../add_remove_ci_variable_spec.rb | 59 +++++++++ qa/qa/support/repeater.rb | 4 +- qa/qa/support/wait_for_requests.rb | 2 +- qa/qa/support/waiter.rb | 25 ++-- qa/spec/support/repeater_spec.rb | 30 +++++ qa/spec/support/waiter_spec.rb | 5 + .../concerns/metrics_dashboard_spec.rb | 20 +++ .../projects/environments_controller_spec.rb | 2 +- .../components/blob_content_error_spec.js | 27 ++++ .../blob/components/blob_content_spec.js | 70 +++++++++++ .../blob_header_default_actions_spec.js | 9 -- spec/frontend/blob/components/mock_data.js | 46 ++++--- .../components/snippet_blob_view_spec.js | 117 ++++++++++++++++-- .../__snapshots__/simple_viewer_spec.js.snap | 86 +++++++++++++ .../blob_viewers/rich_viewer_spec.js | 27 ++++ .../blob_viewers/simple_viewer_spec.js | 81 ++++++++++++ spec/helpers/environments_helper_spec.rb | 2 +- 47 files changed, 858 insertions(+), 126 deletions(-) create mode 100644 app/assets/javascripts/blob/components/blob_content.vue create mode 100644 app/assets/javascripts/blob/components/blob_content_error.vue create mode 100644 app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql create mode 100644 app/assets/javascripts/vue_shared/components/blob_viewers/constants.js create mode 100644 app/assets/javascripts/vue_shared/components/blob_viewers/index.js create mode 100644 app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js create mode 100644 app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue create mode 100644 app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue create mode 100644 changelogs/unreleased/196184-convert-insights-to-echarts.yml create mode 100644 changelogs/unreleased/196797-blob-content.yml create mode 100644 changelogs/unreleased/204740-migrate-fa-spinner-to-spinner-within-app-views-projects-cycle_anal.yml create mode 100644 changelogs/unreleased/defect-safari-line-numbers-height.yml create mode 100644 doc/ci/review_apps/img/enable_review_app_v12_8.png delete mode 100644 doc/user/project/insights/img/insights_example_pie_chart.png delete mode 100644 qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb create mode 100644 qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb create mode 100644 spec/frontend/blob/components/blob_content_error_spec.js create mode 100644 spec/frontend/blob/components/blob_content_spec.js create mode 100644 spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap create mode 100644 spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js create mode 100644 spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue new file mode 100644 index 00000000000..2639a099093 --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_content.vue @@ -0,0 +1,51 @@ + + diff --git a/app/assets/javascripts/blob/components/blob_content_error.vue b/app/assets/javascripts/blob/components/blob_content_error.vue new file mode 100644 index 00000000000..0f1af0a962d --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_content_error.vue @@ -0,0 +1,15 @@ + + diff --git a/app/assets/javascripts/blob/components/blob_header_default_actions.vue b/app/assets/javascripts/blob/components/blob_header_default_actions.vue index f5157fba819..6b38b871606 100644 --- a/app/assets/javascripts/blob/components/blob_header_default_actions.vue +++ b/app/assets/javascripts/blob/components/blob_header_default_actions.vue @@ -36,11 +36,6 @@ export default { return this.activeViewer === RICH_BLOB_VIEWER; }, }, - methods: { - requestCopyContents() { - this.$emit('copy'); - }, - }, BTN_COPY_CONTENTS_TITLE, BTN_DOWNLOAD_TITLE, BTN_RAW_TITLE, @@ -53,7 +48,7 @@ export default { :aria-label="$options.BTN_COPY_CONTENTS_TITLE" :title="$options.BTN_COPY_CONTENTS_TITLE" :disabled="copyDisabled" - @click="requestCopyContents" + data-clipboard-target="#blob-code-content" > diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 43f5e9954ce..6d2b11e39d3 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import Vue from 'vue'; import Cookies from 'js-cookie'; -import { GlEmptyState } from '@gitlab/ui'; +import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import Flash from '../flash'; import { __ } from '~/locale'; @@ -28,6 +28,7 @@ export default () => { name: 'CycleAnalytics', components: { GlEmptyState, + GlLoadingIcon, banner, 'stage-issue-component': stageComponent, 'stage-plan-component': stageComponent, diff --git a/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql index 64c894df115..b202ed12f80 100644 --- a/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql +++ b/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql @@ -1,6 +1,7 @@ fragment BlobViewer on SnippetBlobViewer { collapsed - loadingPartialName renderError tooLarge + type + fileType } diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue index 49e0ef35cb8..4703a940e08 100644 --- a/app/assets/javascripts/snippets/components/snippet_blob_view.vue +++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue @@ -2,13 +2,19 @@ import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import { SNIPPET_VISIBILITY_PUBLIC } from '../constants'; import BlobHeader from '~/blob/components/blob_header.vue'; -import GetSnippetBlobQuery from '../queries/snippet.blob.query.graphql'; +import BlobContent from '~/blob/components/blob_content.vue'; import { GlLoadingIcon } from '@gitlab/ui'; +import GetSnippetBlobQuery from '../queries/snippet.blob.query.graphql'; +import GetBlobContent from '../queries/snippet.blob.content.query.graphql'; + +import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants'; + export default { components: { BlobEmbeddable, BlobHeader, + BlobContent, GlLoadingIcon, }, apollo: { @@ -20,6 +26,23 @@ export default { }; }, update: data => data.snippets.edges[0].node.blob, + result(res) { + const viewer = res.data.snippets.edges[0].node.blob.richViewer + ? RICH_BLOB_VIEWER + : SIMPLE_BLOB_VIEWER; + this.switchViewer(viewer, true); + }, + }, + blobContent: { + query: GetBlobContent, + variables() { + return { + ids: this.snippet.id, + rich: this.activeViewerType === RICH_BLOB_VIEWER, + }; + }, + update: data => + data.snippets.edges[0].node.blob.richData || data.snippets.edges[0].node.blob.plainData, }, }, props: { @@ -31,6 +54,8 @@ export default { data() { return { blob: {}, + blobContent: '', + activeViewerType: window.location.hash ? SIMPLE_BLOB_VIEWER : '', }; }, computed: { @@ -40,6 +65,18 @@ export default { isBlobLoading() { return this.$apollo.queries.blob.loading; }, + isContentLoading() { + return this.$apollo.queries.blobContent.loading; + }, + viewer() { + const { richViewer, simpleViewer } = this.blob; + return this.activeViewerType === RICH_BLOB_VIEWER ? richViewer : simpleViewer; + }, + }, + methods: { + switchViewer(newViewer, respectHash = false) { + this.activeViewerType = respectHash && window.location.hash ? SIMPLE_BLOB_VIEWER : newViewer; + }, }, }; @@ -49,11 +86,12 @@ export default {
- + +
diff --git a/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql b/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql new file mode 100644 index 00000000000..889a88dd93c --- /dev/null +++ b/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql @@ -0,0 +1,13 @@ +query SnippetBlobContent($ids: [ID!], $rich: Boolean!) { + snippets(ids: $ids) { + edges { + node { + id + blob { + richData @include(if: $rich) + plainData @skip(if: $rich) + } + } + } + } +} diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js b/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js new file mode 100644 index 00000000000..d4c1808eec2 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js @@ -0,0 +1,3 @@ +export const HIGHLIGHT_CLASS_NAME = 'hll'; + +export default {}; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/index.js b/app/assets/javascripts/vue_shared/components/blob_viewers/index.js new file mode 100644 index 00000000000..72fba9392f9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/index.js @@ -0,0 +1,4 @@ +import RichViewer from './rich_viewer.vue'; +import SimpleViewer from './simple_viewer.vue'; + +export { RichViewer, SimpleViewer }; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js new file mode 100644 index 00000000000..582213ee8d3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js @@ -0,0 +1,8 @@ +export default { + props: { + content: { + type: String, + required: true, + }, + }, +}; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue new file mode 100644 index 00000000000..b3a1df8f303 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue @@ -0,0 +1,10 @@ + + diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue new file mode 100644 index 00000000000..e64c7132117 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue @@ -0,0 +1,68 @@ + + diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index ee6e53adaf7..73a2170fc68 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -30,7 +30,6 @@ .line { display: block; width: 100%; - min-height: 1.5em; padding-left: 10px; padding-right: 10px; white-space: pre; @@ -48,10 +47,10 @@ font-family: $monospace-font; display: block; font-size: $code-font-size !important; - min-height: 1.5em; white-space: nowrap; - i { + i, + svg { float: left; margin-top: 3px; margin-right: 5px; @@ -62,12 +61,20 @@ &:focus { outline: none; - i { + i, + svg { visibility: visible; } } } } + + pre .line, + .line-numbers a { + font-size: 0.8125rem; + line-height: 1.1875rem; + min-height: 1.1875rem; + } } // Vertically aligns line numbers (eg. blame view) diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 76cd4f34865..21b23feb57f 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -109,14 +109,6 @@ top: $gl-padding-top; } - .fa-spinner { - font-size: 28px; - position: relative; - margin-left: -20px; - left: 50%; - margin-top: 36px; - } - .stage-panel-body { display: flex; flex-wrap: wrap; diff --git a/app/controllers/concerns/metrics_dashboard.rb b/app/controllers/concerns/metrics_dashboard.rb index dc392147cb8..fa79f3bc4e6 100644 --- a/app/controllers/concerns/metrics_dashboard.rb +++ b/app/controllers/concerns/metrics_dashboard.rb @@ -5,6 +5,7 @@ module MetricsDashboard include RenderServiceResults include ChecksCollaboration + include EnvironmentsHelper extend ActiveSupport::Concern @@ -15,8 +16,9 @@ module MetricsDashboard metrics_dashboard_params.to_h.symbolize_keys ) - if include_all_dashboards? && result - result[:all_dashboards] = all_dashboards + if result + result[:all_dashboards] = all_dashboards if include_all_dashboards? + result[:metrics_data] = metrics_data(project_for_dashboard, environment_for_dashboard) if project_for_dashboard && environment_for_dashboard end respond_to do |format| @@ -76,10 +78,14 @@ module MetricsDashboard defined?(project) ? project : nil end + def environment_for_dashboard + defined?(environment) ? environment : nil + end + def dashboard_success_response(result) { status: :ok, - json: result.slice(:all_dashboards, :dashboard, :status) + json: result.slice(:all_dashboards, :dashboard, :status, :metrics_data) } end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 579a57a27de..fd330d4efd9 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module EnvironmentsHelper + include ActionView::Helpers::AssetUrlHelper prepend_if_ee('::EE::EnvironmentsHelper') # rubocop: disable Cop/InjectEnterpriseEditionModule def environments_list_data @@ -21,7 +22,7 @@ module EnvironmentsHelper { "settings-path" => edit_project_service_path(project, 'prometheus'), "clusters-path" => project_clusters_path(project), - "current-environment-name": environment.name, + "current-environment-name" => environment.name, "documentation-path" => help_page_path('administration/monitoring/prometheus/index.md'), "empty-getting-started-svg-path" => image_path('illustrations/monitoring/getting_started.svg'), "empty-loading-svg-path" => image_path('illustrations/monitoring/loading.svg'), diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 70409774a50..b0d9dfb0d37 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -5,7 +5,7 @@ %banner{ "v-if" => "!isOverviewDialogDismissed", "documentation-link": help_page_path('user/analytics/value_stream_analytics.md'), "v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" } - = icon("spinner spin", "v-show" => "isLoading") + %gl-loading-icon{ "v-show" => "isLoading", "size" => "lg" } .wrapper{ "v-show" => "!isLoading && !hasError" } .card .card-header @@ -57,8 +57,7 @@ %ul %stage-nav-item{ "v-for" => "stage in state.stages", ":key" => '`ca-stage-title-${stage.title}`', '@select' => 'selectStage(stage)', ":title" => "stage.title", ":is-user-allowed" => "stage.isUserAllowed", ":value" => "stage.value", ":is-active" => "stage.active" } .section.stage-events - %template{ "v-if" => "isLoadingStage" } - = icon("spinner spin") + %gl-loading-icon{ "v-show" => "isLoadingStage", "size" => "lg" } %template{ "v-if" => "currentStage && !currentStage.isUserAllowed" } = render partial: "no_access" %template{ "v-else" => true } diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 768e4422206..422a467574b 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -12,7 +12,7 @@ %article.file-holder.snippet-file-content = render 'shared/snippets/blob' - .row-content-block.top-block.content-component-block - = render 'award_emoji/awards_block', awardable: @snippet, inline: true +.row-content-block.top-block.content-component-block + = render 'award_emoji/awards_block', awardable: @snippet, inline: true - #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true +#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 080c0ab6ece..30f760f2122 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -13,7 +13,7 @@ %article.file-holder.snippet-file-content = render 'shared/snippets/blob' - .row-content-block.top-block.content-component-block - = render 'award_emoji/awards_block', awardable: @snippet, inline: true +.row-content-block.top-block.content-component-block + = render 'award_emoji/awards_block', awardable: @snippet, inline: true - #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false +#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false diff --git a/changelogs/unreleased/196184-convert-insights-to-echarts.yml b/changelogs/unreleased/196184-convert-insights-to-echarts.yml new file mode 100644 index 00000000000..87219b3ff52 --- /dev/null +++ b/changelogs/unreleased/196184-convert-insights-to-echarts.yml @@ -0,0 +1,5 @@ +--- +title: Move insights charts to echarts +merge_request: 24661 +author: +type: other diff --git a/changelogs/unreleased/196797-blob-content.yml b/changelogs/unreleased/196797-blob-content.yml new file mode 100644 index 00000000000..b1443f60f62 --- /dev/null +++ b/changelogs/unreleased/196797-blob-content.yml @@ -0,0 +1,5 @@ +--- +title: Refactored snippets view to Vue +merge_request: 25188 +author: +type: other diff --git a/changelogs/unreleased/204740-migrate-fa-spinner-to-spinner-within-app-views-projects-cycle_anal.yml b/changelogs/unreleased/204740-migrate-fa-spinner-to-spinner-within-app-views-projects-cycle_anal.yml new file mode 100644 index 00000000000..e417b770668 --- /dev/null +++ b/changelogs/unreleased/204740-migrate-fa-spinner-to-spinner-within-app-views-projects-cycle_anal.yml @@ -0,0 +1,5 @@ +--- +title: Update loading icon in Value Stream Analytics view +merge_request: 24861 +author: +type: other diff --git a/changelogs/unreleased/defect-safari-line-numbers-height.yml b/changelogs/unreleased/defect-safari-line-numbers-height.yml new file mode 100644 index 00000000000..e72a7ffc107 --- /dev/null +++ b/changelogs/unreleased/defect-safari-line-numbers-height.yml @@ -0,0 +1,5 @@ +--- +title: Fix code line and line number alignment in Safari +merge_request: 24820 +author: +type: fixed diff --git a/doc/api/epics.md b/doc/api/epics.md index 078bdfdba69..b8eb1ab9f9a 100644 --- a/doc/api/epics.md +++ b/doc/api/epics.md @@ -1,4 +1,4 @@ -# Epics API **(ULTIMATE)** +# Epics API **(PREMIUM)** Every API call to epic must be authenticated. diff --git a/doc/ci/review_apps/img/enable_review_app_v12_8.png b/doc/ci/review_apps/img/enable_review_app_v12_8.png new file mode 100644 index 0000000000000000000000000000000000000000..364fe402787823147715197f17ff45bbe253f05d GIT binary patch literal 46424 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 dVX$#j{ [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/118844) in GitLab 12.8. + +When configuring Review Apps for a project, you need to add a new job to `.gitlab-ci.yml`, +as mentioned above. To facilitate this and if you are using Kubernetes, you can click +the **Enable Review Apps** button and GitLab will prompt you with a template code block that +you can copy and paste into `.gitlab-ci.yml` as a starting point. To do so: + +1. Go to the project your want to create a Review App job for. +1. From the left nav, go to **Operations** > **Environments**. +1. Click on the **Enable Review Apps** button. It is available to you + if you have Developer or higher [permissions](../../user/permissions.md) to that project. +1. Copy the provided code snippet and paste it into your + `.gitlab-ci.yml` file: + + ![Enable Review Apps modal](img/enable_review_app_v12_8.png) + +1. Feel free to tune this template to your own needs. + ## Review Apps examples The following are example projects that demonstrate Review App configuration: diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 4f42afe4e79..8cf60342446 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -93,6 +93,7 @@ The following table depicts the various user permission levels in a project. | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | +| Enable Review Apps | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ | | Cancel and retry jobs | | | ✓ | ✓ | ✓ | | Create or update commit status | | | ✓ (*5*) | ✓ | ✓ | diff --git a/doc/user/project/insights/img/insights_example_pie_chart.png b/doc/user/project/insights/img/insights_example_pie_chart.png deleted file mode 100644 index 3480bce673881c22fd07518fa6231530ec837425..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6985 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 { + let wrapper; + const viewerError = '

Foo Error

'; + + function createComponent() { + wrapper = shallowMount(BlobContentError, { + propsData: { + viewerError, + }, + }); + } + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the passed error without transformations', () => { + expect(wrapper.html()).toContain(viewerError); + }); +}); diff --git a/spec/frontend/blob/components/blob_content_spec.js b/spec/frontend/blob/components/blob_content_spec.js new file mode 100644 index 00000000000..6a130c9c43d --- /dev/null +++ b/spec/frontend/blob/components/blob_content_spec.js @@ -0,0 +1,70 @@ +import { shallowMount } from '@vue/test-utils'; +import BlobContent from '~/blob/components/blob_content.vue'; +import BlobContentError from '~/blob/components/blob_content_error.vue'; +import { + RichViewerMock, + SimpleViewerMock, + RichBlobContentMock, + SimpleBlobContentMock, +} from './mock_data'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; + +describe('Blob Content component', () => { + let wrapper; + + function createComponent(propsData = {}, activeViewer = SimpleViewerMock) { + wrapper = shallowMount(BlobContent, { + propsData: { + loading: false, + activeViewer, + ...propsData, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('rendering', () => { + it('renders loader if `loading: true`', () => { + createComponent({ loading: true }); + expect(wrapper.contains(GlLoadingIcon)).toBe(true); + expect(wrapper.contains(BlobContentError)).toBe(false); + expect(wrapper.contains(RichViewer)).toBe(false); + expect(wrapper.contains(SimpleViewer)).toBe(false); + }); + + it('renders error if there is any in the viewer', () => { + const renderError = 'Oops'; + const viewer = Object.assign({}, SimpleViewerMock, { renderError }); + createComponent({}, viewer); + expect(wrapper.contains(GlLoadingIcon)).toBe(false); + expect(wrapper.contains(BlobContentError)).toBe(true); + expect(wrapper.contains(RichViewer)).toBe(false); + expect(wrapper.contains(SimpleViewer)).toBe(false); + }); + + it.each` + type | mock | viewer + ${'simple'} | ${SimpleViewerMock} | ${SimpleViewer} + ${'rich'} | ${RichViewerMock} | ${RichViewer} + `( + 'renders $type viewer when activeViewer is $type and no loading or error detected', + ({ mock, viewer }) => { + createComponent({}, mock); + expect(wrapper.contains(viewer)).toBe(true); + }, + ); + + it.each` + content | mock | viewer + ${SimpleBlobContentMock.plainData} | ${SimpleViewerMock} | ${SimpleViewer} + ${RichBlobContentMock.richData} | ${RichViewerMock} | ${RichViewer} + `('renders correct content that is passed to the component', ({ content, mock, viewer }) => { + createComponent({ content }, mock); + expect(wrapper.find(viewer).html()).toContain(content); + }); + }); +}); diff --git a/spec/frontend/blob/components/blob_header_default_actions_spec.js b/spec/frontend/blob/components/blob_header_default_actions_spec.js index 5da0d40ab14..39d627e71c5 100644 --- a/spec/frontend/blob/components/blob_header_default_actions_spec.js +++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js @@ -67,13 +67,4 @@ describe('Blob Header Default Actions', () => { expect(buttons.at(0).attributes('disabled')).toBeTruthy(); }); }); - - describe('functionally', () => { - it('emits an event when a Copy Contents button is clicked', () => { - jest.spyOn(wrapper.vm, '$emit'); - buttons.at(0).vm.$emit('click'); - - expect(wrapper.vm.$emit).toHaveBeenCalledWith('copy'); - }); - }); }); diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js index 4f7b297aba0..bfcca14324f 100644 --- a/spec/frontend/blob/components/mock_data.js +++ b/spec/frontend/blob/components/mock_data.js @@ -1,29 +1,43 @@ +import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants'; + +export const SimpleViewerMock = { + collapsed: false, + loadingPartialName: 'loading', + renderError: null, + tooLarge: false, + type: SIMPLE_BLOB_VIEWER, + fileType: 'text', +}; + +export const RichViewerMock = { + collapsed: false, + loadingPartialName: 'loading', + renderError: null, + tooLarge: false, + type: RICH_BLOB_VIEWER, + fileType: 'markdown', +}; + export const Blob = { binary: false, - highlightedData: - '
\n

\nAnd has sub-header

\n

Even some stupid text here

', name: 'dummy.md', path: 'dummy.md', rawPath: '/flightjs/flight/snippets/51/raw', size: 75, simpleViewer: { - collapsed: false, - fileType: 'text', - loadAsync: true, - loadingPartialName: 'loading', - renderError: null, - tooLarge: false, - type: 'simple', + ...SimpleViewerMock, }, richViewer: { - collapsed: false, - fileType: 'markup', - loadAsync: true, - loadingPartialName: 'loading', - renderError: null, - tooLarge: false, - type: 'rich', + ...RichViewerMock, }, }; +export const RichBlobContentMock = { + richData: '

Rich

', +}; + +export const SimpleBlobContentMock = { + plainData: 'Plain', +}; + export default {}; diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js index efc1c6dcef9..c4f1dd0ca35 100644 --- a/spec/frontend/snippets/components/snippet_blob_view_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js @@ -1,14 +1,18 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { GlLoadingIcon } from '@gitlab/ui'; import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue'; import BlobHeader from '~/blob/components/blob_header.vue'; import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; +import BlobContent from '~/blob/components/blob_content.vue'; +import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; import { SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC, } from '~/snippets/constants'; +import { Blob as BlobMock, SimpleViewerMock, RichViewerMock } from 'jest/blob/components/mock_data'; + describe('Blob Embeddable', () => { let wrapper; const snippet = { @@ -16,27 +20,42 @@ describe('Blob Embeddable', () => { webUrl: 'https://foo.bar', visibilityLevel: SNIPPET_VISIBILITY_PUBLIC, }; + const dataMock = { + blob: BlobMock, + activeViewerType: SimpleViewerMock.type, + }; - function createComponent(props = {}, loading = false) { + function createComponent( + props = {}, + data = dataMock, + blobLoading = false, + contentLoading = false, + ) { const $apollo = { queries: { blob: { - loading, + loading: blobLoading, + }, + blobContent: { + loading: contentLoading, }, }, }; - wrapper = shallowMount(SnippetBlobView, { + wrapper = mount(SnippetBlobView, { propsData: { snippet: { ...snippet, ...props, }, }, + data() { + return { + ...data, + }; + }, mocks: { $apollo }, }); - - wrapper.vm.$apollo.queries.blob.loading = false; } afterEach(() => { @@ -48,6 +67,7 @@ describe('Blob Embeddable', () => { createComponent(); expect(wrapper.find(BlobEmbeddable).exists()).toBe(true); expect(wrapper.find(BlobHeader).exists()).toBe(true); + expect(wrapper.find(BlobContent).exists()).toBe(true); }); it.each([SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PRIVATE, 'foo'])( @@ -68,9 +88,92 @@ describe('Blob Embeddable', () => { }); it('shows loading icon while blob data is in flight', () => { - createComponent({}, true); + createComponent({}, dataMock, true); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); expect(wrapper.find('.snippet-file-content').exists()).toBe(false); }); + + it('sets simple viewer correctly', () => { + createComponent(); + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + }); + + it('sets rich viewer correctly', () => { + const data = Object.assign({}, dataMock, { + activeViewerType: RichViewerMock.type, + }); + createComponent({}, data); + expect(wrapper.find(RichViewer).exists()).toBe(true); + }); + + it('correctly switches viewer type', () => { + createComponent(); + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + + wrapper.vm.switchViewer(RichViewerMock.type); + + return wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.find(RichViewer).exists()).toBe(true); + wrapper.vm.switchViewer(SimpleViewerMock.type); + }) + .then(() => { + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + }); + }); + + describe('URLS with hash', () => { + beforeEach(() => { + window.location.hash = '#LC2'; + }); + + afterEach(() => { + window.location.hash = ''; + }); + + it('renders simple viewer by default if URL contains hash', () => { + createComponent(); + + expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + }); + + describe('switchViewer()', () => { + it('by default switches to the passed viewer', () => { + createComponent(); + + wrapper.vm.switchViewer(RichViewerMock.type); + return wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type); + expect(wrapper.find(RichViewer).exists()).toBe(true); + + wrapper.vm.switchViewer(SimpleViewerMock.type); + }) + .then(() => { + expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + }); + }); + + it('respects hash over richViewer in the blob when corresponding parameter is passed', () => { + createComponent( + {}, + { + blob: BlobMock, + }, + ); + expect(wrapper.vm.blob.richViewer).toEqual(expect.any(Object)); + + wrapper.vm.switchViewer(RichViewerMock.type, true); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); + expect(wrapper.find(SimpleViewer).exists()).toBe(true); + }); + }); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap new file mode 100644 index 00000000000..87f2a8f9eff --- /dev/null +++ b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = ` +
+ + +
+
+      
+        
+          First
+        
+        
+
+        
+          Second
+        
+        
+
+        
+          Third
+        
+      
+    
+
+
+`; diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js new file mode 100644 index 00000000000..17ea78b5826 --- /dev/null +++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js @@ -0,0 +1,27 @@ +import { shallowMount } from '@vue/test-utils'; +import RichViewer from '~/vue_shared/components/blob_viewers/rich_viewer.vue'; + +describe('Blob Rich Viewer component', () => { + let wrapper; + const content = '

Foo Bar

'; + + function createComponent() { + wrapper = shallowMount(RichViewer, { + propsData: { + content, + }, + }); + } + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the passed content without transformations', () => { + expect(wrapper.html()).toContain(content); + }); +}); diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js new file mode 100644 index 00000000000..d12bfc5c686 --- /dev/null +++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js @@ -0,0 +1,81 @@ +import { shallowMount } from '@vue/test-utils'; +import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue'; +import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants'; + +describe('Blob Simple Viewer component', () => { + let wrapper; + const contentMock = `First\nSecond\nThird`; + + function createComponent(content = contentMock) { + wrapper = shallowMount(SimpleViewer, { + propsData: { + content, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + it('does not fail if content is empty', () => { + const spy = jest.spyOn(window.console, 'error'); + createComponent(''); + expect(spy).not.toHaveBeenCalled(); + }); + + describe('rendering', () => { + beforeEach(() => { + createComponent(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('renders exactly three lines', () => { + expect(wrapper.findAll('.js-line-number')).toHaveLength(3); + }); + + it('renders the content without transformations', () => { + expect(wrapper.html()).toContain(contentMock); + }); + }); + + describe('functionality', () => { + const scrollIntoViewMock = jest.fn(); + HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; + + beforeEach(() => { + window.location.hash = '#LC2'; + createComponent(); + }); + + afterEach(() => { + window.location.hash = ''; + }); + + it('scrolls to requested line when rendered', () => { + const linetoBeHighlighted = wrapper.find('#LC2'); + expect(scrollIntoViewMock).toHaveBeenCalled(); + expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element); + expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME); + }); + + it('switches highlighting when another line is selected', () => { + const currentlyHighlighted = wrapper.find('#LC2'); + const hash = '#LC3'; + const linetoBeHighlighted = wrapper.find(hash); + + expect(wrapper.vm.highlightedLine).toBe(currentlyHighlighted.element); + + wrapper.vm.scrollToLine(hash); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element); + expect(currentlyHighlighted.classes()).not.toContain(HIGHLIGHT_CLASS_NAME); + expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME); + }); + }); + }); +}); diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb index ca0360b363e..b72fbc9fd3c 100644 --- a/spec/helpers/environments_helper_spec.rb +++ b/spec/helpers/environments_helper_spec.rb @@ -20,7 +20,7 @@ describe EnvironmentsHelper do expect(metrics_data).to include( 'settings-path' => edit_project_service_path(project, 'prometheus'), 'clusters-path' => project_clusters_path(project), - 'current-environment-name': environment.name, + 'current-environment-name' => environment.name, 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'), 'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'), 'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'), -- GitLab

\nThis one is dummy