From 10fd79745df7b572fc79fd84b58e818e64bf2571 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 16 Jun 2020 15:08:32 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../monitoring/components/charts/options.js | 2 +- .../monitoring/components/dashboard.vue | 3 +- .../javascripts/monitoring/monitoring_app.js | 59 ++++++++++++++ .../javascripts/monitoring/monitoring_bundle.js | 57 -------------- .../monitoring/pages/dashboard_page.vue | 18 +++++ .../javascripts/monitoring/router/constants.js | 3 + app/assets/javascripts/monitoring/router/index.js | 15 ++++ app/assets/javascripts/monitoring/router/routes.js | 18 +++++ .../pages/projects/environments/metrics/index.js | 4 +- .../components/welcome.vue | 1 + app/assets/stylesheets/framework/gitlab_theme.scss | 51 +++++++++--- app/assets/stylesheets/framework/selects.scss | 1 + app/assets/stylesheets/page_bundles/ide.scss | 7 ++ app/assets/stylesheets/themes/_dark.scss | 2 +- app/controllers/concerns/find_snippet.rb | 24 ++++++ app/controllers/concerns/snippet_authorizations.rb | 23 ++++++ app/controllers/concerns/snippets_actions.rb | 61 ++++++++++++++ .../projects/snippets/application_controller.rb | 19 +++++ app/controllers/projects/snippets_controller.rb | 86 +------------------- app/controllers/projects/tags_controller.rb | 18 ++++- app/controllers/snippets/application_controller.rb | 22 ++++++ app/controllers/snippets_controller.rb | 92 +--------------------- app/helpers/gitlab_routing_helper.rb | 8 ++ app/models/concerns/route_model_query.rb | 19 +++++ app/models/project.rb | 2 +- app/models/redirect_route.rb | 2 + app/models/route.rb | 1 + app/models/snippet.rb | 6 +- app/services/projects/destroy_service.rb | 2 +- app/services/releases/create_evidence_service.rb | 3 +- app/services/releases/create_service.rb | 29 +++++-- app/workers/authorized_projects_worker.rb | 2 +- app/workers/create_evidence_worker.rb | 9 ++- .../unreleased/213587-ide-ipad-scroll-issue.yml | 5 ++ ...5-use-ci-job-token-for-terraform-state-auth.yml | 5 ++ ...ck-slack-notifications-service-doc-outdated.yml | 5 ++ changelogs/unreleased/services-usage-2.yml | 5 ++ doc/ci/variables/predefined_variables.md | 2 +- doc/user/infrastructure/index.md | 39 +++++---- lib/api/terraform/state.rb | 10 +-- lib/api/users.rb | 5 +- lib/gitlab/auth/auth_finders.rb | 1 + lib/gitlab/repo_path.rb | 35 +++++--- package.json | 2 +- qa/qa.rb | 1 + qa/qa/flow/project.rb | 8 ++ qa/qa/page/project/new_experiment.rb | 26 ++++++ qa/qa/resource/project.rb | 4 +- .../repository/push_over_http_file_size_spec.rb | 10 ++- spec/controllers/projects/tags_controller_spec.rb | 66 ++++++++++++++++ .../components/charts/time_series_spec.js | 6 ++ .../monitoring/pages/dashboard_page_spec.js | 36 +++++++++ spec/helpers/gitlab_routing_helper_spec.rb | 10 +++ spec/lib/gitlab/auth/auth_finders_spec.rb | 24 +++++- spec/lib/gitlab/repo_path_spec.rb | 18 ++++- spec/models/concerns/route_model_query_spec.rb | 28 +++++++ spec/models/project_spec.rb | 6 +- spec/models/snippet_spec.rb | 26 ++++++ spec/requests/api/oauth_tokens_spec.rb | 15 +--- spec/requests/api/terraform/state_spec.rb | 73 +++++++++++++---- spec/services/releases/create_service_spec.rb | 74 +++++++++++++++++ spec/support/helpers/api_helpers.rb | 11 --- spec/support/helpers/http_basic_auth_helpers.rb | 26 ++++++ spec/workers/create_evidence_worker_spec.rb | 15 +++- yarn.lock | 8 +- 65 files changed, 917 insertions(+), 357 deletions(-) create mode 100644 app/assets/javascripts/monitoring/monitoring_app.js delete mode 100644 app/assets/javascripts/monitoring/monitoring_bundle.js create mode 100644 app/assets/javascripts/monitoring/pages/dashboard_page.vue create mode 100644 app/assets/javascripts/monitoring/router/constants.js create mode 100644 app/assets/javascripts/monitoring/router/index.js create mode 100644 app/assets/javascripts/monitoring/router/routes.js create mode 100644 app/controllers/concerns/find_snippet.rb create mode 100644 app/controllers/concerns/snippet_authorizations.rb create mode 100644 app/controllers/projects/snippets/application_controller.rb create mode 100644 app/controllers/snippets/application_controller.rb create mode 100644 app/models/concerns/route_model_query.rb create mode 100644 changelogs/unreleased/213587-ide-ipad-scroll-issue.yml create mode 100644 changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml create mode 100644 changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml create mode 100644 changelogs/unreleased/services-usage-2.yml create mode 100644 qa/qa/page/project/new_experiment.rb create mode 100644 spec/frontend/monitoring/pages/dashboard_page_spec.js create mode 100644 spec/models/concerns/route_model_query_spec.rb create mode 100644 spec/support/helpers/http_basic_auth_helpers.rb diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js index e94743309c4..f7822e69b1d 100644 --- a/app/assets/javascripts/monitoring/components/charts/options.js +++ b/app/assets/javascripts/monitoring/components/charts/options.js @@ -81,7 +81,7 @@ export const getTimeAxisOptions = ({ timezone = timezones.LOCAL } = {}) => ({ formatter: date => formatDate(date, { format: formats.shortTime, timezone }), }, axisPointer: { - snap: true, + snap: false, }, }); diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 1e7f7882d2a..f54319d283e 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -85,7 +85,8 @@ export default { }, defaultBranch: { type: String, - required: true, + required: false, + default: '', }, emptyGettingStartedSvgPath: { type: String, diff --git a/app/assets/javascripts/monitoring/monitoring_app.js b/app/assets/javascripts/monitoring/monitoring_app.js new file mode 100644 index 00000000000..08543fa6eb3 --- /dev/null +++ b/app/assets/javascripts/monitoring/monitoring_app.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import { GlToast } from '@gitlab/ui'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import { getParameterValues } from '~/lib/utils/url_utility'; +import { createStore } from './stores'; +import createRouter from './router'; + +Vue.use(GlToast); + +export default (props = {}) => { + const el = document.getElementById('prometheus-graphs'); + + if (el && el.dataset) { + const [currentDashboard] = getParameterValues('dashboard'); + + const { + deploymentsEndpoint, + dashboardEndpoint, + dashboardsEndpoint, + projectPath, + logsPath, + currentEnvironmentName, + dashboardTimezone, + metricsDashboardBasePath, + ...dataProps + } = el.dataset; + + const store = createStore({ + currentDashboard, + deploymentsEndpoint, + dashboardEndpoint, + dashboardsEndpoint, + dashboardTimezone, + projectPath, + logsPath, + currentEnvironmentName, + }); + + // HTML attributes are always strings, parse other types. + dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics); + dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable); + dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable); + + const router = createRouter(metricsDashboardBasePath); + + // eslint-disable-next-line no-new + new Vue({ + el, + store, + router, + data() { + return { + dashboardProps: { ...dataProps, ...props }, + }; + }, + template: ``, + }); + } +}; diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js deleted file mode 100644 index e8783a68fe3..00000000000 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ /dev/null @@ -1,57 +0,0 @@ -import Vue from 'vue'; -import { GlToast } from '@gitlab/ui'; -import Dashboard from '~/monitoring/components/dashboard.vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import { getParameterValues } from '~/lib/utils/url_utility'; -import { createStore } from './stores'; - -Vue.use(GlToast); - -export default (props = {}) => { - const el = document.getElementById('prometheus-graphs'); - - if (el && el.dataset) { - const [currentDashboard] = getParameterValues('dashboard'); - - const { - deploymentsEndpoint, - dashboardEndpoint, - dashboardsEndpoint, - projectPath, - logsPath, - currentEnvironmentName, - dashboardTimezone, - ...dataProps - } = el.dataset; - - const store = createStore({ - currentDashboard, - deploymentsEndpoint, - dashboardEndpoint, - dashboardsEndpoint, - dashboardTimezone, - projectPath, - logsPath, - currentEnvironmentName, - }); - - // HTML attributes are always strings, parse other types. - dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics); - dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable); - dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable); - - // eslint-disable-next-line no-new - new Vue({ - el, - store, - render(createElement) { - return createElement(Dashboard, { - props: { - ...dataProps, - ...props, - }, - }); - }, - }); - } -}; diff --git a/app/assets/javascripts/monitoring/pages/dashboard_page.vue b/app/assets/javascripts/monitoring/pages/dashboard_page.vue new file mode 100644 index 00000000000..519a20d7be3 --- /dev/null +++ b/app/assets/javascripts/monitoring/pages/dashboard_page.vue @@ -0,0 +1,18 @@ + + diff --git a/app/assets/javascripts/monitoring/router/constants.js b/app/assets/javascripts/monitoring/router/constants.js new file mode 100644 index 00000000000..acfcd03f928 --- /dev/null +++ b/app/assets/javascripts/monitoring/router/constants.js @@ -0,0 +1,3 @@ +export const BASE_DASHBOARD_PAGE = 'dashboard'; + +export default {}; diff --git a/app/assets/javascripts/monitoring/router/index.js b/app/assets/javascripts/monitoring/router/index.js new file mode 100644 index 00000000000..12692612bbc --- /dev/null +++ b/app/assets/javascripts/monitoring/router/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue'; +import VueRouter from 'vue-router'; +import routes from './routes'; + +Vue.use(VueRouter); + +export default function createRouter(base) { + const router = new VueRouter({ + base, + mode: 'history', + routes, + }); + + return router; +} diff --git a/app/assets/javascripts/monitoring/router/routes.js b/app/assets/javascripts/monitoring/router/routes.js new file mode 100644 index 00000000000..1e0cc1715a7 --- /dev/null +++ b/app/assets/javascripts/monitoring/router/routes.js @@ -0,0 +1,18 @@ +import DashboardPage from '../pages/dashboard_page.vue'; + +import { BASE_DASHBOARD_PAGE } from './constants'; + +/** + * Because the cluster health page uses the dashboard + * app instead the of the dashboard component, hitting + * `/` route is not possible. Hence using `*` until the + * health page is refactored. + * https://gitlab.com/gitlab-org/gitlab/-/issues/221096 + */ +export default [ + { + name: BASE_DASHBOARD_PAGE, + path: '*', + component: DashboardPage, + }, +]; diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js index 0b644780ad4..d3028aec313 100644 --- a/app/assets/javascripts/pages/projects/environments/metrics/index.js +++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js @@ -1,3 +1,3 @@ -import monitoringBundle from '~/monitoring/monitoring_bundle'; +import monitoringApp from '~/monitoring/monitoring_app'; -document.addEventListener('DOMContentLoaded', monitoringBundle); +document.addEventListener('DOMContentLoaded', monitoringApp); diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue index 8d005c085a2..ea22818da0e 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue @@ -32,6 +32,7 @@ export default { v-for="panel in panels" :key="panel.name" :href="`#${panel.name}`" + :data-qa-selector="`${panel.name}_link`" class="blank-state blank-state-link experiment-new-project-page-blank-state" @click="track('click_tab', { label: panel.name })" > diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 4736fb5fbd1..8d5afe1d312 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -308,7 +308,6 @@ body { ); } - &.gl-dark, &.ui-light { @include gitlab-theme( $gray-700, @@ -391,13 +390,47 @@ body { } &.gl-dark { - @include gitlab-theme( - $gray-900, - $gray-500, - $gray-700, - $gray-800, - $gray-50, - $gray-100 - ); + .logo-text svg { + fill: $gl-text-color; + } + + .navbar-gitlab { + background-color: $gray-50; + + .navbar-sub-nav, + .navbar-nav { + li { + > a:hover, + > a:focus, + > button:hover, + > button:focus { + color: $gl-text-color; + background-color: $gray-200; + } + } + + li.active, + li.dropdown.show { + > a, + > button { + color: $gl-text-color; + background-color: $gray-200; + } + } + } + + .search { + form { + background-color: $gray-100; + box-shadow: inset 0 0 0 1px $border-color; + + &:active, + &:hover { + background-color: $gray-100; + box-shadow: inset 0 0 0 1px $blue-200; + } + } + } + } } } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index c8c7d275339..c2ab6f5b8c5 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -12,6 +12,7 @@ .select2-container.select2-drop-above { .select2-choice { background: $white; + color: $gl-text-color; border-color: $input-border; height: 34px; padding: $gl-vert-padding $gl-input-padding; diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index e96cc9ebf93..9c92f891834 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -25,6 +25,13 @@ $ide-commit-header-height: 48px; @include str-truncated(250px); } +.ide-layout { + // Fix for iOS 13+, the height of the page is actually less than + // 100vh because of the presence of the bottom bar + max-height: 100%; + position: fixed; +} + .ide-view { position: relative; margin-top: 0; diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index 23487a2d5f7..1f2a7645495 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -99,7 +99,7 @@ $border-white-normal: $gray-900; $body-bg: $gray-50; $input-bg: $gray-100; -$input-focus-bg: $gray-50; +$input-focus-bg: $gray-100; $input-color: $gray-900; $input-group-addon-bg: $gray-900; diff --git a/app/controllers/concerns/find_snippet.rb b/app/controllers/concerns/find_snippet.rb new file mode 100644 index 00000000000..d51f1a1b3ad --- /dev/null +++ b/app/controllers/concerns/find_snippet.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module FindSnippet + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize + + private + + # rubocop:disable CodeReuse/ActiveRecord + def snippet + strong_memoize(:snippet) do + snippet_klass.inc_relations_for_view.find_by(id: snippet_id) + end + end + # rubocop:enable CodeReuse/ActiveRecord + + def snippet_klass + raise NotImplementedError + end + + def snippet_id + params[:id] + end +end diff --git a/app/controllers/concerns/snippet_authorizations.rb b/app/controllers/concerns/snippet_authorizations.rb new file mode 100644 index 00000000000..9bbb0cc6faa --- /dev/null +++ b/app/controllers/concerns/snippet_authorizations.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SnippetAuthorizations + extend ActiveSupport::Concern + + private + + def authorize_read_snippet! + return render_404 unless can?(current_user, :read_snippet, snippet) + end + + def authorize_update_snippet! + return render_404 unless can?(current_user, :update_snippet, snippet) + end + + def authorize_admin_snippet! + return render_404 unless can?(current_user, :admin_snippet, snippet) + end + + def authorize_create_snippet! + return render_404 unless can?(current_user, :create_snippet) + end +end diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index e78723bdda2..51fc12398d9 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -3,9 +3,18 @@ module SnippetsActions extend ActiveSupport::Concern include SendsBlob + include RendersNotes + include RendersBlob + include PaginatedCollection + include Gitlab::NoteableMetadata included do + skip_before_action :verify_authenticity_token, + if: -> { action_name == 'show' && js_request? } + before_action :redirect_if_binary, only: [:edit, :update] + + respond_to :html end def edit @@ -43,6 +52,58 @@ module SnippetsActions request.format.js? end + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def show + conditionally_expand_blob(blob) + + respond_to do |format| + format.html do + @note = Note.new(noteable: @snippet, project: @snippet.project) + @noteable = @snippet + + @discussions = @snippet.discussions + @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) + render 'show' + end + + format.json do + render_blob_json(blob) + end + + format.js do + if @snippet.embeddable? + render 'shared/snippets/show' + else + head :not_found + end + end + end + end + + def update + update_params = snippet_params.merge(spammable_params) + + service_response = Snippets::UpdateService.new(@snippet.project, current_user, update_params).execute(@snippet) + @snippet = service_response.payload[:snippet] + + handle_repository_error(:edit) + end + + def destroy + service_response = Snippets::DestroyService.new(current_user, @snippet).execute + + if service_response.success? + redirect_to gitlab_dashboard_snippets_path(@snippet), status: :found + elsif service_response.http_status == 403 + access_denied! + else + redirect_to gitlab_snippet_path(@snippet), + status: :found, + alert: service_response.message + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + private def content_disposition diff --git a/app/controllers/projects/snippets/application_controller.rb b/app/controllers/projects/snippets/application_controller.rb new file mode 100644 index 00000000000..3f488b07e96 --- /dev/null +++ b/app/controllers/projects/snippets/application_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Projects::Snippets::ApplicationController < Projects::ApplicationController + include FindSnippet + include SnippetAuthorizations + + private + + # This overrides the default snippet create authorization + # because ProjectSnippets are checked against the project rather + # than the user + def authorize_create_snippet! + return render_404 unless can?(current_user, :create_snippet, project) + end + + def snippet_klass + ProjectSnippet + end +end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 9233f063f55..5ee6abef804 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,34 +1,19 @@ # frozen_string_literal: true -class Projects::SnippetsController < Projects::ApplicationController - include RendersNotes +class Projects::SnippetsController < Projects::Snippets::ApplicationController + include SnippetsActions include ToggleAwardEmoji include SpammableActions - include SnippetsActions - include RendersBlob - include PaginatedCollection - include Gitlab::NoteableMetadata - - skip_before_action :verify_authenticity_token, - if: -> { action_name == 'show' && js_request? } before_action :check_snippets_available! + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] - # Allow create snippet before_action :authorize_create_snippet!, only: [:new, :create] - - # Allow read any snippet before_action :authorize_read_snippet!, except: [:new, :create, :index] - - # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] - - # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - respond_to :html - def index @snippet_counts = Snippets::CountService .new(current_user, project: @project) @@ -56,61 +41,8 @@ class Projects::SnippetsController < Projects::ApplicationController handle_repository_error(:new) end - def update - update_params = snippet_params.merge(spammable_params) - - service_response = Snippets::UpdateService.new(project, current_user, update_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def show - conditionally_expand_blob(blob) - - respond_to do |format| - format.html do - @note = @project.notes.new(noteable: @snippet) - @noteable = @snippet - - @discussions = @snippet.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - render 'show' - end - - format.json do - render_blob_json(blob) - end - - format.js do - if @snippet.embeddable? - render 'shared/snippets/show' - else - head :not_found - end - end - end - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to project_snippets_path(project), status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to project_snippet_path(project, @snippet), - status: :found, - alert: service_response.message - end - end - protected - def snippet - @snippet ||= @project.snippets.inc_relations_for_view.find(params[:id]) - end alias_method :awardable, :snippet alias_method :spammable, :snippet @@ -118,18 +50,6 @@ class Projects::SnippetsController < Projects::ApplicationController project_snippet_path(@project, @snippet) end - def authorize_read_snippet! - return render_404 unless can?(current_user, :read_snippet, @snippet) - end - - def authorize_update_snippet! - return render_404 unless can?(current_user, :update_snippet, @snippet) - end - - def authorize_admin_snippet! - return render_404 unless can?(current_user, :admin_snippet, @snippet) - end - def snippet_params params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description) end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index c89bfd110c4..df20daa8f7e 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -41,16 +41,20 @@ class Projects::TagsController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def create + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + evidence_pipeline = find_evidence_pipeline + result = ::Tags::CreateService.new(@project, current_user) .execute(params[:tag_name], params[:ref], params[:message]) if result[:status] == :success - # Release creation with Tags was deprecated in GitLab 11.7 + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 if params[:release_description].present? release_params = { tag: params[:tag_name], name: params[:tag_name], - description: params[:release_description] + description: params[:release_description], + evidence_pipeline: evidence_pipeline } Releases::CreateService @@ -93,4 +97,14 @@ class Projects::TagsController < Projects::ApplicationController end end end + + private + + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + def find_evidence_pipeline + evidence_pipeline_sha = @project.repository.commit(params[:ref])&.sha + return unless evidence_pipeline_sha + + @project.ci_pipelines.for_sha(evidence_pipeline_sha).last + end end diff --git a/app/controllers/snippets/application_controller.rb b/app/controllers/snippets/application_controller.rb new file mode 100644 index 00000000000..a533e46a75d --- /dev/null +++ b/app/controllers/snippets/application_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class Snippets::ApplicationController < ApplicationController + include FindSnippet + include SnippetAuthorizations + + private + + def authorize_read_snippet! + return if can?(current_user, :read_snippet, snippet) + + if current_user + render_404 + else + authenticate_user! + end + end + + def snippet_klass + PersonalSnippet + end +end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 425e0458b41..87d87390e57 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,19 +1,12 @@ # frozen_string_literal: true -class SnippetsController < ApplicationController - include RendersNotes - include ToggleAwardEmoji - include SpammableActions +class SnippetsController < Snippets::ApplicationController include SnippetsActions - include RendersBlob include PreviewMarkdown - include PaginatedCollection - include Gitlab::NoteableMetadata - - skip_before_action :verify_authenticity_token, - if: -> { action_name == 'show' && js_request? } + include ToggleAwardEmoji + include SpammableActions - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :authorize_create_snippet!, only: [:new, :create] before_action :authorize_read_snippet!, only: [:show, :raw] @@ -23,7 +16,6 @@ class SnippetsController < ApplicationController skip_before_action :authenticate_user!, only: [:index, :show, :raw] layout 'snippets' - respond_to :html def index if params[:username].present? @@ -60,62 +52,8 @@ class SnippetsController < ApplicationController end end - def update - service_response = Snippets::UpdateService.new(nil, current_user, snippet_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def show - conditionally_expand_blob(blob) - - respond_to do |format| - format.html do - @note = Note.new(noteable: @snippet) - @noteable = @snippet - - @discussions = @snippet.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - render 'show' - end - - format.json do - render_blob_json(blob) - end - - format.js do - if @snippet.embeddable? - render 'shared/snippets/show' - else - head :not_found - end - end - end - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to dashboard_snippets_path, status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to snippet_path(@snippet), - status: :found, - alert: service_response.message - end - end - protected - # rubocop: disable CodeReuse/ActiveRecord - def snippet - @snippet ||= PersonalSnippet.inc_relations_for_view.find_by(id: params[:id]) - end - # rubocop: enable CodeReuse/ActiveRecord - alias_method :awardable, :snippet alias_method :spammable, :snippet @@ -123,28 +61,6 @@ class SnippetsController < ApplicationController snippet_path(@snippet) end - def authorize_read_snippet! - return if can?(current_user, :read_snippet, @snippet) - - if current_user - render_404 - else - authenticate_user! - end - end - - def authorize_update_snippet! - return render_404 unless can?(current_user, :update_snippet, @snippet) - end - - def authorize_admin_snippet! - return render_404 unless can?(current_user, :admin_snippet, @snippet) - end - - def authorize_create_snippet! - return render_404 unless can?(current_user, :create_snippet) - end - def snippet_params params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level, :description).merge(spammable_params) end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 52238f89887..8a9380f4771 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -245,6 +245,14 @@ module GitlabRoutingHelper end end + def gitlab_dashboard_snippets_path(snippet, *args) + if snippet.is_a?(ProjectSnippet) + project_snippets_path(snippet.project, *args) + else + dashboard_snippets_path + end + end + def gitlab_raw_snippet_path(snippet, *args) if snippet.is_a?(ProjectSnippet) raw_project_snippet_path(snippet.project, snippet, *args) diff --git a/app/models/concerns/route_model_query.rb b/app/models/concerns/route_model_query.rb new file mode 100644 index 00000000000..bcf64e1b9d6 --- /dev/null +++ b/app/models/concerns/route_model_query.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Shared scope between Route and RedirectRoute +module RouteModelQuery + extend ActiveSupport::Concern + + class_methods do + def find_source_of_path(path, case_sensitive: true) + scope = + if case_sensitive + where(path: path) + else + where('LOWER(path) = LOWER(?)', path) + end + + scope.first&.source + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index f1758f1a830..845e9e83e78 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1253,7 +1253,7 @@ class Project < ApplicationRecord available_services_names.map do |service_name| find_or_initialize_service(service_name) - end + end.sort_by(&:title) end def disabled_services diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb index 22f60802257..9844e6b5381 100644 --- a/app/models/redirect_route.rb +++ b/app/models/redirect_route.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class RedirectRoute < ApplicationRecord + include RouteModelQuery + belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations validates :source, presence: true diff --git a/app/models/route.rb b/app/models/route.rb index 706589e79b8..db475682245 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -3,6 +3,7 @@ class Route < ApplicationRecord include CaseSensitivity include Gitlab::SQL::Pattern + include RouteModelQuery belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations validates :source, presence: true diff --git a/app/models/snippet.rb b/app/models/snippet.rb index b63ab003711..c8a785eec9f 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -167,7 +167,11 @@ class Snippet < ApplicationRecord end def self.find_by_id_and_project(id:, project:) - Snippet.find_by(id: id, project: project) + if project.is_a?(Project) + ProjectSnippet.find_by(id: id, project: project) + elsif project.nil? + PersonalSnippet.find_by(id: id) + end end def self.max_file_limit(user) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index fd1366d2c4a..2e949f2fc55 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -64,7 +64,7 @@ module Projects end def remove_snippets - response = Snippets::BulkDestroyService.new(current_user, project.snippets).execute + response = ::Snippets::BulkDestroyService.new(current_user, project.snippets).execute response.success? end diff --git a/app/services/releases/create_evidence_service.rb b/app/services/releases/create_evidence_service.rb index b9814778e12..ac13dce1729 100644 --- a/app/services/releases/create_evidence_service.rb +++ b/app/services/releases/create_evidence_service.rb @@ -2,8 +2,9 @@ module Releases class CreateEvidenceService - def initialize(release) + def initialize(release, pipeline: nil) @release = release + @pipeline = pipeline end def execute diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index 2a4cbe2dc4b..92a0b875dd4 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -9,11 +9,16 @@ module Releases return error('Release already exists', 409) if release return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any? + # should be found before the creation of new tag + # because tag creation can spawn new pipeline + # which won't have any data for evidence yet + evidence_pipeline = find_evidence_pipeline + tag = ensure_tag return tag unless tag.is_a?(Gitlab::Git::Tag) - create_release(tag) + create_release(tag, evidence_pipeline) end def find_or_build_release @@ -42,14 +47,14 @@ module Releases Ability.allowed?(current_user, :create_release, project) end - def create_release(tag) + def create_release(tag, evidence_pipeline) release = build_release(tag) release.save! notify_create_release(release) - create_evidence!(release) + create_evidence!(release, evidence_pipeline) success(tag: tag, release: release) rescue => e @@ -73,13 +78,25 @@ module Releases ) end - def create_evidence!(release) + def find_evidence_pipeline + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + return params[:evidence_pipeline] if params[:evidence_pipeline] + + sha = existing_tag&.dereferenced_target&.sha + sha ||= repository.commit(ref)&.sha + + return unless sha + + project.ci_pipelines.for_sha(sha).last + end + + def create_evidence!(release, pipeline) return if release.historical_release? if release.upcoming_release? - CreateEvidenceWorker.perform_at(release.released_at, release.id) + CreateEvidenceWorker.perform_at(release.released_at, release.id, pipeline&.id) else - CreateEvidenceWorker.perform_async(release.id) + CreateEvidenceWorker.perform_async(release.id, pipeline&.id) end end end diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index a35e0320553..62eea8e0462 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -7,8 +7,8 @@ class AuthorizedProjectsWorker feature_category :authentication_and_authorization urgency :high weight 2 - idempotent! + loggable_arguments 1 # For the job waiter key # This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the # visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231 diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb index c8fa9b42f38..b18028e4114 100644 --- a/app/workers/create_evidence_worker.rb +++ b/app/workers/create_evidence_worker.rb @@ -6,10 +6,15 @@ class CreateEvidenceWorker # rubocop:disable Scalability/IdempotentWorker feature_category :release_evidence weight 2 - def perform(release_id) + # pipeline_id is optional for backward compatibility with existing jobs + # caller should always try to provide the pipeline and pass nil only + # if pipeline is absent + def perform(release_id, pipeline_id = nil) release = Release.find_by_id(release_id) return unless release - ::Releases::CreateEvidenceService.new(release).execute + pipeline = Ci::Pipeline.find_by_id(pipeline_id) + + ::Releases::CreateEvidenceService.new(release, pipeline: pipeline).execute end end diff --git a/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml new file mode 100644 index 00000000000..b542581f0f8 --- /dev/null +++ b/changelogs/unreleased/213587-ide-ipad-scroll-issue.yml @@ -0,0 +1,5 @@ +--- +title: Fix issues with scroll on iOS / iPad OS +merge_request: 34486 +author: +type: fixed diff --git a/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml new file mode 100644 index 00000000000..c7fec94b9fb --- /dev/null +++ b/changelogs/unreleased/216785-use-ci-job-token-for-terraform-state-auth.yml @@ -0,0 +1,5 @@ +--- +title: Allow CI_JOB_TOKEN for authenticating to the Terraform state API +merge_request: 34618 +author: +type: added diff --git a/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml new file mode 100644 index 00000000000..7fca5a43e6c --- /dev/null +++ b/changelogs/unreleased/221136-docs-product-feedback-slack-notifications-service-doc-outdated.yml @@ -0,0 +1,5 @@ +--- +title: Fix order of integrations to be sorted alphabetically +merge_request: 34501 +author: +type: fixed diff --git a/changelogs/unreleased/services-usage-2.yml b/changelogs/unreleased/services-usage-2.yml new file mode 100644 index 00000000000..8cc4e96d520 --- /dev/null +++ b/changelogs/unreleased/services-usage-2.yml @@ -0,0 +1,5 @@ +--- +title: Use Keys::DestroyService for deleting an SSH key when an admin deletes a key via the API +merge_request: 34535 +author: Rajendra Kadam +type: fixed diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index 1b64b67d9a8..8463bbeb582 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -64,7 +64,7 @@ You can add a command to your `.gitlab-ci.yml` file to | `CI_JOB_MANUAL` | 8.12 | all | The flag to indicate that job was manually started | | `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` | | `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` | -| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry](../../user/packages/container_registry/index.md) and downloading [dependent repositories](../../user/project/new_ci_build_permissions_model.md#dependent-repositories) | +| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry](../../user/packages/container_registry/index.md), downloading [dependent repositories](../../user/project/new_ci_build_permissions_model.md#dependent-repositories), and accessing [GitLab-managed Terraform state](../../user/infrastructure/index.md#gitlab-managed-terraform-state). | | `CI_JOB_JWT` | 12.10 | all | RS256 JSON web token that can be used for authenticating with third party systems that support JWT authentication, for example [HashiCorp's Vault](../examples/authenticating-with-hashicorp-vault). | | `CI_JOB_URL` | 11.1 | 0.5 | Job details URL | | `CI_KUBERNETES_ACTIVE` | 13.0 | all | Included with the value `true` only if the pipeline has a Kubernetes cluster available for deployments. Not included if no cluster is available. Can be used as an alternative to [`only:kubernetes`/`except:kubernetes`](../yaml/README.md#onlykubernetesexceptkubernetes) with [`rules:if`](../yaml/README.md#rulesif) | diff --git a/doc/user/infrastructure/index.md b/doc/user/infrastructure/index.md index 1166e70fa56..c17d1831b50 100644 --- a/doc/user/infrastructure/index.md +++ b/doc/user/infrastructure/index.md @@ -25,7 +25,7 @@ Amazon S3 or Google Cloud Storage. Its features include: To get started with a GitLab-managed Terraform State, there are two different options: - [Use a local machine](#get-started-using-local-development). -- [Use GitLab CI](#get-started-using-a-gitlab-ci). +- [Use GitLab CI](#get-started-using-gitlab-ci). ## Get started using local development @@ -44,10 +44,15 @@ local machine, this is a simple way to get started: } ``` +1. Create a [Personal Access Token](../profile/personal_access_tokens.md) with + the `api` scope. The Terraform backend is restricted to users with + [Maintainer access](../permissions.md) to the repository. + 1. On your local machine, run `terraform init`, passing in the following options, - replacing `` and `` with the values for - your project. This command initializes your Terraform state, and stores that - state within your GitLab project. This example uses `gitlab.com`: + replacing ``, ``, `` and + `` with the relevant values. This command initializes your + Terraform state, and stores that state within your GitLab project. This example + uses `gitlab.com`: ```shell terraform init \ @@ -61,30 +66,24 @@ local machine, this is a simple way to get started: -backend-config="retry_wait_min=5" ``` -Next, [configure the backend](#configure-the-variables-and-backend). +Next, [configure the backend](#configure-the-backend). -## Get started using a GitLab CI +## Get started using GitLab CI If you don't want to start with local development, you can also use GitLab CI to run your `terraform plan` and `terraform apply` commands. -Next, [configure the backend](#configure-the-variables-and-backend). +Next, [configure the backend](#configure-the-backend). -## Configure the variables and backend +## Configure the backend -After executing the `terraform init` command, you must configure the needed CI -variables, the Terraform backend, and the CI YAML file: +After executing the `terraform init` command, you must configure the Terraform backend +and the CI YAML file: -1. Create a [Personal Access Token](../profile/personal_access_tokens.md) with - the `api` scope. The Terraform backend is restricted to tokens with - [Maintainer access](../permissions.md) to the repository. -1. To keep the Personal Access Token secure, add it as a - [CI/CD environment variable](../../ci/variables/README.md). For the examples on - this page, it's set to the environment variable `GITLAB_TF_PASSWORD`. +CAUTION: **Important:** +The Terraform backend is restricted to users with [Maintainer access](../permissions.md) +to the repository. - CAUTION: **Important:** - If you plan to use the environment variable on an unprotected branch, make sure - to set the variable protection settings correctly. 1. In your Terraform project, define the [HTTP backend](https://www.terraform.io/docs/backends/types/http.html) by adding the following code block in a `.tf` file (such as `backend.tf`) to define the remote backend: @@ -129,7 +128,7 @@ variables, the Terraform backend, and the CI YAML file: before_script: - cd ${TF_ROOT} - terraform --version - - terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=${GITLAB_USER_LOGIN}" -backend-config="password=${GITLAB_TF_PASSWORD}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5" + - terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5" stages: - validate diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index 5141d1fd499..e7c9627c753 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -32,7 +32,7 @@ module API end desc 'Get a terraform state by its name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get do remote_state_handler.find_with_lock do |state| no_content! unless state.file.exists? @@ -44,7 +44,7 @@ module API end desc 'Add a new terraform state or update an existing one' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post do data = request.body.read no_content! if data.empty? @@ -57,7 +57,7 @@ module API end desc 'Delete a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth delete do remote_state_handler.handle_with_lock do |state| state.destroy! @@ -66,7 +66,7 @@ module API end desc 'Lock a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do requires :ID, type: String, limit: 255, desc: 'Terraform state lock ID' requires :Operation, type: String, desc: 'Terraform operation' @@ -103,7 +103,7 @@ module API end desc 'Unlock a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID' end diff --git a/lib/api/users.rb b/lib/api/users.rb index 2b2c753cfc1..3d8ae09edf1 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -306,7 +306,10 @@ module API key = user.keys.find_by(id: params[:key_id]) not_found!('Key') unless key - destroy_conditionally!(key) + destroy_conditionally!(key) do |key| + destroy_service = ::Keys::DestroyService.new(current_user) + destroy_service.execute(key) + end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index b7e78189d37..93342fbad51 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -56,6 +56,7 @@ module Gitlab def find_user_from_job_token return unless route_authentication_setting[:job_token_allowed] + return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth token = current_request.params[JOB_TOKEN_PARAM].presence || current_request.params[RUNNER_JOB_TOKEN_PARAM].presence || diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 67e23624045..6593e741c1c 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -4,6 +4,11 @@ module Gitlab module RepoPath NotFoundError = Class.new(StandardError) + # @return [Array] + # 1. container (ActiveRecord which holds repository) + # 2. project (Project) + # 3. repo_type + # 4. redirected_path def self.parse(path) repo_path = path.sub(/\.git\z/, '').sub(%r{\A/}, '') redirected_path = nil @@ -17,7 +22,7 @@ module Gitlab # `Gitlab::GlRepository::PROJECT` type. next unless type.valid?(repo_path) - # Removing the suffix (.wiki, .design, ...) from the project path + # Removing the suffix (.wiki, .design, ...) from path full_path = repo_path.chomp(type.path_suffix) container, project, redirected_path = find_container(type, full_path) @@ -36,23 +41,31 @@ module Gitlab [snippet, snippet&.project, redirected_path] else - project, redirected_path = find_project(full_path) + container, redirected_path = find_routes_source(full_path) - [project, project, redirected_path] + if container.is_a?(Project) + [container, container, redirected_path] + else + [container, nil, redirected_path] + end end end - def self.find_project(project_path) - return [nil, nil] if project_path.blank? + def self.find_routes_source(path) + return [nil, nil] if path.blank? - project = Project.find_by_full_path(project_path, follow_redirects: true) - redirected_path = redirected?(project, project_path) ? project_path : nil + source = + Route.find_source_of_path(path) || + Route.find_source_of_path(path, case_sensitive: false) || + RedirectRoute.find_source_of_path(path, case_sensitive: false) - [project, redirected_path] + redirected_path = redirected?(source, path) ? path : nil + + [source, redirected_path] end - def self.redirected?(project, project_path) - project && project.full_path.casecmp(project_path) != 0 + def self.redirected?(container, container_path) + container && container.full_path.casecmp(container_path) != 0 end # Snippet_path can be either: @@ -62,7 +75,7 @@ module Gitlab return [nil, nil] if snippet_path.blank? snippet_id, project_path = extract_snippet_info(snippet_path) - project, redirected_path = find_project(project_path) + project, redirected_path = find_routes_source(project_path) [Snippet.find_by_id_and_project(id: snippet_id, project: project), redirected_path] end diff --git a/package.json b/package.json index ae50e672dad..99614c0acc3 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.5", "@gitlab/svgs": "1.139.0", - "@gitlab/ui": "16.10.0", + "@gitlab/ui": "16.12.1", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-1", "@sentry/browser": "^5.10.2", diff --git a/qa/qa.rb b/qa/qa.rb index f9e31ed6a07..4649f452c6f 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -231,6 +231,7 @@ module QA module Project autoload :New, 'qa/page/project/new' + autoload :NewExperiment, 'qa/page/project/new_experiment' autoload :Show, 'qa/page/project/show' autoload :Activity, 'qa/page/project/activity' autoload :Menu, 'qa/page/project/menu' diff --git a/qa/qa/flow/project.rb b/qa/qa/flow/project.rb index 8eddd0f30b2..db42a3a3594 100644 --- a/qa/qa/flow/project.rb +++ b/qa/qa/flow/project.rb @@ -14,6 +14,14 @@ module QA member_settings.add_member(username) end end + + def go_to_create_project_from_template + if Page::Project::NewExperiment.perform(&:shown?) + Page::Project::NewExperiment.perform(&:click_create_from_template_link) + else + Page::Project::New.perform(&:click_create_from_template_tab) + end + end end end end diff --git a/qa/qa/page/project/new_experiment.rb b/qa/qa/page/project/new_experiment.rb new file mode 100644 index 00000000000..813f7f6cefe --- /dev/null +++ b/qa/qa/page/project/new_experiment.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + class NewExperiment < Page::Base + view 'app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue' do + element :blank_project_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern + element :create_from_template_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern + end + + def shown? + has_element? :blank_project_link + end + + def click_blank_project_link + click_element :blank_project_link + end + + def click_create_from_template_link + click_element :create_from_template_link + end + end + end + end +end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index bc276e73404..645f4e97ee0 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -71,12 +71,14 @@ module QA end if @template_name + QA::Flow::Project.go_to_create_project_from_template Page::Project::New.perform do |new_page| - new_page.click_create_from_template_tab new_page.use_template_for_project(@template_name) end end + Page::Project::NewExperiment.perform(&:click_blank_project_link) if Page::Project::NewExperiment.perform(&:shown?) + Page::Project::New.perform do |new_page| new_page.choose_test_namespace new_page.choose_name(@name) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 9b504ad76b4..21ae10774c9 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -3,6 +3,12 @@ module QA context 'Create', :requires_admin do describe 'push after setting the file size limit via admin/application_settings' do + # Note: The file size limits in this test should be greater than the limits in + # ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from + # triggering the limit set in this test (which can happen on Staging where the + # tests are run in parallel). + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/218620#note_361634705 + include Support::Api before(:context) do @@ -31,7 +37,7 @@ module QA end it 'push fails when the file size is above the limit' do - set_file_size_limit(1) + set_file_size_limit(2) retry_on_fail do expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } @@ -52,7 +58,7 @@ module QA output = Resource::Repository::Push.fabricate! do |p| p.repository_http_uri = @project.repository_http_location.uri p.file_name = file_name - p.file_content = SecureRandom.random_bytes(2000000) + p.file_content = SecureRandom.random_bytes(3000000) p.commit_message = commit_message p.new_branch = false end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index 9ca56f58055..122d1b072d0 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Projects::TagsController do let(:project) { create(:project, :public, :repository) } let!(:release) { create(:release, project: project) } let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') } + let(:user) { create(:user) } describe 'GET index' do before do @@ -61,4 +62,69 @@ RSpec.describe Projects::TagsController do end end end + + describe 'POST #create' do + before do + project.add_developer(user) + sign_in(user) + end + + let(:release_description) { nil } + let(:request) do + post(:create, params: { + namespace_id: project.namespace.to_param, + project_id: project, + tag_name: '1.0', + ref: 'master', + release_description: release_description + }) + end + + it 'creates tag' do + request + + expect(response).to have_gitlab_http_status(:found) + expect(project.repository.find_tag('1.0')).to be_present + end + + # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245 + context 'when release description is set' do + let(:release_description) { 'some release description' } + + it 'creates tag and release' do + request + + expect(response).to have_gitlab_http_status(:found) + expect(project.repository.find_tag('1.0')).to be_present + + release = project.releases.find_by_tag!('1.0') + + expect(release).to be_present + expect(release.description).to eq(release_description) + end + + it 'passes the last pipeline for evidence creation', :sidekiq_inline do + sha = project.repository.commit('master').sha + create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline + pipeline = create(:ci_empty_pipeline, sha: sha, project: project) + + # simulating pipeline creation by new tag + expect_any_instance_of(Repository).to receive(:add_tag).and_wrap_original do |m, *args| + create(:ci_empty_pipeline, sha: sha, project: project) + m.call(*args) + end + + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + request + + release = project.releases.find_by_tag!('1.0') + + expect(release).to be_present + expect(release.description).to eq(release_description) + end + end + end end diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index d1afcf7da0d..271b65748ea 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -411,6 +411,12 @@ describe('Time series component', () => { }); }); + describe('xAxis pointer', () => { + it('snap is set to false by default', () => { + expect(getChartOptions().xAxis.axisPointer.snap).toBe(false); + }); + }); + describe('are extended by `option`', () => { const mockSeriesName = 'Extra series 1'; const mockOption = { diff --git a/spec/frontend/monitoring/pages/dashboard_page_spec.js b/spec/frontend/monitoring/pages/dashboard_page_spec.js new file mode 100644 index 00000000000..e3c56ef4cbf --- /dev/null +++ b/spec/frontend/monitoring/pages/dashboard_page_spec.js @@ -0,0 +1,36 @@ +import { shallowMount } from '@vue/test-utils'; +import DashboardPage from '~/monitoring/pages/dashboard_page.vue'; +import Dashboard from '~/monitoring/components/dashboard.vue'; +import { propsData } from '../mock_data'; + +describe('monitoring/pages/dashboard_page', () => { + let wrapper; + + const buildWrapper = (props = {}) => { + wrapper = shallowMount(DashboardPage, { + propsData: { + ...props, + }, + }); + }; + + const findDashboardComponent = () => wrapper.find(Dashboard); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + it('throws errors if dashboard props are not passed', () => { + expect(() => buildWrapper()).toThrow('Missing required prop: "dashboardProps"'); + }); + + it('renders the dashboard page with dashboard component', () => { + buildWrapper({ dashboardProps: propsData }); + + expect(findDashboardComponent().props()).toMatchObject(propsData); + expect(findDashboardComponent()).toExist(); + }); +}); diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 64b23043dac..4def04f4284 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -238,6 +238,16 @@ describe GitlabRoutingHelper do expect(gitlab_toggle_award_emoji_snippet_url(personal_snippet)).to eq("http://test.host/snippets/#{personal_snippet.id}/toggle_award_emoji") end end + + describe '#gitlab_dashboard_snippets_path' do + it 'returns the personal snippets dashboard path' do + expect(gitlab_dashboard_snippets_path(personal_snippet)).to eq("/dashboard/snippets") + end + + it 'returns the project snippets dashboard path' do + expect(gitlab_dashboard_snippets_path(project_snippet)).to eq("/#{project_snippet.project.full_path}/snippets") + end + end end context 'wikis' do diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 774a87752b9..2aef206c7fd 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Auth::AuthFinders do include described_class + include HttpBasicAuthHelpers let(:user) { create(:user) } let(:env) do @@ -22,10 +23,7 @@ describe Gitlab::Auth::AuthFinders do end def set_basic_auth_header(username, password) - set_header( - 'HTTP_AUTHORIZATION', - ActionController::HttpAuthentication::Basic.encode_credentials(username, password) - ) + env.merge!(basic_auth_header(username, password)) end describe '#find_user_from_warden' do @@ -653,6 +651,24 @@ describe Gitlab::Auth::AuthFinders do it_behaves_like 'job token params', described_class::JOB_TOKEN_PARAM it_behaves_like 'job token params', described_class::RUNNER_JOB_TOKEN_PARAM end + + context 'when the job token is provided via basic auth' do + let(:route_authentication_setting) { { job_token_allowed: :basic_auth } } + let(:username) { Ci::Build::CI_REGISTRY_USER } + let(:token) { job.token } + + before do + set_basic_auth_header(username, token) + end + + it { is_expected.to eq(user) } + + context 'credentials are provided but route setting is incorrect' do + let(:route_authentication_setting) { { job_token_allowed: :unknown } } + + it { is_expected.to be_nil } + end + end end describe '#find_runner_from_token' do diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 68571b9de20..6d54342ac46 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -67,11 +67,11 @@ describe ::Gitlab::RepoPath do end end - describe '.find_project' do + describe '.find_routes_source' do context 'when finding a project by its canonical path' do context 'when the cases match' do it 'returns the project and nil' do - expect(described_class.find_project(project.full_path)).to eq([project, nil]) + expect(described_class.find_routes_source(project.full_path)).to eq([project, nil]) end end @@ -81,14 +81,14 @@ describe ::Gitlab::RepoPath do # requests, we should accept wrongly-cased URLs because it is a pain to # block people's git operations and force them to update remote URLs. it 'returns the project and nil' do - expect(described_class.find_project(project.full_path.upcase)).to eq([project, nil]) + expect(described_class.find_routes_source(project.full_path.upcase)).to eq([project, nil]) end end end context 'when finding a project via a redirect' do it 'returns the project and nil' do - expect(described_class.find_project(redirect.path)).to eq([project, redirect.path]) + expect(described_class.find_routes_source(redirect.path)).to eq([project, redirect.path]) end end end @@ -110,6 +110,16 @@ describe ::Gitlab::RepoPath do end end + context 'when path is namespace path, but has same id as project' do + let(:namespace) { build_stubbed(:namespace, id: project.id) } + + it 'returns nil if path is referring to namespace' do + allow(described_class).to receive(:find_route_source).and_return(namespace) + + expect(described_class.find_snippet("#{namespace.full_path}/snippets/#{project_snippet.id}")).to eq([nil, nil]) + end + end + it 'returns nil for snippets not associated with the project' do snippet = create(:project_snippet) diff --git a/spec/models/concerns/route_model_query_spec.rb b/spec/models/concerns/route_model_query_spec.rb new file mode 100644 index 00000000000..ac58c8d44fa --- /dev/null +++ b/spec/models/concerns/route_model_query_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Route, 'RouteModelQuery', :aggregate_failures do + let_it_be(:group1) { create(:group, path: 'Group1') } + let_it_be(:group2) { create(:group, path: 'Group2') } + let_it_be(:project1) { create(:project, path: 'Project1', group: group1) } + let_it_be(:project2) { create(:project, path: 'Project2', group: group2) } + + describe '.find_source_of_path' do + it 'finds exact match' do + expect(described_class.find_source_of_path('Group1')).to eq(group1) + expect(described_class.find_source_of_path('Group2/Project2')).to eq(project2) + + expect(described_class.find_source_of_path('GROUP1')).to be_nil + expect(described_class.find_source_of_path('GROUP2/PROJECT2')).to be_nil + end + + it 'finds case insensitive match' do + expect(described_class.find_source_of_path('Group1', case_sensitive: false)).to eq(group1) + expect(described_class.find_source_of_path('Group2/Project2', case_sensitive: false)).to eq(project2) + + expect(described_class.find_source_of_path('GROUP1', case_sensitive: false)).to eq(group1) + expect(described_class.find_source_of_path('GROUP2/PROJECT2', case_sensitive: false)).to eq(project2) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1a223b6c5bd..9ec306d297e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5227,13 +5227,13 @@ describe Project do describe '#find_or_initialize_services' do it 'returns only enabled services' do - allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover]) + allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity]) allow(subject).to receive(:disabled_services).and_return(%w[prometheus]) services = subject.find_or_initialize_services - expect(services.count).to eq 1 - expect(services).to include(PushoverService) + expect(services.count).to eq(2) + expect(services.map(&:title)).to eq(['JetBrains TeamCity CI', 'Pushover']) end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 4d6586c1df4..22e78c49ce5 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -206,6 +206,32 @@ describe Snippet do end end + describe '.find_by_id_and_project' do + let_it_be(:project) { create(:project) } + let_it_be(:project_snippet) { create(:project_snippet, project: project) } + let_it_be(:personal_snippet) { create(:personal_snippet) } + + context 'when project is provided' do + it 'returns ProjectSnippet' do + expect(described_class.find_by_id_and_project(id: project_snippet.id, project: project)).to eq(project_snippet) + end + end + + context 'when project is nil' do + it 'returns PersonalSnippet' do + expect(described_class.find_by_id_and_project(id: personal_snippet.id, project: nil)).to eq(personal_snippet) + end + end + + context 'when project variable is not a Project' do + let(:namespace) { build_stubbed(:namespace, id: project.id) } + + it 'returns nil' do + expect(described_class.find_by_id_and_project(id: project_snippet.id, project: namespace)).to be_nil + end + end + end + describe '.with_optional_visibility' do context 'when a visibility level is provided' do it 'returns snippets with the given visibility' do diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index 3266fed1741..5e775841f12 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -3,20 +3,9 @@ require 'spec_helper' describe 'OAuth tokens' do - context 'Resource Owner Password Credentials' do - def basic_auth_header(username, password) - { - 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials( - username, - password - ) - } - end - - def client_basic_auth_header(client) - basic_auth_header(client.uid, client.secret) - end + include HttpBasicAuthHelpers + context 'Resource Owner Password Credentials' do def request_oauth_token(user, headers = {}) post '/oauth/token', params: { username: user.username, password: user.password, grant_type: 'password' }, diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb index 844cd948411..ec9db5566e3 100644 --- a/spec/requests/api/terraform/state_spec.rb +++ b/spec/requests/api/terraform/state_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe API::Terraform::State do + include HttpBasicAuthHelpers + let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user, developer_projects: [project]) } let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) } @@ -10,7 +12,7 @@ describe API::Terraform::State do let!(:state) { create(:terraform_state, :with_file, project: project) } let(:current_user) { maintainer } - let(:auth_header) { basic_auth_header(current_user) } + let(:auth_header) { user_basic_auth_header(current_user) } let(:project_id) { project.id } let(:state_name) { state.name } let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}" } @@ -23,7 +25,7 @@ describe API::Terraform::State do subject(:request) { get api(state_path), headers: auth_header } context 'without authentication' do - let(:auth_header) { basic_auth_header('failing_token') } + let(:auth_header) { basic_auth_header('bad', 'token') } it 'returns 401 if user is not authenticated' do request @@ -32,34 +34,71 @@ describe API::Terraform::State do end end - context 'with maintainer permissions' do - let(:current_user) { maintainer } + context 'personal acceess token authentication' do + context 'with maintainer permissions' do + let(:current_user) { maintainer } - it 'returns terraform state belonging to a project of given state name' do - request + it 'returns terraform state belonging to a project of given state name' do + request - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(state.file.read) + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to eq(state.file.read) + end + + context 'for a project that does not exist' do + let(:project_id) { '0000' } + + it 'returns not found' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end end - context 'for a project that does not exist' do - let(:project_id) { '0000' } + context 'with developer permissions' do + let(:current_user) { developer } - it 'returns not found' do + it 'returns forbidden if the user cannot access the state' do request - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:forbidden) end end end - context 'with developer permissions' do - let(:current_user) { developer } + context 'job token authentication' do + let(:auth_header) { job_basic_auth_header(job) } - it 'returns forbidden if the user cannot access the state' do - request + context 'with maintainer permissions' do + let(:job) { create(:ci_build, project: project, user: maintainer) } - expect(response).to have_gitlab_http_status(:forbidden) + it 'returns terraform state belonging to a project of given state name' do + request + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to eq(state.file.read) + end + + context 'for a project that does not exist' do + let(:project_id) { '0000' } + + it 'returns not found' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'with developer permissions' do + let(:job) { create(:ci_build, project: project, user: developer) } + + it 'returns forbidden if the user cannot access the state' do + request + + expect(response).to have_gitlab_http_status(:forbidden) + end end end end diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index ece145dcc4b..4e3d9d5f108 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -188,6 +188,7 @@ describe Releases::CreateService do end context 'Evidence collection' do + let(:sha) { project.repository.commit('master').sha } let(:params) do { name: 'New release', @@ -229,6 +230,75 @@ describe Releases::CreateService do end end + shared_examples 'uses the right pipeline for evidence' do + it 'creates evidence without pipeline if it does not exist', :sidekiq_inline do + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: nil) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { subject }.to change(Releases::Evidence, :count).by(1) + end + + it 'uses the last pipeline for evidence', :sidekiq_inline do + create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline + pipeline = create(:ci_empty_pipeline, sha: sha, project: project) + + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { subject }.to change(Releases::Evidence, :count).by(1) + end + + context 'when old evidence_pipeline is passed to service' do + let!(:old_pipeline) { create(:ci_empty_pipeline, sha: sha, project: project) } + let!(:new_pipeline) { create(:ci_empty_pipeline, sha: sha, project: project) } + let(:params) do + super().merge( + evidence_pipeline: old_pipeline + ) + end + + it 'uses the old pipeline for evidence', :sidekiq_inline do + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: old_pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { subject }.to change(Releases::Evidence, :count).by(1) + end + end + + it 'pipeline is still being used for evidence if new pipeline is being created for tag', :sidekiq_inline do + pipeline = create(:ci_empty_pipeline, sha: sha, project: project) + + expect(project.repository).to receive(:add_tag).and_wrap_original do |m, *args| + create(:ci_empty_pipeline, sha: sha, project: project) + m.call(*args) + end + + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { subject }.to change(Releases::Evidence, :count).by(1) + end + + it 'uses the last pipeline for evidence when tag is already created', :sidekiq_inline do + Tags::CreateService.new(project, user).execute('v0.1', 'master', nil) + + expect(project.repository.find_tag('v0.1')).to be_present + + create(:ci_empty_pipeline, sha: sha, project: project) # old pipeline + pipeline = create(:ci_empty_pipeline, sha: sha, project: project) + + expect_next_instance_of(Releases::CreateEvidenceService, anything, pipeline: pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { subject }.to change(Releases::Evidence, :count).by(1) + end + end + context 'immediate release' do let(:released_at) { nil } @@ -257,6 +327,8 @@ describe Releases::CreateService do expect(last_release.upcoming_release?).to be_falsy end + + include_examples 'uses the right pipeline for evidence' end context 'upcoming release' do @@ -287,6 +359,8 @@ describe Releases::CreateService do expect(last_release.upcoming_release?).to be_truthy end + + include_examples 'uses the right pipeline for evidence' end end end diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index eb9594a4fb6..b1e6078c4f2 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -40,17 +40,6 @@ module ApiHelpers end end - def basic_auth_header(user = nil) - return { 'HTTP_AUTHORIZATION' => user } unless user.respond_to?(:username) - - { - 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials( - user.username, - create(:personal_access_token, user: user).token - ) - } - end - def expect_empty_array_response expect_successful_response_with_paginated_array expect(json_response.length).to eq(0) diff --git a/spec/support/helpers/http_basic_auth_helpers.rb b/spec/support/helpers/http_basic_auth_helpers.rb new file mode 100644 index 00000000000..c0b24b3dfa4 --- /dev/null +++ b/spec/support/helpers/http_basic_auth_helpers.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module HttpBasicAuthHelpers + def user_basic_auth_header(user) + access_token = create(:personal_access_token, user: user) + + basic_auth_header(user.username, access_token.token) + end + + def job_basic_auth_header(job) + basic_auth_header(Ci::Build::CI_REGISTRY_USER, job.token) + end + + def client_basic_auth_header(client) + basic_auth_header(client.uid, client.secret) + end + + def basic_auth_header(username, password) + { + 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials( + username, + password + ) + } + end +end diff --git a/spec/workers/create_evidence_worker_spec.rb b/spec/workers/create_evidence_worker_spec.rb index 8cba0777793..b8c622f7d1d 100644 --- a/spec/workers/create_evidence_worker_spec.rb +++ b/spec/workers/create_evidence_worker_spec.rb @@ -3,13 +3,24 @@ require 'spec_helper' describe CreateEvidenceWorker do - let(:release) { create(:release) } + let(:project) { create(:project, :repository) } + let(:release) { create(:release, project: project) } + let(:pipeline) { create(:ci_empty_pipeline, sha: release.sha, project: project) } + # support old scheduled workers without pipeline it 'creates a new Evidence record' do - expect_next_instance_of(::Releases::CreateEvidenceService, release) do |service| + expect_next_instance_of(::Releases::CreateEvidenceService, release, pipeline: nil) do |service| expect(service).to receive(:execute).and_call_original end expect { described_class.new.perform(release.id) }.to change(Releases::Evidence, :count).by(1) end + + it 'creates a new Evidence record with pipeline' do + expect_next_instance_of(::Releases::CreateEvidenceService, release, pipeline: pipeline) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect { described_class.new.perform(release.id, pipeline.id) }.to change(Releases::Evidence, :count).by(1) + end end diff --git a/yarn.lock b/yarn.lock index d5943bfaf57..3b3766f540a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -840,10 +840,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.139.0.tgz#8a4874e76000e2dd7d3ed3a8967d62bed47d7ea7" integrity sha512-o1KAmQLYL727HodlPHkmj+d+Kdw8OIgHzlKmmPYMzeE+As2l1oz6CTilca56KqXGklOgrouC9P2puMwyX8e/6g== -"@gitlab/ui@16.10.0": - version "16.10.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-16.10.0.tgz#845d6b655baff813ba865e1abb3c318b8cfe2f8a" - integrity sha512-IZj38XWjAsr/kS2hYVONQQHYKzQIHUQ9h/v/qpDd24CnFV9BODf5vh+iCO+vZFejKaDTI22a0d+VRyLjUIL8ag== +"@gitlab/ui@16.12.1": + version "16.12.1" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-16.12.1.tgz#4d6865308596b09e36961210df7a8a489aaadb6d" + integrity sha512-jF6/I71Q0mjHetIRDO8O4VO2KIGWKL/yH2Mdb/CqQKaEasgnc/YpuyHGCsBXqDPxCjRbXPeKp0EywICQx4agZA== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" -- cgit v1.2.3