diff options
155 files changed, 959 insertions, 342 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 5889771806e..84ad1ea742c 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -633,7 +633,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/api/project_statistics.md @aqualls /doc/api/project_templates.md @aqualls /doc/api/project_vulnerabilities.md @aqualls -/doc/api/projects.md @msedlakjakubowski +/doc/api/projects.md @lciutacu /doc/api/protected_branches.md @aqualls /doc/api/protected_environments.md @rdickenson /doc/api/protected_tags.md @aqualls @@ -766,6 +766,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/gitaly.md @eread /doc/development/gitlab_flavored_markdown/ @ashrafkhamis /doc/development/gitlab_flavored_markdown/specification_guide/ @ashrafkhamis +/doc/development/gitlab_shell/ @aqualls /doc/development/graphql_guide/ @ashrafkhamis /doc/development/graphql_guide/batchloader.md @aqualls /doc/development/i18n/ @eread diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue index c6aeb6c726d..9811a0774e1 100644 --- a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue +++ b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue @@ -11,7 +11,7 @@ import { debounce } from 'lodash'; import axios from '~/lib/utils/axios_utils'; import { backOff } from '~/lib/utils/common_utils'; import csrf from '~/lib/utils/csrf'; -import statusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { __, s__ } from '~/locale'; import { queryTypes, formDataValidator } from '../constants'; @@ -23,7 +23,7 @@ function backOffRequest(makeRequestCallback) { return backOff((next, stop) => { makeRequestCallback() .then((resp) => { - if (resp.status === statusCodes.OK) { + if (resp.status === HTTP_STATUS_OK) { stop(resp); } else { next(); diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index b87fe65841b..9f90de9abde 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -9,7 +9,7 @@ import { createAlert, VARIANT_WARNING } from '~/flash'; import { diffViewerModes } from '~/ide/constants'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import Poll from '~/lib/utils/poll'; import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility'; import { __, s__ } from '~/locale'; @@ -248,7 +248,7 @@ export const fetchCoverageFiles = ({ commit, state }) => { data: state.endpointCoverage, method: 'getCoverageReports', successCallback: ({ status, data }) => { - if (status === httpStatusCodes.OK) { + if (status === HTTP_STATUS_OK) { commit(types.SET_COVERAGE_DATA, data); coveragePoll.stop(); diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js index c3adb79bcbe..9ca4efe6dfe 100644 --- a/app/assets/javascripts/lib/utils/http_status.js +++ b/app/assets/javascripts/lib/utils/http_status.js @@ -1,4 +1,5 @@ export const HTTP_STATUS_ABORTED = 0; +export const HTTP_STATUS_OK = 200; export const HTTP_STATUS_CREATED = 201; export const HTTP_STATUS_ACCEPTED = 202; export const HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203; @@ -28,7 +29,7 @@ const httpStatusCodes = { }; export const successCodes = [ - httpStatusCodes.OK, + HTTP_STATUS_OK, HTTP_STATUS_CREATED, HTTP_STATUS_ACCEPTED, HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION, diff --git a/app/assets/javascripts/lib/utils/poll_until_complete.js b/app/assets/javascripts/lib/utils/poll_until_complete.js index 3545db3a227..dbe54dceb52 100644 --- a/app/assets/javascripts/lib/utils/poll_until_complete.js +++ b/app/assets/javascripts/lib/utils/poll_until_complete.js @@ -1,5 +1,5 @@ import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from './http_status'; +import { HTTP_STATUS_OK } from './http_status'; import Poll from './poll'; /** @@ -30,7 +30,7 @@ export default (url, config = {}) => data: { url, config }, method: 'axiosGet', successCallback: (response) => { - if (response.status === httpStatusCodes.OK) { + if (response.status === HTTP_STATUS_OK) { resolve(response); eTagPoll.stop(); } diff --git a/app/assets/javascripts/self_monitor/store/actions.js b/app/assets/javascripts/self_monitor/store/actions.js index 7198dbe8b04..d94fd77dd42 100644 --- a/app/assets/javascripts/self_monitor/store/actions.js +++ b/app/assets/javascripts/self_monitor/store/actions.js @@ -1,6 +1,6 @@ import axios from '~/lib/utils/axios_utils'; import { backOff } from '~/lib/utils/common_utils'; -import statusCodes, { HTTP_STATUS_ACCEPTED } from '~/lib/utils/http_status'; +import { HTTP_STATUS_ACCEPTED, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { __, s__ } from '~/locale'; import * as types from './mutation_types'; @@ -43,7 +43,7 @@ export const requestCreateProject = ({ dispatch, state, commit }) => { export const requestCreateProjectStatus = ({ dispatch, state }, jobId) => { backOffRequest(() => axios.get(state.createProjectStatusEndpoint, { params: { job_id: jobId } })) .then((resp) => { - if (resp.status === statusCodes.OK) { + if (resp.status === HTTP_STATUS_OK) { dispatch('requestCreateProjectSuccess', resp.data); } }) @@ -95,7 +95,7 @@ export const requestDeleteProject = ({ dispatch, state, commit }) => { export const requestDeleteProjectStatus = ({ dispatch, state }, jobId) => { backOffRequest(() => axios.get(state.deleteProjectStatusEndpoint, { params: { job_id: jobId } })) .then((resp) => { - if (resp.status === statusCodes.OK) { + if (resp.status === HTTP_STATUS_OK) { dispatch('requestDeleteProjectSuccess', resp.data); } }) diff --git a/app/assets/javascripts/super_sidebar/components/counter.vue b/app/assets/javascripts/super_sidebar/components/counter.vue index 873d7c48574..d790e61ca31 100644 --- a/app/assets/javascripts/super_sidebar/components/counter.vue +++ b/app/assets/javascripts/super_sidebar/components/counter.vue @@ -6,24 +6,43 @@ export default { GlIcon, }, props: { + count: { + type: Number, + required: true, + }, + href: { + type: String, + required: false, + default: '', + }, icon: { type: String, required: true, }, - count: { - type: Number, + label: { + type: String, required: true, }, }, + computed: { + ariaLabel() { + return `${this.label} ${this.count}`; + }, + component() { + return this.href ? 'a' : 'button'; + }, + }, }; </script> <template> - <a - href="#" + <component + :is="component" + :aria-label="ariaLabel" + :href="href" class="counter gl-relative gl-display-inline-block gl-flex-grow-1 gl-text-center gl-py-3 gl-bg-gray-10 gl-rounded-base gl-text-black-normal gl-border gl-border-gray-a-08 gl-font-sm gl-font-weight-bold" > - <gl-icon :name="icon" /> - {{ count }} - </a> + <gl-icon aria-hidden="true" :name="icon" /> + <span aria-hidden="true">{{ count }}</span> + </component> </template> diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue index e5c29f966c1..e2eac64f5ad 100644 --- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue +++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue @@ -1,6 +1,6 @@ <script> import { GlCollapse } from '@gitlab/ui'; -import { user, counts, context } from '../mock_data'; +import { context } from '../mock_data'; import UserBar from './user_bar.vue'; import ContextSwitcherToggle from './context_switcher_toggle.vue'; import ContextSwitcher from './context_switcher.vue'; @@ -8,8 +8,6 @@ import BottomBar from './bottom_bar.vue'; export default { context, - user, - counts, components: { GlCollapse, UserBar, @@ -17,6 +15,12 @@ export default { ContextSwitcher, BottomBar, }, + props: { + sidebarData: { + type: Object, + required: true, + }, + }, data() { return { contextSwitcherOpened: false, @@ -30,7 +34,7 @@ export default { class="super-sidebar gl-fixed gl-bottom-0 gl-left-0 gl-display-flex gl-flex-direction-column gl-bg-gray-10 gl-border-r gl-border-gray-a-08 gl-z-index-9999" data-testid="super-sidebar" > - <user-bar :user="$options.user" :counts="$options.counts" /> + <user-bar :sidebar-data="sidebarData" /> <div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-overflow-hidden"> <div class="gl-flex-grow-1 gl-overflow-auto"> <context-switcher-toggle :context="$options.context" :expanded="contextSwitcherOpened" /> diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue index 00fcf70929c..7ee1776bf07 100644 --- a/app/assets/javascripts/super_sidebar/components/user_bar.vue +++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue @@ -1,5 +1,6 @@ <script> import { GlAvatar, GlDropdown, GlIcon } from '@gitlab/ui'; +import { __ } from '~/locale'; import SafeHtml from '~/vue_shared/directives/safe_html'; import NewNavToggle from '~/nav/components/new_nav_toggle.vue'; import logo from '../../../../views/shared/_logo.svg'; @@ -14,16 +15,17 @@ export default { NewNavToggle, Counter, }, + i18n: { + issues: __('Issues'), + mergeRequests: __('Merge requests'), + todoList: __('To-Do list'), + }, directives: { SafeHtml, }, inject: ['rootPath', 'toggleNewNavEndpoint'], props: { - user: { - type: Object, - required: true, - }, - counts: { + sidebarData: { type: Object, required: true, }, @@ -47,15 +49,29 @@ export default { </button> <gl-dropdown data-testid="user-dropdown" variant="link" no-caret> <template #button-content> - <gl-avatar :entity-name="user.name" :src="user.avatar_url" :size="32" /> + <gl-avatar :entity-name="sidebarData.name" :src="sidebarData.avatar_url" :size="32" /> </template> <new-nav-toggle :endpoint="toggleNewNavEndpoint" enabled /> </gl-dropdown> </div> <div class="gl-display-flex gl-justify-content-space-between gl-px-3 gl-py-2 gl-gap-2"> - <counter icon="issues" :count="counts.assigned_issues" /> - <counter icon="merge-request-open" :count="counts.assigned_merge_requests" /> - <counter icon="todo-done" :count="counts.pending_todos" /> + <counter + icon="issues" + :count="sidebarData.assigned_open_issues_count" + :href="sidebarData.issues_dashboard_path" + :label="$options.i18n.issues" + /> + <counter + icon="merge-request-open" + :count="sidebarData.assigned_open_merge_requests_count" + :label="$options.i18n.mergeRequests" + /> + <counter + icon="todo-done" + :count="sidebarData.todos_pending_count" + href="/dashboard/todos" + :label="$options.i18n.todoList" + /> </div> </div> </template> diff --git a/app/assets/javascripts/super_sidebar/mock_data.js b/app/assets/javascripts/super_sidebar/mock_data.js index b16a188b94f..0d1ac006df7 100644 --- a/app/assets/javascripts/super_sidebar/mock_data.js +++ b/app/assets/javascripts/super_sidebar/mock_data.js @@ -1,16 +1,5 @@ import { s__ } from '~/locale'; -export const user = { - name: 'GitLab Bot', - avatar_url: '', -}; - -export const counts = { - assigned_issues: 0, - assigned_merge_requests: 4, - pending_todos: 12, -}; - export const context = { title: 'Typeahead.js', link: '/', diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js index 35aa6aff08c..b9c7073df8c 100644 --- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js +++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js @@ -6,7 +6,7 @@ export const initSuperSidebar = () => { if (!el) return false; - const { rootPath, toggleNewNavEndpoint } = el.dataset; + const { rootPath, sidebar, toggleNewNavEndpoint } = el.dataset; return new Vue({ el, @@ -16,7 +16,11 @@ export const initSuperSidebar = () => { toggleNewNavEndpoint, }, render(h) { - return h(SuperSidebar); + return h(SuperSidebar, { + props: { + sidebarData: JSON.parse(sidebar), + }, + }); }, }); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js index 5fd5950859b..c8d969e3adf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js @@ -1,6 +1,6 @@ import Visibility from 'visibilityjs'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import Poll from '~/lib/utils/poll'; import * as types from './mutation_types'; @@ -63,7 +63,7 @@ export const fetchArtifacts = ({ state, dispatch }) => { export const receiveArtifactsSuccess = ({ commit }, response) => { // With 204 we keep polling and don't update the state - if (response.status === httpStatusCodes.OK) { + if (response.status === HTTP_STATUS_OK) { commit(types.RECEIVE_ARTIFACTS_SUCCESS, response.data); } }; diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb index 69a540158c6..442110d1044 100644 --- a/app/controllers/projects/protected_refs_controller.rb +++ b/app/controllers/projects/protected_refs_controller.rb @@ -22,7 +22,10 @@ class Projects::ProtectedRefsController < Projects::ApplicationController flash[:alert] = protected_ref.errors.full_messages.join(', ').html_safe end - redirect_to_repository_settings(@project, anchor: params[:update_section]) + respond_to do |format| + format.html { redirect_to_repository_settings(@project, anchor: params[:update_section]) } + format.json { head :ok } + end end def show diff --git a/app/helpers/protected_branches_helper.rb b/app/helpers/protected_branches_helper.rb new file mode 100644 index 00000000000..07b07bfd33c --- /dev/null +++ b/app/helpers/protected_branches_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ProtectedBranchesHelper + def protected_branch_can_admin_entity?(protected_branch_entity) + if protected_branch_entity.is_a?(Group) + can?(current_user, :admin_group, protected_branch_entity) + else + can?(current_user, :admin_project, protected_branch_entity) + end + end + + def protected_branch_path_by_entity(protected_branch, protected_branch_entity) + if protected_branch_entity.is_a?(Group) + group_protected_branch_path(protected_branch_entity, protected_branch) + else + project_protected_branch_path(protected_branch_entity, protected_branch) + end + end +end diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index cbee02a28c0..f6257c92f3f 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -31,6 +31,18 @@ module SidebarsHelper Sidebars::Groups::Context.new(**context_data) end + def super_sidebar_context(user) + { + name: user.name, + username: user.username, + avatar_url: user.avatar_url, + assigned_open_issues_count: user.assigned_open_issues_count, + assigned_open_merge_requests_count: user.assigned_open_merge_requests_count, + todos_pending_count: user.todos_pending_count, + issues_dashboard_path: issues_dashboard_path(assignee_username: user.username) + } + end + private def sidebar_attributes_for_object(object) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 1753b2a0220..050db3b6870 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -104,6 +104,10 @@ class ProtectedBranch < ApplicationRecord name == project.default_branch end + def entity + group || project + end + private def validate_either_project_or_top_group @@ -111,7 +115,7 @@ class ProtectedBranch < ApplicationRecord errors.add(:base, _('must be associated with a Group or a Project')) elsif project && group errors.add(:base, _('cannot be associated with both a Group and a Project')) - elsif group && group.root_ancestor != group + elsif group && group.subgroup? errors.add(:base, _('cannot be associated with a subgroup')) end end diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index df75c557717..76e620aa3bf 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ProtectedBranch::MergeAccessLevel < ApplicationRecord + include Importable include ProtectedBranchAccess # default value for the access_level column GITLAB_DEFAULT_ACCESS_LEVEL = Gitlab::Access::MAINTAINER diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 6076fab20b7..66fe57be25f 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ProtectedBranch::PushAccessLevel < ApplicationRecord + include Importable include ProtectedBranchAccess # default value for the access_level column GITLAB_DEFAULT_ACCESS_LEVEL = Gitlab::Access::MAINTAINER diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb index 9fcfa7646a2..5d8b1fb4f71 100644 --- a/app/models/protected_tag/create_access_level.rb +++ b/app/models/protected_tag/create_access_level.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ProtectedTag::CreateAccessLevel < ApplicationRecord + include Importable include ProtectedTagAccess def check_access(user) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 9c3cc803587..eb25aeaf5a5 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -157,6 +157,13 @@ module Ci duration >= LOG_MAX_CREATION_THRESHOLD end + + l.log_when do |observations| + pipeline_includes_count = observations['pipeline_includes_count'] + next false unless pipeline_includes_count + + pipeline_includes_count.to_i > Gitlab::Ci::Config::External::Context::MAX_INCLUDES + end end end end diff --git a/app/services/environments/stop_stale_service.rb b/app/services/environments/stop_stale_service.rb index b2f1d74365a..7b7032cb670 100644 --- a/app/services/environments/stop_stale_service.rb +++ b/app/services/environments/stop_stale_service.rb @@ -18,7 +18,7 @@ module Environments ) end - ServiceResponse.success(message: 'Successfully scheduled stale environments to stop') + ServiceResponse.success(message: 'Successfully requested stop for all stale environments') end end end diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml index f1f6dd34401..e1f5802a407 100644 --- a/app/views/admin/application_settings/_kroki.html.haml +++ b/app/views/admin/application_settings/_kroki.html.haml @@ -32,4 +32,4 @@ - kroki_available_formats.each do |format| = f.gitlab_ui_checkbox_component format[:name], format[:label] - = f.submit _('Save changes'), class: "btn gl-button btn-confirm" + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 829e9f508e0..bfa17daf1c2 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -135,7 +135,7 @@ - c.header do = s_('ProjectSettings|Transfer project') - c.body do - = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| + = gitlab_ui_form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| .form-group.row .col-sm-3.col-form-label = f.label :new_namespace_id, _("Namespace") @@ -147,13 +147,13 @@ .form-group.row .offset-sm-3.col-sm-9 - = f.submit _('Transfer'), class: 'gl-button btn btn-confirm' + = f.submit _('Transfer'), pajamas_button: true = render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5 repository-check' }) do |c| - c.header do = _("Repository check") - c.body do - = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f| + = gitlab_ui_form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f| .form-group - if @project.last_repository_check_at.nil? = _("This repository has never been checked.") @@ -167,7 +167,7 @@ = link_to sprite_icon('question-o'), help_page_path('administration/repository_checks') .form-group - = f.submit _('Trigger repository check'), class: 'gl-button btn btn-confirm' + = f.submit _('Trigger repository check'), pajamas_button: true .col-md-6 - if @group diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index fd20ff9a418..f63f1aa9197 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,7 +3,7 @@ .login-box.gl-p-5 .login-body - if @user.two_factor_otp_enabled? - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_u2f_enabled?}" }) do |f| + = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_u2f_enabled?}" }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div @@ -11,6 +11,6 @@ = f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', title: _('This field is required.'), data: { qa_selector: 'two_fa_code_field' } %p.form-text.text-muted.hint= _("Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.") .prepend-top-20 - = f.submit _("Verify code"), class: "gl-button btn btn-confirm", data: { qa_selector: 'verify_code_button' } + = f.submit _("Verify code"), pajamas_button: true, data: { qa_selector: 'verify_code_button' } - if @user.two_factor_webauthn_u2f_enabled? = render "authentication/authenticate", params: params, resource: resource, resource_name: resource_name, render_remember_me: true, target_path: new_user_session_path diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index a3a5fe690a7..b9fe61229bc 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -6,7 +6,7 @@ - if show_omniauth_providers && omniauth_providers_placement == :top = render 'devise/shared/signup_omniauth_providers_top' - = form_for(resource, as: "new_#{resource_name}", url: url, html: { class: 'new_user gl-show-field-errors js-arkose-labs-form', 'aria-live' => 'assertive' }, data: { testid: 'signup-form' }) do |f| + = gitlab_ui_form_for(resource, as: "new_#{resource_name}", url: url, html: { class: 'new_user gl-show-field-errors js-arkose-labs-form', 'aria-live' => 'assertive' }, data: { testid: 'signup-form' }) do |f| .devise-errors = render 'devise/shared/error_messages', resource: resource - if Gitlab::CurrentSettings.invisible_captcha_enabled @@ -72,7 +72,7 @@ = recaptcha_tags nonce: content_security_policy_nonce .submit-container.gl-mt-5 - = f.submit button_text, class: 'btn gl-button btn-confirm gl-display-block gl-w-full', data: { qa_selector: 'new_user_register_button' } + = f.submit button_text, pajamas_button: true, class: 'gl-w-full', data: { qa_selector: 'new_user_register_button' } - if Gitlab::CurrentSettings.sign_in_text.present? && Feature.enabled?(:restyle_login_page, @project) .gl-pt-5 = markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text) diff --git a/app/views/groups/settings/repository/show.html.haml b/app/views/groups/settings/repository/show.html.haml index a15652b3179..c6bf2d66683 100644 --- a/app/views/groups/settings/repository/show.html.haml +++ b/app/views/groups/settings/repository/show.html.haml @@ -8,5 +8,7 @@ = render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description = render "default_branch", group: @group += render_if_exists "protected_branches/protected_branches", protected_branch_entity: @group + - if can?(current_user, :change_push_rules, @group) = render "push_rules" diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index f1d29e77e34..010ddd8da39 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -2,7 +2,8 @@ - @left_sidebar = true .layout-page.hide-when-top-nav-responsive-open{ class: page_with_sidebar_class } - if show_super_sidebar? - %aside.js-super-sidebar.nav-sidebar{ data: { root_path: root_path, toggle_new_nav_endpoint: profile_preferences_url } } + - sidebar_data = super_sidebar_context(current_user).to_json + %aside.js-super-sidebar.nav-sidebar{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } } - elsif defined?(nav) && nav = render "layouts/nav/sidebar/#{nav}" .content-wrapper.content-wrapper-margin{ class: "#{@content_wrapper_class}" } diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 306f24d717b..efb364bd013 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -16,4 +16,4 @@ = render 'shared/import_form', f: f .form-actions - = f.submit 'Start import', class: 'gl-button btn btn-confirm', data: { disable_with: false } + = f.submit 'Start import', pajamas_button: true, data: { disable_with: false } diff --git a/app/views/projects/settings/repository/_protected_branches.html.haml b/app/views/projects/settings/repository/_protected_branches.html.haml index d2356b5df09..340883ba853 100644 --- a/app/views/projects/settings/repository/_protected_branches.html.haml +++ b/app/views/projects/settings/repository/_protected_branches.html.haml @@ -1,2 +1,2 @@ -= render "protected_branches/index" += render "protected_branches/index", protected_branch_entity: protected_branch_entity = render "projects/protected_tags/index" diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 306ce47cee7..953bfcf71ab 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -13,7 +13,7 @@ -# The shared parts of the views can be found in the `shared` directory. -# Those are used throughout the actual views. These `shared` views are then -# reused in EE. -= render "projects/settings/repository/protected_branches" += render "projects/settings/repository/protected_branches", protected_branch_entity: @project = render "shared/deploy_tokens/index", group_or_project: @project, description: deploy_token_description = render @deploy_keys = render "projects/cleanup/show" diff --git a/app/views/protected_branches/_branches_list.html.haml b/app/views/protected_branches/_branches_list.html.haml index 82eac348f16..2b0160f98e7 100644 --- a/app/views/protected_branches/_branches_list.html.haml +++ b/app/views/protected_branches/_branches_list.html.haml @@ -1,4 +1,4 @@ -- can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_entity = protected_branch_can_admin_entity?(protected_branch_entity) -= render layout: 'protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do - = render partial: 'protected_branches/protected_branch', collection: @protected_branches += render layout: 'protected_branches/shared/branches_list', locals: { can_admin_entity: can_admin_entity, protected_branch_entity: protected_branch_entity } do + = render partial: 'protected_branches/protected_branch', collection: @protected_branches, locals: { protected_branch_entity: protected_branch_entity } diff --git a/app/views/protected_branches/_create_protected_branch.html.haml b/app/views/protected_branches/_create_protected_branch.html.haml index 22a49ba9c7e..b4765ab49c2 100644 --- a/app/views/protected_branches/_create_protected_branch.html.haml +++ b/app/views/protected_branches/_create_protected_branch.html.haml @@ -11,4 +11,4 @@ dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'allowed_to_push_dropdown_content' , dropdown_testid: 'allowed-to-push-dropdown', data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes', qa_selector: 'allowed_to_push_dropdown' }}) -= render 'protected_branches/shared/create_protected_branch' += render 'protected_branches/shared/create_protected_branch', protected_branch_entity: protected_branch_entity diff --git a/app/views/protected_branches/_index.html.haml b/app/views/protected_branches/_index.html.haml index 4beca4845b8..84a221555ab 100644 --- a/app/views/protected_branches/_index.html.haml +++ b/app/views/protected_branches/_index.html.haml @@ -1,7 +1,7 @@ - content_for :create_protected_branch do - = render 'protected_branches/create_protected_branch' + = render 'protected_branches/create_protected_branch', protected_branch_entity: protected_branch_entity - content_for :branches_list do - = render "protected_branches/branches_list" + = render "protected_branches/branches_list", protected_branch_entity: protected_branch_entity -= render 'protected_branches/shared/index' += render 'protected_branches/shared/index', protected_branch_entity: protected_branch_entity diff --git a/app/views/protected_branches/_protected_branch.html.haml b/app/views/protected_branches/_protected_branch.html.haml index 423d7f23eb5..acd62968b09 100644 --- a/app/views/protected_branches/_protected_branch.html.haml +++ b/app/views/protected_branches/_protected_branch.html.haml @@ -1,2 +1,2 @@ -= render layout: 'protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch } do += render layout: 'protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch, protected_branch_entity: protected_branch_entity } do = render_if_exists 'protected_branches/update_protected_branch', protected_branch: protected_branch diff --git a/app/views/protected_branches/shared/_branches_list.html.haml b/app/views/protected_branches/shared/_branches_list.html.haml index d041f9c5b48..c35895e000c 100644 --- a/app/views/protected_branches/shared/_branches_list.html.haml +++ b/app/views/protected_branches/shared/_branches_list.html.haml @@ -13,7 +13,7 @@ %col{ width: "20%" } %col{ width: "10%" } %col{ width: "10%" } - - if can_admin_project + - if can_admin_entity %col %thead %tr @@ -28,9 +28,9 @@ %span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Allow all users with push access to force push.'), 'aria-hidden': 'true' } = sprite_icon('question', size: 16, css_class: 'gl-text-gray-500') - = render_if_exists 'protected_branches/ee/code_owner_approval_table_head' + = render_if_exists 'protected_branches/ee/code_owner_approval_table_head', protected_branch_entity: protected_branch_entity - - if can_admin_project + - if can_admin_entity %th %tbody = yield diff --git a/app/views/protected_branches/shared/_create_protected_branch.html.haml b/app/views/protected_branches/shared/_create_protected_branch.html.haml index 6b4a143df69..315daa5e029 100644 --- a/app/views/protected_branches/shared/_create_protected_branch.html.haml +++ b/app/views/protected_branches/shared/_create_protected_branch.html.haml @@ -1,4 +1,4 @@ -= gitlab_ui_form_for [@project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f| += gitlab_ui_form_for [protected_branch_entity, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f| %input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' } = render Pajamas::CardComponent.new(card_options: { class: "gl-mb-5" }) do |c| - c.header do @@ -8,11 +8,18 @@ .form-group.row = f.label :name, s_('ProtectedBranch|Branch:'), class: 'col-sm-12' .col-sm-12 - = render partial: "protected_branches/shared/dropdown", locals: { f: f, toggle_classes: 'gl-w-full! gl-form-input-lg' } + - if protected_branch_entity.is_a?(Group) + = f.text_field :name, placeholder: 'prod*', class: 'form-control gl-w-full! gl-form-input-lg' + - else + = render partial: "protected_branches/shared/dropdown", locals: { f: f, toggle_classes: 'gl-w-full! gl-form-input-lg' } .form-text.text-muted - wildcards_url = help_page_url('user/project/protected_branches', anchor: 'configure-multiple-protected-branches-by-using-a-wildcard') - wildcards_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wildcards_url } - = (s_("ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % { wildcards_link_start: wildcards_link_start, wildcards_link_end: '</a>', code_tag_start: '<code>', code_tag_end: '</code>' }).html_safe + - placeholders = { wildcards_link_start: wildcards_link_start, wildcards_link_end: '</a>', code_tag_start: '<code>', code_tag_end: '</code>' } + - if protected_branch_entity.is_a?(Group) + = (s_("ProtectedBranch|Only %{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe + - else + = (s_("ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe .form-group.row = f.label :merge_access_levels_attributes, s_("ProtectedBranch|Allowed to merge:"), class: 'col-sm-12' .col-sm-12 @@ -30,6 +37,6 @@ - force_push_docs_url = help_page_url('topics/git/git_rebase', anchor: 'force-push') - force_push_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: force_push_docs_url } = (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '</a>' }).html_safe - = render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f + = render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity - c.footer do = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { qa_selector: 'protect_button' }, pajamas_button: true diff --git a/app/views/protected_branches/shared/_index.html.haml b/app/views/protected_branches/shared/_index.html.haml index c204508d355..d0e21e38429 100644 --- a/app/views/protected_branches/shared/_index.html.haml +++ b/app/views/protected_branches/shared/_index.html.haml @@ -1,3 +1,4 @@ +- can_admin_entity = protected_branch_can_admin_entity?(protected_branch_entity) - expanded = expanded_by_default? %section.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded), data: { qa_selector: 'protected_branches_settings_content' } } @@ -14,7 +15,7 @@ = s_("ProtectedBranch|By default, protected branches restrict who can modify the branch.") = link_to s_("ProtectedBranch|Learn more."), help_page_path("user/project/protected_branches", anchor: "who-can-modify-a-protected-branch") - - if can? current_user, :admin_project, @project + - if can_admin_entity = content_for :create_protected_branch = content_for :branches_list diff --git a/app/views/protected_branches/shared/_protected_branch.html.haml b/app/views/protected_branches/shared/_protected_branch.html.haml index 5dea85aaa41..b4fd7a24b41 100644 --- a/app/views/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/protected_branches/shared/_protected_branch.html.haml @@ -1,23 +1,25 @@ -- can_admin_project = can?(current_user, :admin_project, @project) +- can_admin_entity = protected_branch_can_admin_entity?(protected_branch_entity) +- url = protected_branch_path_by_entity(protected_branch, protected_branch_entity) -%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), testid: 'protected-branch' } } +%tr.js-protected-branch-edit-form{ data: { url: url, testid: 'protected-branch' } } %td %span.ref-name= protected_branch.name - - if @project.root_ref?(protected_branch.name) + - if protected_branch_entity.is_a?(Project) && protected_branch_entity.root_ref?(protected_branch.name) = gl_badge_tag s_('ProtectedBranch|default'), variant: :info - %div - - if protected_branch.wildcard? - - matching_branches = protected_branch.matching(repository.branch_names) - = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) - - elsif !protected_branch.commit - %span.text-muted Branch was deleted. + - if protected_branch_entity.is_a?(Project) + %div + - if protected_branch.wildcard? + - matching_branches = protected_branch.matching(repository.branch_names) + = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) + - elsif !protected_branch.commit + %span.text-muted= s_('ProtectedBranch|Branch does not exist.') = yield - = render_if_exists 'protected_branches/ee/code_owner_approval_table', protected_branch: protected_branch + = render_if_exists 'protected_branches/ee/code_owner_approval_table', protected_branch: protected_branch, protected_branch_entity: protected_branch_entity - - if can_admin_project + - if can_admin_entity %td - = link_to s_('ProtectedBranch|Unprotect'), [@project, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger' }, method: :delete, class: "btn gl-button btn-danger btn-sm" + = link_to s_('ProtectedBranch|Unprotect'), [protected_branch_entity, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger' }, method: :delete, class: "btn gl-button btn-danger btn-sm" diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb index 5a53d53ccf9..fb843bd421c 100644 --- a/app/workers/pipeline_schedule_worker.rb +++ b/app/workers/pipeline_schedule_worker.rb @@ -6,19 +6,52 @@ class PipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker data_consistency :always include CronjobQueue + include ::Gitlab::ExclusiveLeaseHelpers + + LOCK_RETRY = 3 + LOCK_TTL = 5.minutes feature_category :continuous_integration worker_resource_boundary :cpu def perform - Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules| - schedules.each do |schedule| - next unless schedule.project + if Feature.enabled?(:ci_use_run_pipeline_schedule_worker) + in_lock(lock_key, **lock_params) do + Ci::PipelineSchedule + .select(:id, :owner_id, :project_id) # Minimize the selected columns + .runnable_schedules + .preloaded + .find_in_batches do |schedules| + RunPipelineScheduleWorker.bulk_perform_async_with_contexts( + schedules, + arguments_proc: ->(schedule) { [schedule.id, schedule.owner_id] }, + context_proc: ->(schedule) { { project: schedule.project, user: schedule.owner } } + ) + end + end + else + Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules| + schedules.each do |schedule| + next unless schedule.project - with_context(project: schedule.project, user: schedule.owner) do - Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule) + with_context(project: schedule.project, user: schedule.owner) do + Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule) + end end end end end + + private + + def lock_key + self.class.name.underscore + end + + def lock_params + { + ttl: LOCK_TTL, + retries: LOCK_RETRY + } + end end diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb index f31f006eec1..f44bc159279 100644 --- a/app/workers/run_pipeline_schedule_worker.rb +++ b/app/workers/run_pipeline_schedule_worker.rb @@ -19,6 +19,12 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker return unless schedule && schedule.project && user + if Feature.enabled?(:ci_use_run_pipeline_schedule_worker) + return if schedule.next_run_at > Time.current + + update_next_run_at_for(schedule) + end + run_pipeline_schedule(schedule, user) end @@ -37,6 +43,12 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker private + def update_next_run_at_for(schedule) + # Ensure `next_run_at` is set properly before creating a pipeline. + # Otherwise, multiple pipelines could be created in a short interval. + schedule.schedule_next_run! + end + def error(schedule, error) failed_creation_counter.increment log_error(schedule, error) diff --git a/config/feature_flags/development/ci_use_run_pipeline_schedule_worker.yml b/config/feature_flags/development/ci_use_run_pipeline_schedule_worker.yml new file mode 100644 index 00000000000..27ab6e221a3 --- /dev/null +++ b/config/feature_flags/development/ci_use_run_pipeline_schedule_worker.yml @@ -0,0 +1,8 @@ +--- +name: ci_use_run_pipeline_schedule_worker +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106661 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378361 +milestone: '15.8' +type: development +group: group::pipeline execution +default_enabled: false diff --git a/db/post_migrate/20221220075936_add_query_index_for_ci_pipeline_schedules.rb b/db/post_migrate/20221220075936_add_query_index_for_ci_pipeline_schedules.rb new file mode 100644 index 00000000000..4fc64f66a83 --- /dev/null +++ b/db/post_migrate/20221220075936_add_query_index_for_ci_pipeline_schedules.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddQueryIndexForCiPipelineSchedules < Gitlab::Database::Migration[2.1] + TABLE_NAME = :ci_pipeline_schedules + INDEX_NAME = :index_ci_pipeline_schedules_on_id_and_next_run_at_and_active + COLUMNS = %i[id next_run_at].freeze + INDEX_CONDITION = 'active = TRUE' + + disable_ddl_transaction! + + def up + add_concurrent_index(TABLE_NAME, COLUMNS, name: INDEX_NAME, where: INDEX_CONDITION) + end + + def down + remove_concurrent_index(TABLE_NAME, COLUMNS, name: INDEX_NAME) + end +end diff --git a/db/schema_migrations/20221220075936 b/db/schema_migrations/20221220075936 new file mode 100644 index 00000000000..194046d51e2 --- /dev/null +++ b/db/schema_migrations/20221220075936 @@ -0,0 +1 @@ +43f0493091c58f1573613d5672a999bf07994ced2b7172a7aef9148f4d8b8dbe
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 7815d81562d..b4a9509ea5b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -28907,6 +28907,8 @@ CREATE INDEX index_ci_pipeline_metadata_on_project_id ON ci_pipeline_metadata US CREATE UNIQUE INDEX index_ci_pipeline_schedule_variables_on_schedule_id_and_key ON ci_pipeline_schedule_variables USING btree (pipeline_schedule_id, key); +CREATE INDEX index_ci_pipeline_schedules_on_id_and_next_run_at_and_active ON ci_pipeline_schedules USING btree (id, next_run_at) WHERE (active = true); + CREATE INDEX index_ci_pipeline_schedules_on_next_run_at_and_active ON ci_pipeline_schedules USING btree (next_run_at, active); CREATE INDEX index_ci_pipeline_schedules_on_owner_id ON ci_pipeline_schedules USING btree (owner_id); diff --git a/doc/api/environments.md b/doc/api/environments.md index 89b4bb6a1de..8bf3d3c8bce 100644 --- a/doc/api/environments.md +++ b/doc/api/environments.md @@ -412,3 +412,28 @@ Example response: "updated_at": "2019-05-27T18:55:13.252Z" } ``` + +## Stop stale environments + +Issue stop request to all environments that were last modified or deployed to before a specified date. Excludes protected environments. Returns `200` if stop request was successful and `400` if the before date is invalid. For details of exactly when the environment is stopped, see [Stop an environment](../ci/environments/index.md#stop-an-environment). + +```plaintext +POST /projects/:id/environments/stop_stale +``` + +| Attribute | Type | Required | Description | +|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user | +| `before` | date | yes | Stop environments that have been modified or deployed to before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). Valid inputs are between 10 years ago and 1 week ago | + +```shell +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/stop_stale?before=10%2F10%2F2021" +``` + +Example response: + +```json +{ + "message": "Successfully requested stop for all stale environments" +} +``` diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md index 14146745d86..46f0a28e945 100644 --- a/doc/api/group_badges.md +++ b/doc/api/group_badges.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/groups.md b/doc/api/groups.md index 40f3d86288c..d0592522029 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/projects.md b/doc/api/projects.md index 4f0b93a9566..716df21149f 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/topics.md b/doc/api/topics.md index 13a5eabf8f9..8acf6bd645d 100644 --- a/doc/api/topics.md +++ b/doc/api/topics.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md index f2a513fdde5..c93100b355a 100644 --- a/doc/ci/environments/index.md +++ b/doc/ci/environments/index.md @@ -436,6 +436,8 @@ There are multiple ways to clean up [dynamic environments](#create-a-dynamic-env - If you do _NOT_ use [merge request pipelines](../pipelines/merge_request_pipelines.md), GitLab stops an environment [when the associated feature branch is deleted](#stop-an-environment-when-a-branch-is-deleted). - If you set [an expiry period to an environment](../yaml/index.md#environmentauto_stop_in), GitLab stops an environment [when it's expired](#stop-an-environment-after-a-certain-time-period). +To stop stale environments, you can [use the API](../../api/environments.md#stop-stale-environments). + #### Stop an environment when a branch is deleted You can configure environments to stop when a branch is deleted. diff --git a/doc/development/approval_rules.md b/doc/development/approval_rules.md index 312bf2b1bb7..f75cf35b32a 100644 --- a/doc/development/approval_rules.md +++ b/doc/development/approval_rules.md @@ -4,7 +4,7 @@ group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Approval Rules development guide **(FREE)** +# Approval Rules development guide This document explains the backend design and flow of all related functionality about [merge request approval rules](../user/project/merge_requests/approvals/index.md). diff --git a/doc/development/auto_devops.md b/doc/development/auto_devops.md index 7a684f64d64..53033cd19ff 100644 --- a/doc/development/auto_devops.md +++ b/doc/development/auto_devops.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Auto DevOps development guide **(FREE)** +# Auto DevOps development guide This document provides a development guide for contributors to [Auto DevOps](../topics/autodevops/index.md). diff --git a/doc/development/cicd/cicd_reference_documentation_guide.md b/doc/development/cicd/cicd_reference_documentation_guide.md index 8c75e66c33a..530bc62b603 100644 --- a/doc/development/cicd/cicd_reference_documentation_guide.md +++ b/doc/development/cicd/cicd_reference_documentation_guide.md @@ -4,7 +4,7 @@ group: Pipeline Authoring info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Documenting the `.gitlab-ci.yml` keywords **(FREE)** +# Documenting the `.gitlab-ci.yml` keywords The [CI/CD YAML reference](../../ci/yaml/index.md) uses a standard style to make it easier to use and update. diff --git a/doc/development/cicd/index.md b/doc/development/cicd/index.md index 73ece709b8d..edc78fc874c 100644 --- a/doc/development/cicd/index.md +++ b/doc/development/cicd/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: index, concepts, howto --- -# CI/CD development documentation **(FREE)** +# CI/CD development documentation Development guides that are specific to CI/CD are listed here: diff --git a/doc/development/cicd/schema.md b/doc/development/cicd/schema.md index 28ab88c6856..26e63fb53d8 100644 --- a/doc/development/cicd/schema.md +++ b/doc/development/cicd/schema.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: index, howto --- -# Contribute to the CI/CD Schema **(FREE)** +# Contribute to the CI/CD Schema The [pipeline editor](../../ci/pipeline_editor/index.md) uses a CI/CD schema to enhance the authoring experience of our CI/CD configuration files. With the CI/CD schema, the editor can: diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md index 6bc6c57e809..4f6799d12d8 100644 --- a/doc/development/cicd/templates.md +++ b/doc/development/cicd/templates.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: index, concepts, howto --- -# Development guide for GitLab CI/CD templates **(FREE)** +# Development guide for GitLab CI/CD templates This document explains how to develop [GitLab CI/CD templates](../../ci/examples/index.md#cicd-templates). diff --git a/doc/development/code_intelligence/index.md b/doc/development/code_intelligence/index.md index f8fb794053d..107a1588e18 100644 --- a/doc/development/code_intelligence/index.md +++ b/doc/development/code_intelligence/index.md @@ -4,7 +4,7 @@ group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Code Intelligence **(FREE)** +# Code Intelligence > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1. diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md index 9e62f019fbf..79d0ff84713 100644 --- a/doc/development/distributed_tracing.md +++ b/doc/development/distributed_tracing.md @@ -4,7 +4,7 @@ group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Distributed Tracing - development guidelines **(FREE)** +# Distributed Tracing - development guidelines GitLab is instrumented for distributed tracing. Distributed Tracing in GitLab is currently considered **experimental**, as it has not yet been tested at scale on GitLab.com. diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md index 88a417b4745..dc69eb2d4f3 100644 --- a/doc/development/elasticsearch.md +++ b/doc/development/elasticsearch.md @@ -4,7 +4,7 @@ group: Global Search info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Elasticsearch knowledge **(PREMIUM SELF)** +# Elasticsearch knowledge This area is to maintain a compendium of useful information when working with Elasticsearch. diff --git a/doc/development/fe_guide/content_editor.md b/doc/development/fe_guide/content_editor.md index 982033cf2ad..152b68bbeb7 100644 --- a/doc/development/fe_guide/content_editor.md +++ b/doc/development/fe_guide/content_editor.md @@ -4,7 +4,7 @@ group: Editor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Content Editor development guidelines **(FREE)** +# Content Editor development guidelines The Content Editor is a UI component that provides a WYSIWYG editing experience for [GitLab Flavored Markdown](../../user/markdown.md) in the GitLab application. diff --git a/doc/development/fe_guide/customizable_dashboards.md b/doc/development/fe_guide/customizable_dashboards.md index 807f83f5bec..f6839460beb 100644 --- a/doc/development/fe_guide/customizable_dashboards.md +++ b/doc/development/fe_guide/customizable_dashboards.md @@ -4,7 +4,7 @@ group: Product Analytics info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Customizable dashboards **(PREMIUM)** +# Customizable dashboards > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98610) in GitLab 15.5 as an [Alpha feature](../../policy/alpha-beta-support.md#alpha-features). diff --git a/doc/development/fe_guide/merge_request_widget_extensions.md b/doc/development/fe_guide/merge_request_widget_extensions.md index d6f4fde3320..5ad918d466b 100644 --- a/doc/development/fe_guide/merge_request_widget_extensions.md +++ b/doc/development/fe_guide/merge_request_widget_extensions.md @@ -4,7 +4,7 @@ group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Merge request widget extensions **(FREE)** +# Merge request widget extensions > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44616) in GitLab 13.6. diff --git a/doc/development/fe_guide/source_editor.md b/doc/development/fe_guide/source_editor.md index 4cfc68553e0..5f2e8c1e029 100644 --- a/doc/development/fe_guide/source_editor.md +++ b/doc/development/fe_guide/source_editor.md @@ -4,7 +4,7 @@ group: Editor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Source Editor **(FREE)** +# Source Editor **Source Editor** provides the editing experience at GitLab. This thin wrapper around [the Monaco editor](https://microsoft.github.io/monaco-editor/) provides necessary diff --git a/doc/development/geo.md b/doc/development/geo.md index ee76c54decc..710d0eec3b0 100644 --- a/doc/development/geo.md +++ b/doc/development/geo.md @@ -4,7 +4,7 @@ group: Geo info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Geo (development) **(PREMIUM SELF)** +# Geo (development) Geo connects GitLab instances together. One GitLab instance is designated as a **primary** site and can be run with multiple diff --git a/doc/development/gitlab_flavored_markdown/index.md b/doc/development/gitlab_flavored_markdown/index.md index f7f1468003c..f115ae9a11c 100644 --- a/doc/development/gitlab_flavored_markdown/index.md +++ b/doc/development/gitlab_flavored_markdown/index.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w <!-- vale gitlab.GitLabFlavoredMarkdown = NO --> -# GitLab Flavored Markdown (GLFM) developer documentation **(FREE)** +# GitLab Flavored Markdown (GLFM) developer documentation This page contains the MVC for the developer documentation for GitLab Flavored Markdown (GLFM). For the user documentation about Markdown in GitLab, refer to diff --git a/doc/development/gitlab_flavored_markdown/specification_guide/index.md b/doc/development/gitlab_flavored_markdown/specification_guide/index.md index e2661145fbc..79a4ac9b49a 100644 --- a/doc/development/gitlab_flavored_markdown/specification_guide/index.md +++ b/doc/development/gitlab_flavored_markdown/specification_guide/index.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w <!-- vale gitlab.GitLabFlavoredMarkdown = NO --> -# GitLab Flavored Markdown (GLFM) Specification Guide **(FREE)** +# GitLab Flavored Markdown (GLFM) Specification Guide ## Summary diff --git a/doc/development/image_scaling.md b/doc/development/image_scaling.md index 502a18fecd7..d182bd8333e 100644 --- a/doc/development/image_scaling.md +++ b/doc/development/image_scaling.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index 1c9144a1163..9fd8fb7eb61 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w description: "GitLab's development guidelines for Integrations" --- -# Integrations development guide **(FREE)** +# Integrations development guide This page provides development guidelines for implementing [GitLab integrations](../../user/project/integrations/index.md), which are part of our [main Rails project](https://gitlab.com/gitlab-org/gitlab). diff --git a/doc/development/integrations/jenkins.md b/doc/development/integrations/jenkins.md index f314f4536e2..6baccdca327 100644 --- a/doc/development/integrations/jenkins.md +++ b/doc/development/integrations/jenkins.md @@ -4,7 +4,7 @@ group: Integrations info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# How to run Jenkins in development environment (on macOS) **(FREE)** +# How to run Jenkins in development environment (on macOS) This is a step by step guide on how to set up [Jenkins](https://www.jenkins.io/) on your local machine and connect to it from your GitLab instance. GitLab triggers webhooks on Jenkins, and Jenkins connects to GitLab using the API. By running both applications on the same machine, we can make sure they are able to access each other. diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md index c7bb77a6a5d..5b460f8723a 100644 --- a/doc/development/integrations/jira_connect.md +++ b/doc/development/integrations/jira_connect.md @@ -4,7 +4,7 @@ group: Integrations info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Set up a development environment **(FREE)** +# Set up a development environment The following are required to install and test the app: diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md index 68d9b88bc05..464cb692790 100644 --- a/doc/development/internal_api/index.md +++ b/doc/development/internal_api/index.md @@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated type: reference, api --- -# Internal API **(FREE)** +# Internal API The internal API is used by different GitLab components, it cannot be used by other consumers. This documentation is intended for people diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md index 332a3c4ab09..e44e2af4371 100644 --- a/doc/development/kubernetes.md +++ b/doc/development/kubernetes.md @@ -4,7 +4,7 @@ group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Kubernetes integration - development guidelines **(FREE)** +# Kubernetes integration - development guidelines This document provides various guidelines when developing for the GitLab [Kubernetes integration](../user/infrastructure/clusters/index.md). diff --git a/doc/development/lfs.md b/doc/development/lfs.md index dd7687cd28b..ec91f9f3c8b 100644 --- a/doc/development/lfs.md +++ b/doc/development/lfs.md @@ -4,7 +4,7 @@ group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Git LFS developer information **(FREE)** +# Git LFS developer information This page contains developer-centric information for GitLab team members. For the user documentation, see [Git Large File Storage](../topics/git/lfs/index.md). diff --git a/doc/development/logging.md b/doc/development/logging.md index 44a47f96de1..6282f0f6677 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -4,7 +4,7 @@ group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# GitLab Developers Guide to Logging **(FREE)** +# GitLab Developers Guide to Logging [GitLab Logs](../administration/logs/index.md) play a critical role for both administrators and GitLab team members to diagnose problems in the field. diff --git a/doc/development/maintenance_mode.md b/doc/development/maintenance_mode.md index b61b7472385..486519715ab 100644 --- a/doc/development/maintenance_mode.md +++ b/doc/development/maintenance_mode.md @@ -4,7 +4,7 @@ group: Geo info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Internal workings of GitLab Maintenance Mode **(PREMIUM SELF)** +# Internal workings of GitLab Maintenance Mode ## Where is Maintenance Mode enforced? diff --git a/doc/development/merge_request_concepts/diffs/development.md b/doc/development/merge_request_concepts/diffs/development.md index 7a7d037ef9e..1c22eff34db 100644 --- a/doc/development/merge_request_concepts/diffs/development.md +++ b/doc/development/merge_request_concepts/diffs/development.md @@ -4,7 +4,7 @@ group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Merge request diffs development guide **(FREE)** +# Merge request diffs development guide This document explains the backend design and flow of merge request diffs. It should help contributors: diff --git a/doc/development/project_templates.md b/doc/development/project_templates.md index 269724c0a7f..55460ec2710 100644 --- a/doc/development/project_templates.md +++ b/doc/development/project_templates.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" --- diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md index 456f2eb50aa..834a20239fc 100644 --- a/doc/development/prometheus_metrics.md +++ b/doc/development/prometheus_metrics.md @@ -4,7 +4,7 @@ group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Working with Prometheus Metrics **(FREE)** +# Working with Prometheus Metrics ## Adding to the library diff --git a/doc/development/sec/index.md b/doc/development/sec/index.md index 3f52020701f..4ed0eadd92f 100644 --- a/doc/development/sec/index.md +++ b/doc/development/sec/index.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: index, concepts, howto --- -# Sec section development **(FREE)** +# Sec section development The Sec section is responsible for GitLab application security features, the "Sec" part of DevSecOps. Development guides that are specific to the Sec section are listed here. diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md index 37e0b753448..40bb03cb5b4 100644 --- a/doc/development/service_ping/index.md +++ b/doc/development/service_ping/index.md @@ -4,7 +4,7 @@ group: Product Intelligence info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Service Ping Guide **(FREE SELF)** +# Service Ping Guide > - Introduced in GitLab Ultimate 11.2, more statistics. > - In GitLab 14.1, [renamed from Usage Ping to Service Ping](https://gitlab.com/groups/gitlab-org/-/epics/5990). In 14.0 and earlier, use the Usage Ping documentation for the Rails commands appropriate to your version. diff --git a/doc/development/wikis.md b/doc/development/wikis.md index 67dc567cc5f..4541b6cca66 100644 --- a/doc/development/wikis.md +++ b/doc/development/wikis.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w description: "GitLab's development guidelines for Wikis" --- -# Wikis development guide **(FREE)** +# Wikis development guide > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227027) in GitLab 13.5. diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md index 117781f7222..3d96eaf793f 100644 --- a/doc/user/admin_area/moderate_users.md +++ b/doc/user/admin_area/moderate_users.md @@ -142,7 +142,7 @@ A deactivated user: - Cannot access Git repositories or the API. - Does not receive any notifications from GitLab. -- Does not be able to use [slash commands](../../integration/slash_commands.md). +- Cannot use [slash commands](../../integration/slash_commands.md). - Does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users). Personal projects, and group and user history of the deactivated user are left intact. diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md index 8d2ae72ba69..4f6e727f673 100644 --- a/doc/user/admin_area/settings/third_party_offers.md +++ b/doc/user/admin_area/settings/third_party_offers.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments type: reference --- diff --git a/doc/user/group/access_and_permissions.md b/doc/user/group/access_and_permissions.md index 3dcfc73072f..4629f33f088 100644 --- a/doc/user/group/access_and_permissions.md +++ b/doc/user/group/access_and_permissions.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 62659938d91..b96ab643c57 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 414b80d0f1d..340d6611685 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index 8f0475885f4..f8d3456648d 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/namespace/index.md b/doc/user/namespace/index.md index 7d26600cc38..bc7ec25b19c 100644 --- a/doc/user/namespace/index.md +++ b/doc/user/namespace/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/profile/contributions_calendar.md b/doc/user/profile/contributions_calendar.md index d66e555970a..49e6319aa12 100644 --- a/doc/user/profile/contributions_calendar.md +++ b/doc/user/profile/contributions_calendar.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments type: concepts, howto --- diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 78a6e8d565f..74e43e855ac 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md index 15de5f6a073..0b0184db14c 100644 --- a/doc/user/project/members/index.md +++ b/doc/user/project/members/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index 938d1fe88a7..d1ee42f723d 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 6c29cba281d..3798643549d 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: 'To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments' type: reference, index, howto --- diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 77a7c48658e..b3a328eba55 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" --- diff --git a/doc/user/public_access.md b/doc/user/public_access.md index acfc28a6f65..2d90013e3b4 100644 --- a/doc/user/public_access.md +++ b/doc/user/public_access.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments type: reference --- diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md index 3eaf3178f3a..372ec141112 100644 --- a/doc/user/reserved_names.md +++ b/doc/user/reserved_names.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md index 4a8b083e3a2..3806c78589a 100644 --- a/doc/user/workspace/index.md +++ b/doc/user/workspace/index.md @@ -1,6 +1,6 @@ --- stage: Manage -group: Workspace +group: Organization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/lib/api/environments.rb b/lib/api/environments.rb index cc3e41e343f..64510a9615a 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -188,6 +188,35 @@ module API present environment, with: Entities::Environment, current_user: current_user end + desc 'Stop stale environments' do + detail 'It returns `200` if stale environment check was scheduled successfully' + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags %w[environments] + end + params do + requires :before, + type: DateTime, + desc: 'Stop all environments that were last modified or deployed to before this date.' + end + post ':id/environments/stop_stale' do + authorize! :stop_environment, user_project + + bad_request!('Invalid Date') if params[:before] < 10.years.ago || params[:before] > 1.week.ago + + service_response = ::Environments::StopStaleService.new(user_project, current_user, params.slice(:before)).execute + + if service_response.error? + status 400 + else + status 200 + end + + present message: service_response.message + end + desc 'Get a specific environment' do success Entities::Environment failure [ diff --git a/lib/banzai/filter/repository_link_filter.rb b/lib/banzai/filter/repository_link_filter.rb index 86beeae01b7..ddc3f5cf715 100644 --- a/lib/banzai/filter/repository_link_filter.rb +++ b/lib/banzai/filter/repository_link_filter.rb @@ -90,14 +90,14 @@ module Banzai end def get_uri(html_attr) - uri = URI(html_attr.value) + uri = Addressable::URI.parse(html_attr.value) uri if uri.relative? && uri.path.present? rescue URI::Error, Addressable::URI::InvalidURIError end def process_link_to_repository_attr(html_attr) - uri = URI(html_attr.value) + uri = Addressable::URI.parse(html_attr.value) if uri.relative? && uri.path.present? html_attr.value = rebuild_relative_uri(uri).to_s diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index 31b130b5ab7..d2dc712e366 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -127,6 +127,10 @@ module Gitlab .observe({ plan: project.actual_plan_name }, jobs_count) end + def observe_pipeline_includes_count(pipeline) + logger.observe(:pipeline_includes_count, pipeline.config_metadata&.[](:includes)&.count, once: true) + end + def increment_pipeline_failure_reason_counter(reason) metrics.pipeline_failure_reason_counter .increment(reason: (reason || :unknown_failure).to_s) diff --git a/lib/gitlab/ci/pipeline/chain/sequence.rb b/lib/gitlab/ci/pipeline/chain/sequence.rb index de147914850..dd097187955 100644 --- a/lib/gitlab/ci/pipeline/chain/sequence.rb +++ b/lib/gitlab/ci/pipeline/chain/sequence.rb @@ -30,6 +30,7 @@ module Gitlab @command.observe_creation_duration(current_monotonic_time - @start) @command.observe_pipeline_size(@pipeline) @command.observe_jobs_count_in_alive_pipelines + @command.observe_pipeline_includes_count(@pipeline) @pipeline end diff --git a/lib/gitlab/import_export/base/relation_object_saver.rb b/lib/gitlab/import_export/base/relation_object_saver.rb index ed3858d0bf4..77b85fc9f15 100644 --- a/lib/gitlab/import_export/base/relation_object_saver.rb +++ b/lib/gitlab/import_export/base/relation_object_saver.rb @@ -71,6 +71,8 @@ module Gitlab invalid_subrelations << invalid_record unless invalid_record.persisted? end + + relation_object.save end end end diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake index ec2ea623e02..b3559bde988 100644 --- a/lib/tasks/gitlab/tw/codeowners.rake +++ b/lib/tasks/gitlab/tw/codeowners.rake @@ -78,7 +78,7 @@ namespace :tw do CodeOwnerRule.new('Tutorials', '@kpaizee'), CodeOwnerRule.new('Utilization', '@fneill'), CodeOwnerRule.new('Vulnerability Research', '@claytoncornell'), - CodeOwnerRule.new('Workspace', '@lciutacu') + CodeOwnerRule.new('Organization', '@lciutacu') ].freeze ERRORS_EXCLUDED_FILES = [ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 91268b87315..3a34cc3a341 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34009,6 +34009,9 @@ msgstr "" msgid "ProtectedBranch|Branch" msgstr "" +msgid "ProtectedBranch|Branch does not exist." +msgstr "" + msgid "ProtectedBranch|Branch will be writable for developers. Are you sure?" msgstr "" @@ -34042,6 +34045,9 @@ msgstr "" msgid "ProtectedBranch|No tags are protected." msgstr "" +msgid "ProtectedBranch|Only %{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported." +msgstr "" + msgid "ProtectedBranch|Protect" msgstr "" @@ -43878,6 +43884,9 @@ msgstr "" msgid "To-Do List" msgstr "" +msgid "To-Do list" +msgstr "" + msgid "To-do item successfully marked as done." msgstr "" diff --git a/package.json b/package.json index 8fb61012c05..0e4ebacbcff 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@cubejs-client/vue": "^0.31.19", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/fonts": "^1.0.1", + "@gitlab/fonts": "^1.1.0", "@gitlab/svgs": "3.14.0", "@gitlab/ui": "52.7.2", "@gitlab/visual-review-tools": "1.7.3", diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb index 14728618633..6778d4100b8 100644 --- a/spec/controllers/projects/protected_branches_controller_spec.rb +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -33,21 +33,26 @@ RSpec.describe Projects::ProtectedBranchesController do let(:create_params) { attributes_for(:protected_branch).merge(access_level_params) } - it 'creates the protected branch rule' do - expect do - post(:create, params: project_params.merge(protected_branch: create_params)) - end.to change(ProtectedBranch, :count).by(1) - end + describe "created successfully" do + using RSpec::Parameterized::TableSyntax - context 'when repository is empty' do - let(:project) { empty_project } + let(:protected_branch) { create(:protected_branch, project: ref_project) } + let(:project_params) { { namespace_id: ref_project.namespace.to_param, project_id: ref_project } } + + subject { post(:create, params: project_params.merge(protected_branch: create_params), format: format) } - it 'creates the protected branch rule' do - expect do - post(:create, params: project_params.merge(protected_branch: create_params)) - end.to change(ProtectedBranch, :count).by(1) + where(:format, :ref_project, :response_status) do + :html | ref(:project) | :found + :html | ref(:empty_project) | :found + :json | ref(:project) | :ok + :json | ref(:empty_project) | :ok + end - expect(response).to have_gitlab_http_status(:found) + with_them do + it 'creates a protected branch' do + expect { subject }.to change(ProtectedBranch, :count).by(1) + expect(response).to have_gitlab_http_status(response_status) + end end end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index c549d99a51f..04096b3e4f9 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -117,7 +117,7 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem set_protected_branch_name('some-branch') click_on "Protect" - within(".protected-branches-list") { expect(page).to have_content('Branch was deleted') } + within(".protected-branches-list") { expect(page).to have_content('Branch does not exist') } end end diff --git a/spec/frontend/analytics/cycle_analytics/store/actions_spec.js b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js index 3f5f1dbb51b..3030fca126b 100644 --- a/spec/frontend/analytics/cycle_analytics/store/actions_spec.js +++ b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import * as actions from '~/analytics/cycle_analytics/store/actions'; import * as getters from '~/analytics/cycle_analytics/store/getters'; -import httpStatusCodes, { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status'; +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { allowedStages, selectedStage, @@ -197,7 +197,7 @@ describe('Project Value Stream Analytics actions', () => { selectedStage, }; mock = new MockAdapter(axios); - mock.onGet(mockStagePath).reply(httpStatusCodes.OK, reviewEvents, headers); + mock.onGet(mockStagePath).reply(HTTP_STATUS_OK, reviewEvents, headers); }); it(`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`, () => @@ -223,7 +223,7 @@ describe('Project Value Stream Analytics actions', () => { selectedStage, }; mock = new MockAdapter(axios); - mock.onGet(mockStagePath).reply(httpStatusCodes.OK, { error: tooMuchDataError }); + mock.onGet(mockStagePath).reply(HTTP_STATUS_OK, { error: tooMuchDataError }); }); it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () => @@ -269,7 +269,7 @@ describe('Project Value Stream Analytics actions', () => { endpoints: mockEndpoints, }; mock = new MockAdapter(axios); - mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + mock.onGet(mockValueStreamPath).reply(HTTP_STATUS_OK); }); it(`commits the 'REQUEST_VALUE_STREAMS' mutation`, () => @@ -337,7 +337,7 @@ describe('Project Value Stream Analytics actions', () => { selectedValueStream, }; mock = new MockAdapter(axios); - mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + mock.onGet(mockValueStreamPath).reply(HTTP_STATUS_OK); }); it(`commits the 'REQUEST_VALUE_STREAM_STAGES' and 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' mutations`, () => @@ -392,7 +392,7 @@ describe('Project Value Stream Analytics actions', () => { stages: allowedStages, }; mock = new MockAdapter(axios); - mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + mock.onGet(mockValueStreamPath).reply(HTTP_STATUS_OK); }); it(`commits the 'REQUEST_STAGE_MEDIANS' and 'RECEIVE_STAGE_MEDIANS_SUCCESS' mutations`, () => @@ -446,11 +446,11 @@ describe('Project Value Stream Analytics actions', () => { mock = new MockAdapter(axios); mock .onGet(mockValueStreamPath) - .replyOnce(httpStatusCodes.OK, { count: 1 }) + .replyOnce(HTTP_STATUS_OK, { count: 1 }) .onGet(mockValueStreamPath) - .replyOnce(httpStatusCodes.OK, { count: 2 }) + .replyOnce(HTTP_STATUS_OK, { count: 2 }) .onGet(mockValueStreamPath) - .replyOnce(httpStatusCodes.OK, { count: 3 }); + .replyOnce(HTTP_STATUS_OK, { count: 3 }); }); it(`commits the 'REQUEST_STAGE_COUNTS' and 'RECEIVE_STAGE_COUNTS_SUCCESS' mutations`, () => diff --git a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js index f4588c199a7..002fe7c6e71 100644 --- a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js +++ b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js @@ -4,10 +4,11 @@ import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_i import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error'; import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { +import { HTTP_STATUS_CONFLICT, HTTP_STATUS_METHOD_NOT_ALLOWED, HTTP_STATUS_NOT_FOUND, + HTTP_STATUS_OK, } from '~/lib/utils/http_status'; jest.mock('~/captcha/wait_for_captcha_to_be_solved'); @@ -47,7 +48,7 @@ describe('registerCaptchaModalInterceptor', () => { } = config.headers; if (captchaResponse === CAPTCHA_RESPONSE && spamLogId === SPAM_LOG_ID) { - return [httpStatusCodes.OK, { ...data, method: config.method, CAPTCHA_SUCCESS }]; + return [HTTP_STATUS_OK, { ...data, method: config.method, CAPTCHA_SUCCESS }]; } return [HTTP_STATUS_CONFLICT, NEEDS_CAPTCHA_RESPONSE]; @@ -65,7 +66,7 @@ describe('registerCaptchaModalInterceptor', () => { it('successful requests are passed through', async () => { const { data, status } = await axios[method]('/endpoint-without-captcha'); - expect(status).toEqual(httpStatusCodes.OK); + expect(status).toEqual(HTTP_STATUS_OK); expect(data).toEqual(AXIOS_RESPONSE); expect(mock.history[method]).toHaveLength(1); }); diff --git a/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js index e54c72a758f..6a6cc3a14de 100644 --- a/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js +++ b/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers'; import { mockLintResponse } from '../mock_data'; @@ -20,7 +20,7 @@ describe('~/ci/pipeline_editor/graphql/resolvers', () => { beforeEach(async () => { mock = new MockAdapter(axios); - mock.onPost(endpoint).reply(httpStatus.OK, mockLintResponse); + mock.onPost(endpoint).reply(HTTP_STATUS_OK, mockLintResponse); result = await resolvers.Mutation.lintCI(null, { endpoint, diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js index b85568e428e..6b804b3b4c6 100644 --- a/spec/frontend/content_editor/extensions/attachment_spec.js +++ b/spec/frontend/content_editor/extensions/attachment_spec.js @@ -8,7 +8,7 @@ import Video from '~/content_editor/extensions/video'; import Link from '~/content_editor/extensions/link'; import Loading from '~/content_editor/extensions/loading'; import { VARIANT_DANGER } from '~/flash'; -import httpStatus, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import eventHubFactory from '~/helpers/event_hub_factory'; import { createTestEditor, createDocBuilder } from '../test_utils'; import { @@ -132,7 +132,7 @@ describe('content_editor/extensions/attachment', () => { }; beforeEach(() => { - mock.onPost().reply(httpStatus.OK, successResponse); + mock.onPost().reply(HTTP_STATUS_OK, successResponse); }); it('inserts a media content with src set to the encoded content and uploading true', async () => { @@ -209,7 +209,7 @@ describe('content_editor/extensions/attachment', () => { }; beforeEach(() => { - mock.onPost().reply(httpStatus.OK, successResponse); + mock.onPost().reply(HTTP_STATUS_OK, successResponse); }); it('inserts a loading mark', async () => { diff --git a/spec/frontend/content_editor/services/upload_helpers_spec.js b/spec/frontend/content_editor/services/upload_helpers_spec.js index ee9333232db..3423e4db3dc 100644 --- a/spec/frontend/content_editor/services/upload_helpers_spec.js +++ b/spec/frontend/content_editor/services/upload_helpers_spec.js @@ -1,7 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { uploadFile } from '~/content_editor/services/upload_helpers'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; describe('content_editor/services/upload_helpers', () => { const uploadsPath = '/uploads'; @@ -26,7 +26,7 @@ describe('content_editor/services/upload_helpers', () => { renderedMarkdown = parseHTML(renderedAttachmentLinkFixture); mock = new MockAdapter(axios); - mock.onPost(uploadsPath, formData).reply(httpStatus.OK, successResponse); + mock.onPost(uploadsPath, formData).reply(HTTP_STATUS_OK, successResponse); renderMarkdown = jest.fn().mockResolvedValue(renderedAttachmentLinkFixture); }); diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index 944cec77efb..ccfc36f8f16 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -18,7 +18,7 @@ import createDiffsStore from '~/diffs/store/modules'; import { diffViewerModes, diffViewerErrors } from '~/ide/constants'; import axios from '~/lib/utils/axios_utils'; import { scrollToElement } from '~/lib/utils/common_utils'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import createNotesStore from '~/notes/stores/modules'; import { getDiffFileMock } from '../mock_data/diff_file'; import diffFileMockDataUnreadable from '../mock_data/diff_file_unreadable'; @@ -436,7 +436,7 @@ describe('DiffFile', () => { describe('loading', () => { it('should have loading icon while loading a collapsed diffs', async () => { const { load_collapsed_diff_url } = store.state.diffs.diffFiles[0]; - axiosMock.onGet(load_collapsed_diff_url).reply(httpStatus.OK, getReadableFile()); + axiosMock.onGet(load_collapsed_diff_url).reply(HTTP_STATUS_OK, getReadableFile()); makeFileAutomaticallyCollapsed(store); wrapper.vm.requestDiff(); @@ -517,7 +517,7 @@ describe('DiffFile', () => { viewer: { name: 'collapsed', automaticallyCollapsed: true }, }; - axiosMock.onGet(file.load_collapsed_diff_url).reply(httpStatus.OK, getReadableFile()); + axiosMock.onGet(file.load_collapsed_diff_url).reply(HTTP_STATUS_OK, getReadableFile()); ({ wrapper, store } = createComponent({ file, props: { viewDiffsFileByFile: true } })); diff --git a/spec/frontend/error_tracking/store/list/actions_spec.js b/spec/frontend/error_tracking/store/list/actions_spec.js index 4a3e0de3ae9..590983bd93d 100644 --- a/spec/frontend/error_tracking/store/list/actions_spec.js +++ b/spec/frontend/error_tracking/store/list/actions_spec.js @@ -4,7 +4,7 @@ import * as actions from '~/error_tracking/store/list/actions'; import * as types from '~/error_tracking/store/list/mutation_types'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status'; +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; jest.mock('~/flash.js'); @@ -23,7 +23,7 @@ describe('error tracking actions', () => { it('should start polling for data', () => { const payload = { errors: [{ id: 1 }, { id: 2 }] }; - mock.onGet().reply(httpStatusCodes.OK, payload); + mock.onGet().reply(HTTP_STATUS_OK, payload); return testAction( actions.startPolling, {}, diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js index 2b9710c9085..a4738fed37e 100644 --- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js +++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js @@ -6,7 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { TEST_HOST } from 'spec/test_constants'; import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; describe('Feature flags > Environments dropdown', () => { let wrapper; @@ -51,7 +51,7 @@ describe('Feature flags > Environments dropdown', () => { describe('on focus', () => { it('sets results with the received data', async () => { - mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(httpStatusCodes.OK, results); + mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(HTTP_STATUS_OK, results); factory(); findEnvironmentSearchInput().vm.$emit('focus'); await waitForPromises(); @@ -63,7 +63,7 @@ describe('Feature flags > Environments dropdown', () => { describe('on keyup', () => { it('sets results with the received data', async () => { - mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(httpStatusCodes.OK, results); + mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(HTTP_STATUS_OK, results); factory(); findEnvironmentSearchInput().vm.$emit('keyup'); await waitForPromises(); @@ -76,7 +76,7 @@ describe('Feature flags > Environments dropdown', () => { describe('on input change', () => { describe('on success', () => { beforeEach(async () => { - mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(httpStatusCodes.OK, results); + mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(HTTP_STATUS_OK, results); factory(); findEnvironmentSearchInput().vm.$emit('focus'); findEnvironmentSearchInput().vm.$emit('input', 'production'); @@ -128,7 +128,7 @@ describe('Feature flags > Environments dropdown', () => { describe('on click create button', () => { beforeEach(async () => { - mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(httpStatusCodes.OK, []); + mock.onGet(`${TEST_HOST}/environments.json'`).replyOnce(HTTP_STATUS_OK, []); factory(); findEnvironmentSearchInput().vm.$emit('focus'); findEnvironmentSearchInput().vm.$emit('input', 'production'); diff --git a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js index 1c0c444c296..b71cdf78207 100644 --- a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js +++ b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js @@ -4,7 +4,7 @@ import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import NewEnvironmentsDropdown from '~/feature_flags/components/new_environments_dropdown.vue'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; const TEST_HOST = '/test'; const TEST_SEARCH = 'production'; @@ -74,7 +74,7 @@ describe('New Environments Dropdown', () => { describe('with results', () => { let items; beforeEach(() => { - axiosMock.onGet(TEST_HOST).reply(httpStatusCodes.OK, ['prod', 'production']); + axiosMock.onGet(TEST_HOST).reply(HTTP_STATUS_OK, ['prod', 'production']); wrapper.findComponent(GlSearchBoxByType).vm.$emit('focus'); wrapper.findComponent(GlSearchBoxByType).vm.$emit('input', 'prod'); return axios.waitForAll().then(() => { diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js index adc4ebcffb8..ce111a0c10c 100644 --- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js +++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js @@ -13,7 +13,7 @@ import updateImportStatusMutation from '~/import_entities/import_groups/graphql/ import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { statusEndpointFixture } from './fixtures'; jest.mock('~/flash'); @@ -52,7 +52,7 @@ describe('Bulk import resolvers', () => { axiosMockAdapter = new MockAdapter(axios); client = createClient(); - axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture); + axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(HTTP_STATUS_OK, statusEndpointFixture); client.watchQuery({ query: bulkImportSourceGroupsQuery }).subscribe(({ data }) => { results = data.bulkImportSourceGroups.nodes; }); @@ -143,7 +143,7 @@ describe('Bulk import resolvers', () => { it('sets import status to CREATED for successful groups when request completes', async () => { axiosMockAdapter .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.OK, [{ success: true, id: 1 }]); + .reply(HTTP_STATUS_OK, [{ success: true, id: 1 }]); await client.mutate({ mutation: importGroupsMutation, @@ -163,7 +163,7 @@ describe('Bulk import resolvers', () => { }); it('sets import status to CREATED for successful groups when request completes with legacy response', async () => { - axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 }); + axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(HTTP_STATUS_OK, { id: 1 }); await client.mutate({ mutation: importGroupsMutation, @@ -186,7 +186,7 @@ describe('Bulk import resolvers', () => { const FAKE_ERROR_MESSAGE = 'foo'; axiosMockAdapter .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.OK, [{ success: false, id: 1, message: FAKE_ERROR_MESSAGE }]); + .reply(HTTP_STATUS_OK, [{ success: false, id: 1, message: FAKE_ERROR_MESSAGE }]); await client.mutate({ mutation: importGroupsMutation, @@ -210,7 +210,7 @@ describe('Bulk import resolvers', () => { it('updateImportStatus updates status', async () => { axiosMockAdapter .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.OK, [{ success: true, id: 1 }]); + .reply(HTTP_STATUS_OK, [{ success: true, id: 1 }]); const NEW_STATUS = 'dummy'; await client.mutate({ diff --git a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js index ceb996926dd..1d1b285c1b6 100644 --- a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js +++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js @@ -3,7 +3,7 @@ import { createAlert } from '~/flash'; import { ERROR_MSG } from '~/incidents_settings/constants'; import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status'; +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { refreshCurrentPage } from '~/lib/utils/url_utility'; jest.mock('~/flash'); @@ -26,7 +26,7 @@ describe('IncidentsSettingsService', () => { describe('updateSettings', () => { it('should refresh the page on successful update', () => { - mock.onPatch().reply(httpStatusCodes.OK); + mock.onPatch().reply(HTTP_STATUS_OK); return service.updateSettings({}).then(() => { expect(refreshCurrentPage).toHaveBeenCalled(); @@ -47,7 +47,7 @@ describe('IncidentsSettingsService', () => { describe('resetWebhookUrl', () => { it('should make a call for webhook update', () => { jest.spyOn(axios, 'post'); - mock.onPost().reply(httpStatusCodes.OK); + mock.onPost().reply(HTTP_STATUS_OK); return service.resetWebhookUrl().then(() => { expect(axios.post).toHaveBeenCalledWith(webhookUpdateEndpoint); diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 1240d48f283..f7f6750e2cd 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -22,7 +22,7 @@ import { billingPlanNames, } from '~/integrations/constants'; import { createStore } from '~/integrations/edit/store'; -import httpStatus, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { refreshCurrentPage } from '~/lib/utils/url_utility'; import { mockIntegrationProps, @@ -458,9 +458,9 @@ describe('IntegrationForm', () => { describe.each` scenario | replyStatus | errorMessage | serviceResponse | expectToast | expectSentry ${'when "test settings" request fails'} | ${HTTP_STATUS_INTERNAL_SERVER_ERROR} | ${undefined} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true} - ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${undefined} | ${'an error'} | ${false} - ${'when "test settings" returns an error with details'} | ${httpStatus.OK} | ${'an error.'} | ${'extra info'} | ${'an error. extra info'} | ${false} - ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false} + ${'when "test settings" returns an error'} | ${HTTP_STATUS_OK} | ${'an error'} | ${undefined} | ${'an error'} | ${false} + ${'when "test settings" returns an error with details'} | ${HTTP_STATUS_OK} | ${'an error.'} | ${'extra info'} | ${'an error. extra info'} | ${false} + ${'when "test settings" succeeds'} | ${HTTP_STATUS_OK} | ${undefined} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false} `( '$scenario', ({ replyStatus, errorMessage, serviceResponse, expectToast, expectSentry }) => { @@ -526,7 +526,7 @@ describe('IntegrationForm', () => { describe('when "reset settings" succeeds', () => { beforeEach(async () => { - mockAxios.onPost(mockResetPath).replyOnce(httpStatus.OK); + mockAxios.onPost(mockResetPath).replyOnce(HTTP_STATUS_OK); createComponent({ customStateProps: { resetPath: mockResetPath, diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js index faccc2ca050..fdb728281b5 100644 --- a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js +++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js @@ -8,7 +8,7 @@ import IntegrationOverrides from '~/integrations/overrides/components/integratio import IntegrationTabs from '~/integrations/overrides/components/integration_tabs.vue'; import axios from '~/lib/utils/axios_utils'; -import httpStatus, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue'; @@ -39,7 +39,7 @@ describe('IntegrationOverrides', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, mockOverrides, { + mockAxios.onGet(defaultProps.overridesPath).reply(HTTP_STATUS_OK, mockOverrides, { 'X-TOTAL': mockOverrides.length, 'X-PAGE': 1, }); @@ -150,7 +150,7 @@ describe('IntegrationOverrides', () => { describe('pagination', () => { describe('when total items does not exceed the page limit', () => { it('does not render', async () => { - mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { + mockAxios.onGet(defaultProps.overridesPath).reply(HTTP_STATUS_OK, [mockOverrides[0]], { 'X-TOTAL': DEFAULT_PER_PAGE - 1, 'X-PAGE': 1, }); @@ -169,7 +169,7 @@ describe('IntegrationOverrides', () => { beforeEach(async () => { createComponent({ stubs: { UrlSync } }); - mockAxios.onGet(defaultProps.overridesPath).reply(httpStatus.OK, [mockOverrides[0]], { + mockAxios.onGet(defaultProps.overridesPath).reply(HTTP_STATUS_OK, [mockOverrides[0]], { 'X-TOTAL': DEFAULT_PER_PAGE * 2, 'X-PAGE': mockPage, }); diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js index c472bfc42e8..b04a6c0b8fd 100644 --- a/spec/frontend/issuable/components/issuable_by_email_spec.js +++ b/spec/frontend/issuable/components/issuable_by_email_spec.js @@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; -import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; const initialEmail = 'user@gitlab.com'; @@ -130,7 +130,7 @@ describe('IssuableByEmail', () => { }); it('should update the email when the request succeeds', async () => { - mockAxios.onPut(resetPath).reply(httpStatus.OK, { new_address: 'foo@bar.com' }); + mockAxios.onPut(resetPath).reply(HTTP_STATUS_OK, { new_address: 'foo@bar.com' }); wrapper = createComponent({ issuableType: 'issue', diff --git a/spec/frontend/jira_connect/subscriptions/api_spec.js b/spec/frontend/jira_connect/subscriptions/api_spec.js index cf496d5836a..21636017f10 100644 --- a/spec/frontend/jira_connect/subscriptions/api_spec.js +++ b/spec/frontend/jira_connect/subscriptions/api_spec.js @@ -9,7 +9,7 @@ import { updateInstallation, } from '~/jira_connect/subscriptions/api'; import { getJwt } from '~/jira_connect/subscriptions/utils'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; jest.mock('~/jira_connect/subscriptions/utils', () => ({ getJwt: jest.fn().mockResolvedValue('jwt'), @@ -49,7 +49,7 @@ describe('JiraConnect API', () => { jwt: mockJwt, namespace_path: mockNamespace, }) - .replyOnce(httpStatus.OK, mockResponse); + .replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); @@ -67,7 +67,7 @@ describe('JiraConnect API', () => { it('returns success response', async () => { jest.spyOn(axiosInstance, 'delete'); - axiosMock.onDelete(mockRemovePath).replyOnce(httpStatus.OK, mockResponse); + axiosMock.onDelete(mockRemovePath).replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); @@ -99,7 +99,7 @@ describe('JiraConnect API', () => { page: mockPage, per_page: mockPerPage, }) - .replyOnce(httpStatus.OK, mockResponse); + .replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); @@ -121,7 +121,7 @@ describe('JiraConnect API', () => { jest.spyOn(axiosInstance, 'get'); - axiosMock.onGet(expectedUrl).replyOnce(httpStatus.OK, mockResponse); + axiosMock.onGet(expectedUrl).replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); @@ -139,7 +139,7 @@ describe('JiraConnect API', () => { jest.spyOn(axiosInstance, 'post'); - axiosMock.onPost(expectedUrl).replyOnce(httpStatus.OK, mockResponse); + axiosMock.onPost(expectedUrl).replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); @@ -175,7 +175,7 @@ describe('JiraConnect API', () => { instance_url: expectedInstanceUrl, }, }) - .replyOnce(httpStatus.OK, mockResponse); + .replyOnce(HTTP_STATUS_OK, mockResponse); response = await makeRequest(); diff --git a/spec/frontend/jobs/components/job/sidebar_spec.js b/spec/frontend/jobs/components/job/sidebar_spec.js index 27911eb76eb..aa9ca932023 100644 --- a/spec/frontend/jobs/components/job/sidebar_spec.js +++ b/spec/frontend/jobs/components/job/sidebar_spec.js @@ -3,7 +3,7 @@ import { nextTick } from 'vue'; import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import ArtifactsBlock from '~/jobs/components/job/sidebar/artifacts_block.vue'; import JobRetryForwardDeploymentModal from '~/jobs/components/job/sidebar/job_retry_forward_deployment_modal.vue'; import JobsContainer from '~/jobs/components/job/sidebar/jobs_container.vue'; @@ -43,7 +43,7 @@ describe('Sidebar details block', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet().reply(httpStatus.OK, { + mock.onGet().reply(HTTP_STATUS_OK, { name: job.stage, }); }); diff --git a/spec/frontend/lib/utils/poll_until_complete_spec.js b/spec/frontend/lib/utils/poll_until_complete_spec.js index e8ca2bddc4e..309e0cc540b 100644 --- a/spec/frontend/lib/utils/poll_until_complete_spec.js +++ b/spec/frontend/lib/utils/poll_until_complete_spec.js @@ -1,9 +1,10 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { +import { HTTP_STATUS_NO_CONTENT, HTTP_STATUS_NOT_FOUND, + HTTP_STATUS_OK, } from '~/lib/utils/http_status'; import pollUntilComplete from '~/lib/utils/poll_until_complete'; @@ -27,7 +28,7 @@ describe('pollUntilComplete', () => { describe('given an immediate success response', () => { beforeEach(() => { - mock.onGet(endpoint).replyOnce(httpStatusCodes.OK, mockData); + mock.onGet(endpoint).replyOnce(HTTP_STATUS_OK, mockData); }); it('resolves with the response', () => @@ -42,7 +43,7 @@ describe('pollUntilComplete', () => { .onGet(endpoint) .replyOnce(HTTP_STATUS_NO_CONTENT, undefined, pollIntervalHeader) .onGet(endpoint) - .replyOnce(httpStatusCodes.OK, mockData); + .replyOnce(HTTP_STATUS_OK, mockData); }); it('calls the endpoint until it succeeds, and resolves with the response', () => @@ -81,7 +82,7 @@ describe('pollUntilComplete', () => { describe('given params', () => { const params = { foo: 'bar' }; beforeEach(() => { - mock.onGet(endpoint, { params }).replyOnce(httpStatusCodes.OK, mockData); + mock.onGet(endpoint, { params }).replyOnce(HTTP_STATUS_OK, mockData); }); it('requests the expected URL', () => diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js index 20dce639177..38214048b23 100644 --- a/spec/frontend/members/store/actions_spec.js +++ b/spec/frontend/members/store/actions_spec.js @@ -4,7 +4,7 @@ import { noop } from 'lodash'; import { useFakeDate } from 'helpers/fake_date'; import testAction from 'helpers/vuex_action_helper'; import { members, group, modalData } from 'jest/members/mock_data'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { updateMemberRole, showRemoveGroupLinkModal, @@ -44,7 +44,7 @@ describe('Vuex members actions', () => { describe('successful request', () => { it(`commits ${types.RECEIVE_MEMBER_ROLE_SUCCESS} mutation`, async () => { - mock.onPut().replyOnce(httpStatusCodes.OK); + mock.onPut().replyOnce(HTTP_STATUS_OK); await testAction(updateMemberRole, payload, state, [ { @@ -83,7 +83,7 @@ describe('Vuex members actions', () => { describe('successful request', () => { describe('changing expiration date', () => { it(`commits ${types.RECEIVE_MEMBER_EXPIRATION_SUCCESS} mutation`, async () => { - mock.onPut().replyOnce(httpStatusCodes.OK); + mock.onPut().replyOnce(HTTP_STATUS_OK); await testAction(updateMemberExpiration, { memberId, expiresAt }, state, [ { @@ -98,7 +98,7 @@ describe('Vuex members actions', () => { describe('removing the expiration date', () => { it(`commits ${types.RECEIVE_MEMBER_EXPIRATION_SUCCESS} mutation`, async () => { - mock.onPut().replyOnce(httpStatusCodes.OK); + mock.onPut().replyOnce(HTTP_STATUS_OK); await testAction(updateMemberExpiration, { memberId, expiresAt: null }, state, [ { diff --git a/spec/frontend/monitoring/requests/index_spec.js b/spec/frontend/monitoring/requests/index_spec.js index 94f1f896e3c..cf7df3dd9d5 100644 --- a/spec/frontend/monitoring/requests/index_spec.js +++ b/spec/frontend/monitoring/requests/index_spec.js @@ -2,9 +2,10 @@ import MockAdapter from 'axios-mock-adapter'; import { backoffMockImplementation } from 'helpers/backoff_helper'; import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; -import statusCodes, { +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_UNAUTHORIZED, HTTP_STATUS_UNPROCESSABLE_ENTITY, @@ -35,7 +36,7 @@ describe('monitoring metrics_requests', () => { }; it('returns a dashboard response', () => { - mock.onGet(dashboardEndpoint).reply(statusCodes.OK, response); + mock.onGet(dashboardEndpoint).reply(HTTP_STATUS_OK, response); return getDashboard(dashboardEndpoint, params).then((data) => { expect(data).toEqual(metricsDashboardResponse); @@ -45,7 +46,7 @@ describe('monitoring metrics_requests', () => { it('returns a dashboard response after retrying twice', () => { mock.onGet(dashboardEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); mock.onGet(dashboardEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); - mock.onGet(dashboardEndpoint).reply(statusCodes.OK, response); + mock.onGet(dashboardEndpoint).reply(HTTP_STATUS_OK, response); return getDashboard(dashboardEndpoint, params).then((data) => { expect(data).toEqual(metricsDashboardResponse); @@ -78,7 +79,7 @@ describe('monitoring metrics_requests', () => { }; it('returns a dashboard response', () => { - mock.onGet(prometheusEndpoint).reply(statusCodes.OK, response); + mock.onGet(prometheusEndpoint).reply(HTTP_STATUS_OK, response); return getPrometheusQueryData(prometheusEndpoint, params).then((data) => { expect(data).toEqual(response.data); @@ -89,7 +90,7 @@ describe('monitoring metrics_requests', () => { // Mock multiple attempts while the cache is filling up mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); - mock.onGet(prometheusEndpoint).reply(statusCodes.OK, response); // 3rd attempt + mock.onGet(prometheusEndpoint).reply(HTTP_STATUS_OK, response); // 3rd attempt return getPrometheusQueryData(prometheusEndpoint, params).then((data) => { expect(data).toEqual(response.data); diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 9322ce1168a..fbe030b1a7d 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -4,9 +4,10 @@ import testAction from 'helpers/vuex_action_helper'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; -import statusCodes, { +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CREATED, + HTTP_STATUS_OK, HTTP_STATUS_UNPROCESSABLE_ENTITY, } from '~/lib/utils/http_status'; import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; @@ -1117,7 +1118,7 @@ describe('Monitoring store actions', () => { mock .onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent }) - .reply(statusCodes.OK, mockPanel); + .reply(HTTP_STATUS_OK, mockPanel); testAction( fetchPanelPreview, diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js index a787c9ebac0..70749557e61 100644 --- a/spec/frontend/notifications/components/custom_notifications_modal_spec.js +++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js @@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue'; import { i18n } from '~/notifications/constants'; @@ -138,7 +138,7 @@ describe('CustomNotificationsModal', () => { mockAxios .onGet(endpointUrl) - .reply(httpStatus.OK, mockNotificationSettingsResponses.default); + .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default); wrapper = createComponent({ injectedProperties }); @@ -155,7 +155,7 @@ describe('CustomNotificationsModal', () => { mockAxios .onGet(endpointUrl) - .reply(httpStatus.OK, mockNotificationSettingsResponses.default); + .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default); wrapper = createComponent(); @@ -201,11 +201,11 @@ describe('CustomNotificationsModal', () => { async ({ projectId, groupId, endpointUrl }) => { mockAxios .onGet(endpointUrl) - .reply(httpStatus.OK, mockNotificationSettingsResponses.default); + .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default); mockAxios .onPut(endpointUrl) - .reply(httpStatus.OK, mockNotificationSettingsResponses.updated); + .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.updated); const injectedProperties = { projectId, diff --git a/spec/frontend/notifications/components/notifications_dropdown_spec.js b/spec/frontend/notifications/components/notifications_dropdown_spec.js index 9eb29ce6ff6..0f13de0e6d8 100644 --- a/spec/frontend/notifications/components/notifications_dropdown_spec.js +++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js @@ -4,7 +4,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import waitForPromises from 'helpers/wait_for_promises'; -import httpStatus, { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; +import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import CustomNotificationsModal from '~/notifications/components/custom_notifications_modal.vue'; import NotificationsDropdown from '~/notifications/components/notifications_dropdown.vue'; import NotificationsDropdownItem from '~/notifications/components/notifications_dropdown_item.vue'; @@ -98,7 +98,7 @@ describe('NotificationsDropdown', () => { it('opens the modal when the user clicks the button', async () => { jest.spyOn(axios, 'put'); - mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {}); + mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_OK, {}); wrapper = createComponent({ initialNotificationLevel: 'custom', @@ -233,7 +233,7 @@ describe('NotificationsDropdown', () => { ); it('updates the selectedNotificationLevel and marks the item with a checkmark', async () => { - mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {}); + mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_OK, {}); wrapper = createComponent(); const dropdownItem = findDropdownItemAt(1); @@ -257,7 +257,7 @@ describe('NotificationsDropdown', () => { }); it('opens the modal when the user clicks on the "Custom" dropdown item', async () => { - mockAxios.onPut('/api/v4/notification_settings').reply(httpStatus.OK, {}); + mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_OK, {}); wrapper = createComponent(); await clickDropdownItemAt(5); diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js index 5829c4d5546..2ff45266a07 100644 --- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js +++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js @@ -6,7 +6,7 @@ import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status'; +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue'; import { codeCoverageMockData, sortedDataByDates } from './mock_data'; @@ -49,7 +49,7 @@ describe('Code Coverage', () => { describe('when fetching data is successful', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, codeCoverageMockData); createComponent(); @@ -108,7 +108,7 @@ describe('Code Coverage', () => { describe('when fetching data succeed but returns an empty state', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - mockAxios.onGet().replyOnce(httpStatusCodes.OK, []); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, []); createComponent(); @@ -136,7 +136,7 @@ describe('Code Coverage', () => { describe('dropdown options', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, codeCoverageMockData); createComponent(); @@ -153,7 +153,7 @@ describe('Code Coverage', () => { describe('interactions', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, codeCoverageMockData); createComponent(); diff --git a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js index 1a696e00f85..c8e9a31b526 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js @@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import WikiContent from '~/pages/shared/wikis/components/wiki_content.vue'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; import axios from '~/lib/utils/axios_utils'; -import httpStatus, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import waitForPromises from 'helpers/wait_for_promises'; import { handleLocationHash } from '~/lib/utils/common_utils'; @@ -59,7 +59,7 @@ describe('pages/shared/wikis/components/wiki_content', () => { const content = 'content'; beforeEach(() => { - mock.onGet(PATH, { params: { render_html: true } }).replyOnce(httpStatus.OK, { content }); + mock.onGet(PATH, { params: { render_html: true } }).replyOnce(HTTP_STATUS_OK, { content }); buildWrapper(); return waitForPromises(); }); diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js index 1c7cb07ccfe..56940e43365 100644 --- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js @@ -8,9 +8,10 @@ import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_help import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_OK, } from '~/lib/utils/http_status'; import { redirectTo } from '~/lib/utils/url_utility'; import PipelineNewForm from '~/pipeline_new/components/pipeline_new_form.vue'; @@ -111,7 +112,7 @@ describe('Pipeline New Form', () => { beforeEach(() => { mock = new MockAdapter(axios); mockCiConfigVariables = jest.fn(); - mock.onGet(projectRefsEndpoint).reply(httpStatusCodes.OK, mockRefs); + mock.onGet(projectRefsEndpoint).reply(HTTP_STATUS_OK, mockRefs); dummySubmitEvent = { preventDefault: jest.fn(), @@ -176,7 +177,7 @@ describe('Pipeline New Form', () => { describe('Pipeline creation', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); - mock.onPost(pipelinesPath).reply(httpStatusCodes.OK, newPipelinePostResponse); + mock.onPost(pipelinesPath).reply(HTTP_STATUS_OK, newPipelinePostResponse); }); it('does not submit the native HTML form', async () => { diff --git a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js index a9921a228c4..975f39562be 100644 --- a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js +++ b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import RefsDropdown from '~/pipeline_new/components/refs_dropdown.vue'; @@ -41,7 +41,7 @@ describe('Pipeline New Form', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(httpStatusCodes.OK, mockRefs); + mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockRefs); }); beforeEach(() => { @@ -111,7 +111,7 @@ describe('Pipeline New Form', () => { beforeEach(async () => { mock .onGet(projectRefsEndpoint, { params: { search: mockSearchTerm } }) - .reply(httpStatusCodes.OK, mockFilteredRefs); + .reply(HTTP_STATUS_OK, mockFilteredRefs); await findSearchBox().vm.$emit('input', mockSearchTerm); await waitForPromises(); @@ -142,7 +142,7 @@ describe('Pipeline New Form', () => { .onGet(projectRefsEndpoint, { params: { ref: mockFullName }, }) - .reply(httpStatusCodes.OK, mockRefs); + .reply(HTTP_STATUS_OK, mockRefs); createComponent( { @@ -188,9 +188,7 @@ describe('Pipeline New Form', () => { `( 'should render branches and tags based on presence', async ({ mockData, expectedGroupLength, expectedListboxItemsLength }) => { - mock - .onGet(projectRefsEndpoint, { params: { search: '' } }) - .reply(httpStatusCodes.OK, mockData); + mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockData); createComponent({}, mountExtended); findDropdown().vm.$emit('shown'); await waitForPromises(); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js index 13f3eea277a..5fc9f9ba629 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue'; import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue'; @@ -95,7 +95,7 @@ describe('ServiceDeskRoot', () => { }); it('sends a request to turn service desk on', () => { - axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); + axiosMock.onPut(provideData.endpoint).replyOnce(HTTP_STATUS_OK); expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: true }); }); @@ -117,7 +117,7 @@ describe('ServiceDeskRoot', () => { }); it('sends a request to turn service desk off', () => { - axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); + axiosMock.onPut(provideData.endpoint).replyOnce(HTTP_STATUS_OK); expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: false }); }); @@ -133,7 +133,7 @@ describe('ServiceDeskRoot', () => { describe('save event', () => { describe('successful request', () => { beforeEach(async () => { - axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); + axiosMock.onPut(provideData.endpoint).replyOnce(HTTP_STATUS_OK); wrapper = createComponent(); diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js index 3b7db6ffba3..e56975d021a 100644 --- a/spec/frontend/repository/commits_service_spec.js +++ b/spec/frontend/repository/commits_service_spec.js @@ -1,7 +1,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service'; -import httpStatus, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { createAlert } from '~/flash'; import { I18N_COMMIT_DATA_FETCH_ERROR } from '~/repository/constants'; import { refWithSpecialCharMock } from './mock_data'; @@ -15,7 +15,7 @@ describe('commits service', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet(url).reply(httpStatus.OK, [], {}); + mock.onGet(url).reply(HTTP_STATUS_OK, [], {}); jest.spyOn(axios, 'get'); }); diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index 99e23348b92..2e8860f67ef 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -25,7 +25,7 @@ import CodeIntelligence from '~/code_navigation/components/app.vue'; import * as urlUtility from '~/lib/utils/url_utility'; import { isLoggedIn, handleLocationHash } from '~/lib/utils/common_utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import httpStatusCodes, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import LineHighlighter from '~/blob/line_highlighter'; import { LEGACY_FILE_TYPES } from '~/repository/constants'; import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants'; @@ -256,19 +256,19 @@ describe('Blob content viewer component', () => { ); it('loads the LineHighlighter', async () => { - mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test'); + mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test'); await createComponent({ blob: { ...simpleViewerMock, fileType, highlightJs } }); expect(LineHighlighter).toHaveBeenCalled(); }); it('does not load the LineHighlighter for RichViewers', async () => { - mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test'); + mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test'); await createComponent({ blob: { ...richViewerMock, fileType, highlightJs } }); expect(LineHighlighter).not.toHaveBeenCalled(); }); it('scrolls to the hash', async () => { - mockAxios.onGet(legacyViewerUrl).replyOnce(httpStatusCodes.OK, 'test'); + mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, 'test'); await createComponent({ blob: { ...simpleViewerMock, fileType, highlightJs } }); expect(handleLocationHash).toHaveBeenCalled(); }); diff --git a/spec/frontend/repository/components/new_directory_modal_spec.js b/spec/frontend/repository/components/new_directory_modal_spec.js index cf0d48280f4..4e5c9a685c4 100644 --- a/spec/frontend/repository/components/new_directory_modal_spec.js +++ b/spec/frontend/repository/components/new_directory_modal_spec.js @@ -5,7 +5,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; import NewDirectoryModal from '~/repository/components/new_directory_modal.vue'; @@ -149,7 +149,7 @@ describe('NewDirectoryModal', () => { originalBranch, createNewMr, } = defaultFormValue; - mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {}); + mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, {}); await fillForm(); await submitForm(); @@ -161,7 +161,7 @@ describe('NewDirectoryModal', () => { }); it('does not submit "create_merge_request" formData if createNewMr is not checked', async () => { - mock.onPost(initialProps.path).reply(httpStatusCodes.OK, {}); + mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, {}); await fillForm({ createNewMr: false }); await submitForm(); expect(mock.history.post[0].data.get('create_merge_request')).toBeNull(); @@ -169,7 +169,7 @@ describe('NewDirectoryModal', () => { it('redirects to the new directory', async () => { const response = { filePath: 'new-dir-path' }; - mock.onPost(initialProps.path).reply(httpStatusCodes.OK, response); + mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, response); await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' }); await submitForm(); diff --git a/spec/frontend/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js index 8db169b02b4..9de0666f27a 100644 --- a/spec/frontend/repository/components/upload_blob_modal_spec.js +++ b/spec/frontend/repository/components/upload_blob_modal_spec.js @@ -5,7 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/flash'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; import UploadBlobModal from '~/repository/components/upload_blob_modal.vue'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; @@ -158,7 +158,7 @@ describe('UploadBlobModal', () => { describe('successful response', () => { beforeEach(async () => { mock = new MockAdapter(axios); - mock.onPost(initialProps.path).reply(httpStatusCodes.OK, { filePath: 'blah' }); + mock.onPost(initialProps.path).reply(HTTP_STATUS_OK, { filePath: 'blah' }); findModal().vm.$emit('primary', mockEvent); diff --git a/spec/frontend/self_monitor/store/actions_spec.js b/spec/frontend/self_monitor/store/actions_spec.js index 65c9d2f5f01..4c266fabea6 100644 --- a/spec/frontend/self_monitor/store/actions_spec.js +++ b/spec/frontend/self_monitor/store/actions_spec.js @@ -1,7 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import statusCodes, { HTTP_STATUS_ACCEPTED } from '~/lib/utils/http_status'; +import { HTTP_STATUS_ACCEPTED, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/self_monitor/store/actions'; import * as types from '~/self_monitor/store/mutation_types'; import createState from '~/self_monitor/store/state'; @@ -47,7 +47,7 @@ describe('self-monitor actions', () => { mock.onPost(state.createProjectEndpoint).reply(HTTP_STATUS_ACCEPTED, { job_id: '123', }); - mock.onGet(state.createProjectStatusEndpoint).reply(statusCodes.OK, { + mock.onGet(state.createProjectStatusEndpoint).reply(HTTP_STATUS_OK, { project_full_path: '/self-monitor-url', }); }); @@ -154,7 +154,7 @@ describe('self-monitor actions', () => { mock.onDelete(state.deleteProjectEndpoint).reply(HTTP_STATUS_ACCEPTED, { job_id: '456', }); - mock.onGet(state.deleteProjectStatusEndpoint).reply(statusCodes.OK, { + mock.onGet(state.deleteProjectStatusEndpoint).reply(HTTP_STATUS_OK, { status: 'success', }); }); diff --git a/spec/frontend/super_sidebar/components/counter_spec.js b/spec/frontend/super_sidebar/components/counter_spec.js new file mode 100644 index 00000000000..1150b0a3aa8 --- /dev/null +++ b/spec/frontend/super_sidebar/components/counter_spec.js @@ -0,0 +1,56 @@ +import { GlIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { __ } from '~/locale'; +import Counter from '~/super_sidebar/components/counter.vue'; + +describe('Counter component', () => { + let wrapper; + + const defaultPropsData = { + count: 3, + href: '', + icon: 'issues', + label: __('Issues'), + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findButton = () => wrapper.find('button'); + const findIcon = () => wrapper.getComponent(GlIcon); + const findLink = () => wrapper.find('a'); + + const createWrapper = (props = {}) => { + wrapper = shallowMountExtended(Counter, { + propsData: { + ...defaultPropsData, + ...props, + }, + }); + }; + + beforeEach(() => { + createWrapper(); + }); + + describe('default', () => { + it('renders icon', () => { + expect(findIcon().props('name')).toBe('issues'); + }); + + it('renders button', () => { + expect(findButton().attributes('aria-label')).toBe('Issues 3'); + expect(findLink().exists()).toBe(false); + }); + }); + + describe('link', () => { + it('renders link', () => { + createWrapper({ href: '/dashboard/todos' }); + expect(findLink().attributes('aria-label')).toBe('Issues 3'); + expect(findLink().attributes('href')).toBe('/dashboard/todos'); + expect(findButton().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js new file mode 100644 index 00000000000..d7d2f67dc8a --- /dev/null +++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js @@ -0,0 +1,33 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue'; +import UserBar from '~/super_sidebar/components/user_bar.vue'; +import { sidebarData } from '../mock_data'; + +describe('SuperSidebar component', () => { + let wrapper; + + const findUserBar = () => wrapper.findComponent(UserBar); + + afterEach(() => { + wrapper.destroy(); + }); + + const createWrapper = (props = {}) => { + wrapper = shallowMountExtended(SuperSidebar, { + propsData: { + sidebarData, + ...props, + }, + }); + }; + + describe('default', () => { + beforeEach(() => { + createWrapper(); + }); + + it('renders UserBar with sidebarData', () => { + expect(findUserBar().props('sidebarData')).toBe(sidebarData); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js new file mode 100644 index 00000000000..6d0186a2749 --- /dev/null +++ b/spec/frontend/super_sidebar/components/user_bar_spec.js @@ -0,0 +1,46 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { __ } from '~/locale'; +import Counter from '~/super_sidebar/components/counter.vue'; +import UserBar from '~/super_sidebar/components/user_bar.vue'; +import { sidebarData } from '../mock_data'; + +describe('UserBar component', () => { + let wrapper; + + const findCounter = (at) => wrapper.findAllComponents(Counter).at(at); + + afterEach(() => { + wrapper.destroy(); + }); + + const createWrapper = (props = {}) => { + wrapper = shallowMountExtended(UserBar, { + propsData: { + sidebarData, + ...props, + }, + provide: { + rootPath: '/', + toggleNewNavEndpoint: '/-/profile/preferences', + }, + }); + }; + + describe('default', () => { + beforeEach(() => { + createWrapper(); + }); + + it('renders issues counter', () => { + expect(findCounter(0).props('count')).toBe(sidebarData.assigned_open_issues_count); + expect(findCounter(0).props('href')).toBe(sidebarData.issues_dashboard_path); + expect(findCounter(0).props('label')).toBe(__('Issues')); + }); + + it('renders todos counter', () => { + expect(findCounter(2).props('count')).toBe(sidebarData.todos_pending_count); + expect(findCounter(2).props('href')).toBe('/dashboard/todos'); + expect(findCounter(2).props('label')).toBe(__('To-Do list')); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js new file mode 100644 index 00000000000..7db0d0ea5cc --- /dev/null +++ b/spec/frontend/super_sidebar/mock_data.js @@ -0,0 +1,9 @@ +export const sidebarData = { + name: 'Administrator', + username: 'root', + avatar_url: 'path/to/img_administrator', + assigned_open_issues_count: 1, + assigned_open_merge_requests_count: 2, + todos_pending_count: 3, + issues_dashboard_path: 'path/to/issues', +}; diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js index 334cddbbc06..548b68bc103 100644 --- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js @@ -8,9 +8,10 @@ import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; -import httpStatusCodes, { +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, } from '~/lib/utils/http_status'; import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue'; @@ -60,7 +61,7 @@ describe('Test report extension', () => { }; const createExpandedWidgetWithData = async (data = mixedResultsTestReports) => { - mockApi(httpStatusCodes.OK, data); + mockApi(HTTP_STATUS_OK, data); createComponent(); await waitForPromises(); findToggleCollapsedButton().trigger('click'); @@ -78,7 +79,7 @@ describe('Test report extension', () => { describe('summary', () => { it('displays loading state initially', () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); expect(wrapper.text()).toContain(i18n.loading); @@ -110,7 +111,7 @@ describe('Test report extension', () => { ${'failed test results'} | ${newFailedTestReports} | ${'Test summary: 2 failed, 11 total tests'} ${'resolved failures'} | ${resolvedFailures} | ${'Test summary: 4 fixed test results, 11 total tests'} `('displays summary text for $description', async ({ mockData, expectedResult }) => { - mockApi(httpStatusCodes.OK, mockData); + mockApi(HTTP_STATUS_OK, mockData); createComponent(); await waitForPromises(); @@ -119,7 +120,7 @@ describe('Test report extension', () => { }); it('displays report level recently failed count', async () => { - mockApi(httpStatusCodes.OK, recentFailures); + mockApi(HTTP_STATUS_OK, recentFailures); createComponent(); await waitForPromises(); @@ -130,7 +131,7 @@ describe('Test report extension', () => { }); it('displays a link to the full report', async () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); await waitForPromises(); @@ -140,7 +141,7 @@ describe('Test report extension', () => { }); it('hides copy failed tests button when there are no failing tests', async () => { - mockApi(httpStatusCodes.OK); + mockApi(HTTP_STATUS_OK); createComponent(); await waitForPromises(); @@ -149,7 +150,7 @@ describe('Test report extension', () => { }); it('displays copy failed tests button when there are failing tests', async () => { - mockApi(httpStatusCodes.OK, newFailedTestReports); + mockApi(HTTP_STATUS_OK, newFailedTestReports); createComponent(); await waitForPromises(); @@ -162,7 +163,7 @@ describe('Test report extension', () => { }); it('hides copy failed tests button when endpoint returns null files', async () => { - mockApi(httpStatusCodes.OK, newFailedTestWithNullFilesReport); + mockApi(HTTP_STATUS_OK, newFailedTestWithNullFilesReport); createComponent(); await waitForPromises(); @@ -171,7 +172,7 @@ describe('Test report extension', () => { }); it('copy failed tests button updates tooltip text when clicked', async () => { - mockApi(httpStatusCodes.OK, newFailedTestReports); + mockApi(HTTP_STATUS_OK, newFailedTestReports); createComponent(); await waitForPromises(); @@ -198,7 +199,7 @@ describe('Test report extension', () => { }); it('shows an error when a suite has a parsing error', async () => { - mockApi(httpStatusCodes.OK, reportWithParsingErrors); + mockApi(HTTP_STATUS_OK, reportWithParsingErrors); createComponent(); await waitForPromises(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js index d3ae81b9b10..01049e54a7f 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js @@ -6,7 +6,7 @@ import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import accessibilityExtension from '~/vue_merge_request_widget/extensions/accessibility'; -import httpStatusCodes, { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { accessibilityReportResponseErrors, accessibilityReportResponseSuccess } from './mock_data'; describe('Accessibility extension', () => { @@ -45,7 +45,7 @@ describe('Accessibility extension', () => { describe('summary', () => { it('displays loading text', () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); @@ -63,7 +63,7 @@ describe('Accessibility extension', () => { }); it('displays detected errors and is expandable', async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); @@ -76,7 +76,7 @@ describe('Accessibility extension', () => { }); it('displays no detected errors and is not expandable', async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseSuccess); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseSuccess); createComponent(); @@ -91,7 +91,7 @@ describe('Accessibility extension', () => { describe('expanded data', () => { beforeEach(async () => { - mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors); + mockApi(HTTP_STATUS_OK, accessibilityReportResponseErrors); createComponent(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js index 99c4c898d7a..9e004f98715 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js @@ -7,9 +7,10 @@ import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import codeQualityExtension from '~/vue_merge_request_widget/extensions/code_quality'; -import httpStatusCodes, { +import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_OK, } from '~/lib/utils/http_status'; import { i18n, @@ -62,7 +63,7 @@ describe('Code Quality extension', () => { describe('summary', () => { it('displays loading text', () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseNewErrors); createComponent(); @@ -90,7 +91,7 @@ describe('Code Quality extension', () => { }); it('displays correct single Report', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseNewErrors); createComponent(); @@ -108,7 +109,7 @@ describe('Code Quality extension', () => { }); it('displays quality improvement and degradation', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseResolvedAndNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedAndNewErrors); createComponent(); await waitForPromises(); @@ -133,7 +134,7 @@ describe('Code Quality extension', () => { }); it('displays no detected errors', async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseNoErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseNoErrors); createComponent(); @@ -146,7 +147,7 @@ describe('Code Quality extension', () => { describe('expanded data', () => { beforeEach(async () => { - mockApi(httpStatusCodes.OK, codeQualityResponseResolvedAndNewErrors); + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedAndNewErrors); createComponent(); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js index 658681bcd05..63c22aff3d5 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js @@ -4,7 +4,7 @@ import testAction from 'helpers/vuex_action_helper'; import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data'; import Api from '~/api'; import { createAlert } from '~/flash'; -import httpStatusCodes, { HTTP_STATUS_SERVICE_UNAVAILABLE } from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE } from '~/lib/utils/http_status'; import * as actions from '~/vue_shared/components/filtered_search_bar/store/modules/filters/actions'; import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types'; import initialState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state'; @@ -122,7 +122,7 @@ describe('Filters actions', () => { ':id', encodeURIComponent(projectEndpoint), ); - mock.onGet(url).replyOnce(httpStatusCodes.OK, mockBranches); + mock.onGet(url).replyOnce(HTTP_STATUS_OK, mockBranches); }); it('dispatches RECEIVE_BRANCHES_SUCCESS with received data', () => { @@ -177,7 +177,7 @@ describe('Filters actions', () => { describe('success', () => { beforeEach(() => { - mock.onAny().replyOnce(httpStatusCodes.OK, filterUsers); + mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers); }); it('dispatches RECEIVE_AUTHORS_SUCCESS with received data and groupEndpoint set', () => { @@ -261,7 +261,7 @@ describe('Filters actions', () => { describe('fetchMilestones', () => { describe('success', () => { beforeEach(() => { - mock.onGet(milestonesEndpoint).replyOnce(httpStatusCodes.OK, filterMilestones); + mock.onGet(milestonesEndpoint).replyOnce(HTTP_STATUS_OK, filterMilestones); }); it('dispatches RECEIVE_MILESTONES_SUCCESS with received data', () => { @@ -307,7 +307,7 @@ describe('Filters actions', () => { describe('success', () => { let restoreVersion; beforeEach(() => { - mock.onAny().replyOnce(httpStatusCodes.OK, filterUsers); + mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers); restoreVersion = gon.api_version; gon.api_version = 'v1'; }); @@ -404,7 +404,7 @@ describe('Filters actions', () => { describe('fetchLabels', () => { describe('success', () => { beforeEach(() => { - mock.onGet(labelsEndpoint).replyOnce(httpStatusCodes.OK, filterLabels); + mock.onGet(labelsEndpoint).replyOnce(HTTP_STATUS_OK, filterLabels); }); it('dispatches RECEIVE_LABELS_SUCCESS with received data', () => { diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 6db955f3637..299e4cb0133 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe SidebarsHelper do + include Devise::Test::ControllerHelpers + describe '#sidebar_tracking_attributes_by_object' do subject { helper.sidebar_tracking_attributes_by_object(object) } @@ -42,4 +44,26 @@ RSpec.describe SidebarsHelper do end end end + + describe '#super_sidebar_context' do + let(:user) { build(:user) } + + subject { helper.super_sidebar_context(user) } + + it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do + Rails.cache.write(['users', user.id, 'assigned_open_issues_count'], 1) + Rails.cache.write(['users', user.id, 'assigned_open_merge_requests_count'], 2) + Rails.cache.write(['users', user.id, 'todos_pending_count'], 3) + + expect(subject).to eq({ + name: user.name, + username: user.username, + avatar_url: user.avatar_url, + assigned_open_issues_count: 1, + assigned_open_merge_requests_count: 2, + todos_pending_count: 3, + issues_dashboard_path: issues_dashboard_path(assignee_username: user.username) + }) + end + end end diff --git a/spec/lib/banzai/filter/repository_link_filter_spec.rb b/spec/lib/banzai/filter/repository_link_filter_spec.rb index 0df680dc0c8..b2162ea2756 100644 --- a/spec/lib/banzai/filter/repository_link_filter_spec.rb +++ b/spec/lib/banzai/filter/repository_link_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Banzai::Filter::RepositoryLinkFilter do +RSpec.describe Banzai::Filter::RepositoryLinkFilter, feature_category: :team_planning do include RepoHelpers def filter(doc, contexts = {}) @@ -303,6 +303,12 @@ RSpec.describe Banzai::Filter::RepositoryLinkFilter do expect(doc.at_css('img')['src']).to eq "/#{project_path}/-/raw/#{Addressable::URI.escape(ref)}/#{escaped}" end + it 'supports percent sign in filenames' do + doc = filter(link('doc/api/README%.md')) + expect(doc.at_css('a')['href']) + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README%25.md" + end + context 'when requested path is a file in the repo' do let(:requested_path) { 'doc/api/README.md' } diff --git a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb index 4ee825c71b6..a8b4b9a6f05 100644 --- a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb +++ b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do +RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver, feature_category: :importers do let(:project) { create(:project) } let(:relation_object) { build(:issue, project: project) } let(:relation_definition) { {} } @@ -34,6 +34,7 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do it 'saves relation object with subrelations' do expect(relation_object.notes).to receive(:<<).and_call_original + expect(relation_object).to receive(:save).and_call_original saver.execute @@ -80,6 +81,7 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do it 'saves valid subrelations and logs invalid subrelation' do expect(relation_object.notes).to receive(:<<).twice.and_call_original + expect(relation_object).to receive(:save).and_call_original expect(Gitlab::Import::Logger) .to receive(:info) .with( diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 049c4500207..6164555ad19 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -4,12 +4,14 @@ require 'spec_helper' RSpec.describe API::Environments, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } + let_it_be(:developer) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) } let_it_be_with_reload(:environment) { create(:environment, project: project) } before do project.add_maintainer(user) + project.add_developer(developer) end describe 'GET /projects/:id/environments', :aggregate_failures do @@ -182,6 +184,50 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do end end + describe 'POST /projects/:id/environments/stop_stale' do + context 'as a maintainer' do + it 'returns a 200' do + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'returns a 400 for bad input date' do + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.day.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('400 Bad request - Invalid Date') + end + + it 'returns a 400 for service error' do + expect_next_instance_of(::Environments::StopStaleService) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.error(message: 'Test Error')) + end + + post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Test Error') + end + end + + context 'a non member' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments/stop_stale", non_member), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'a developer' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments/stop_stale", developer), params: { before: 1.week.ago.to_date.to_s } + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + describe 'PUT /projects/:id/environments/:environment_id' do it 'returns a 200 if name and external_url are changed' do url = 'https://mepmep.whatever.ninja' diff --git a/spec/services/ci/create_pipeline_service/logger_spec.rb b/spec/services/ci/create_pipeline_service/logger_spec.rb index ccb15bfa684..ecb24a61075 100644 --- a/spec/services/ci/create_pipeline_service/logger_spec.rb +++ b/spec/services/ci/create_pipeline_service/logger_spec.rb @@ -139,5 +139,74 @@ RSpec.describe Ci::CreatePipelineService, # rubocop: disable RSpec/FilePath expect(pipeline).to be_created_successfully end end + + describe 'pipeline includes count' do + before do + stub_const('Gitlab::Ci::Config::External::Context::MAX_INCLUDES', 2) + end + + context 'when the includes count exceeds the maximum' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:config_metadata) + .and_return({ includes: [{ file: 1 }, { file: 2 }, { file: 3 }] }) + end + end + + it 'creates a log entry' do + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including({ 'pipeline_includes_count' => 3 })) + .and_call_original + + expect(pipeline).to be_created_successfully + end + end + + context 'when the includes count does not exceed the maximum' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:config_metadata) + .and_return({ includes: [{ file: 1 }, { file: 2 }] }) + end + end + + it 'does not create a log entry but it collects the data' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + expect(pipeline).to be_created_successfully + + expect(service.logger.observations_hash) + .to match(a_hash_including({ 'pipeline_includes_count' => 2 })) + end + end + + context 'when the includes data is nil' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:config_metadata) + .and_return({}) + end + end + + it 'does not create a log entry' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + expect(pipeline).to be_created_successfully + end + end + + context 'when the pipeline config_metadata is nil' do + before do + allow_next_instance_of(Ci::Pipeline) do |pipeline| + allow(pipeline).to receive(:config_metadata) + .and_return(nil) + end + end + + it 'does not create a log entry but it collects the data' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + expect(pipeline).to be_created_successfully + end + end + end end end diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index d23907a8def..db58dc00338 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PipelineScheduleWorker do +RSpec.describe PipelineScheduleWorker, :sidekiq_inline, feature_category: :continuous_integration do include ExclusiveLeaseHelpers subject { described_class.new.perform } @@ -30,7 +30,7 @@ RSpec.describe PipelineScheduleWorker do project.add_maintainer(user) end - context 'when there is a scheduled pipeline within next_run_at', :sidekiq_inline do + context 'when there is a scheduled pipeline within next_run_at' do shared_examples 'successful scheduling' do it 'creates a new pipeline' do expect { subject }.to change { project.ci_pipelines.count }.by(1) @@ -49,7 +49,19 @@ RSpec.describe PipelineScheduleWorker do end end - it_behaves_like 'successful scheduling' + shared_examples 'successful scheduling with/without ci_use_run_pipeline_schedule_worker' do + it_behaves_like 'successful scheduling' + + context 'when feature flag ci_use_run_pipeline_schedule_worker is disabled' do + before do + stub_feature_flags(ci_use_run_pipeline_schedule_worker: false) + end + + it_behaves_like 'successful scheduling' + end + end + + it_behaves_like 'successful scheduling with/without ci_use_run_pipeline_schedule_worker' context 'when the latest commit contains [ci skip]' do before do @@ -58,7 +70,7 @@ RSpec.describe PipelineScheduleWorker do .and_return('some commit [ci skip]') end - it_behaves_like 'successful scheduling' + it_behaves_like 'successful scheduling with/without ci_use_run_pipeline_schedule_worker' end end @@ -123,4 +135,13 @@ RSpec.describe PipelineScheduleWorker do expect { subject }.not_to raise_error end end + + context 'when max retry attempts reach' do + let!(:lease) { stub_exclusive_lease_taken(described_class.name.underscore) } + + it 'does not raise error' do + expect(lease).to receive(:try_obtain).exactly(described_class::LOCK_RETRY + 1).times + expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) + end + end end diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index 4fdf6149435..cf06351b693 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe RunPipelineScheduleWorker do +RSpec.describe RunPipelineScheduleWorker, feature_category: :continuous_integration do it 'has an until_executed deduplicate strategy' do expect(described_class.get_deduplicate_strategy).to eq(:until_executed) end @@ -56,37 +56,91 @@ RSpec.describe RunPipelineScheduleWorker do let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService, execute: service_response) } let(:service_response) { instance_double(ServiceResponse, payload: pipeline, error?: false) } - before do - expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service) + context 'when pipeline can be created' do + before do + expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service) - expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule).and_return(service_response) - end + expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule).and_return(service_response) + end + + context "when pipeline is persisted" do + let(:pipeline) { instance_double(Ci::Pipeline, persisted?: true) } + + it "returns the service response" do + expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response) + end - context "when pipeline is persisted" do - let(:pipeline) { instance_double(Ci::Pipeline, persisted?: true) } + it "does not log errors" do + expect(worker).not_to receive(:log_extra_metadata_on_done) - it "returns the service response" do - expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response) + expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response) + end + + it "changes the next_run_at" do + expect { worker.perform(pipeline_schedule.id, user.id) }.to change { pipeline_schedule.reload.next_run_at }.by(1.day) + end + + context 'when feature flag ci_use_run_pipeline_schedule_worker is disabled' do + before do + stub_feature_flags(ci_use_run_pipeline_schedule_worker: false) + end + + it 'does not change the next_run_at' do + expect { worker.perform(pipeline_schedule.id, user.id) }.not_to change { pipeline_schedule.reload.next_run_at } + end + end end - it "does not log errors" do - expect(worker).not_to receive(:log_extra_metadata_on_done) + context "when pipeline was not persisted" do + let(:service_response) { instance_double(ServiceResponse, error?: true, message: "Error", payload: pipeline) } + let(:pipeline) { instance_double(Ci::Pipeline, persisted?: false) } - expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response) + it "logs a pipeline creation error" do + expect(worker) + .to receive(:log_extra_metadata_on_done) + .with(:pipeline_creation_error, service_response.message) + .and_call_original + + expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response.message) + end end end - context "when pipeline was not persisted" do - let(:service_response) { instance_double(ServiceResponse, error?: true, message: "Error", payload: pipeline) } - let(:pipeline) { instance_double(Ci::Pipeline, persisted?: false) } + context 'when schedule is already executed' do + let(:time_in_future) { 1.hour.since } + + before do + pipeline_schedule.update_column(:next_run_at, time_in_future) + end + + it 'does not change the next_run_at' do + expect { worker.perform(pipeline_schedule.id, user.id) }.to not_change { pipeline_schedule.reload.next_run_at } + end + + it 'does not create a pipeline' do + expect(Ci::CreatePipelineService).not_to receive(:new) + + worker.perform(pipeline_schedule.id, user.id) + end + + context 'when feature flag ci_use_run_pipeline_schedule_worker is disabled' do + let(:pipeline) { instance_double(Ci::Pipeline, persisted?: true) } + + before do + stub_feature_flags(ci_use_run_pipeline_schedule_worker: false) + + expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service) + + expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule).and_return(service_response) + end - it "logs a pipeline creation error" do - expect(worker) - .to receive(:log_extra_metadata_on_done) - .with(:pipeline_creation_error, service_response.message) - .and_call_original + it 'does not change the next_run_at' do + expect { worker.perform(pipeline_schedule.id, user.id) }.to not_change { pipeline_schedule.reload.next_run_at } + end - expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response.message) + it "returns the service response" do + expect(worker.perform(pipeline_schedule.id, user.id)).to eq(service_response) + end end end end diff --git a/yarn.lock b/yarn.lock index 48678e839fe..56a1baf3495 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1126,10 +1126,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/favicon-overlay/-/favicon-overlay-2.0.0.tgz#2f32d0b6a4d5b8ac44e2927083d9ab478a78c984" integrity sha512-GNcORxXJ98LVGzOT9dDYKfbheqH6lNgPDD72lyXRnQIH7CjgGyos8i17aSBPq1f4s3zF3PyedFiAR4YEZbva2Q== -"@gitlab/fonts@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.0.1.tgz#5bcdf85d240fb053a7606a4cb79ab1c9b643f148" - integrity sha512-0e2a7x4A9ngEO/SsV/OQZYvuEAfLxKlS5Smn2zg+XI/6WlyFWNjmxDvdD58zav9Pjar1jrNPWMDLtGqV4U+QxQ== +"@gitlab/fonts@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@gitlab/fonts/-/fonts-1.1.0.tgz#141db7d7c8e72113307614932d64103d3a68668c" + integrity sha512-5XNDJke0ElrMRG5iLWOirFRSLQhf9oNpPx5uie1j2l8QU6RLkMAE0nXG6cwJ82mgAV49ZcqLXXSopWvuUg8Y9Q== "@gitlab/stylelint-config@4.1.0": version "4.1.0" |