diff options
59 files changed, 525 insertions, 139 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 48005787d81..8dc44231e26 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -79,6 +79,7 @@ const Api = { issuePath: '/api/:version/projects/:id/issues/:issue_iid', tagsPath: '/api/:version/projects/:id/repository/tags', freezePeriodsPath: '/api/:version/projects/:id/freeze_periods', + freezePeriodPath: '/api/:version/projects/:id/freeze_periods/:freeze_period_id', usageDataIncrementCounterPath: '/api/:version/usage_data/increment_counter', usageDataIncrementUniqueUsersPath: '/api/:version/usage_data/increment_unique_users', featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists', @@ -832,6 +833,14 @@ const Api = { return axios.post(url, freezePeriod); }, + updateFreezePeriod(id, freezePeriod = {}) { + const url = Api.buildUrl(this.freezePeriodPath) + .replace(':id', encodeURIComponent(id)) + .replace(':freeze_period_id', encodeURIComponent(freezePeriod.id)); + + return axios.put(url, freezePeriod); + }, + trackRedisCounterEvent(event) { if (!gon.features?.usageDataApi) { return null; diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js index 37b5f7e6df7..08bfabc05bc 100644 --- a/app/assets/javascripts/ci_variable_list/index.js +++ b/app/assets/javascripts/ci_variable_list/index.js @@ -3,8 +3,7 @@ import { parseBoolean } from '~/lib/utils/common_utils'; import CiVariableSettings from './components/ci_variable_settings.vue'; import createStore from './store'; -export default (containerId = 'js-ci-project-variables') => { - const containerEl = document.getElementById(containerId); +const mountCiVariableListApp = (containerEl) => { const { endpoint, projectId, @@ -43,3 +42,8 @@ export default (containerId = 'js-ci-project-variables') => { }, }); }; + +export default () => { + const el = document.querySelector('#js-ci-project-variables'); + return !el ? {} : mountCiVariableListApp(el); +}; diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue index d05a0761ae3..051ab710e5f 100644 --- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue @@ -18,7 +18,6 @@ export default { modalOptions: { ref: 'modal', modalId: 'deploy-freeze-modal', - title: __('Add deploy freeze'), actionCancel: { text: __('Cancel'), }, @@ -30,10 +29,13 @@ export default { cronSyntaxInstructions: __( 'Define a custom deploy freeze pattern with %{cronSyntaxStart}cron syntax%{cronSyntaxEnd}', ), + addTitle: __('Add deploy freeze'), + editTitle: __('Edit deploy freeze'), }, computed: { ...mapState([ 'projectId', + 'selectedId', 'selectedTimezone', 'timezoneData', 'freezeStartCron', @@ -45,9 +47,9 @@ export default { ]), addDeployFreezeButton() { return { - text: __('Add deploy freeze'), + text: this.isEditing ? __('Save deploy freeze') : __('Add deploy freeze'), attributes: [ - { variant: 'success' }, + { variant: 'confirm' }, { disabled: !isValidCron(this.freezeStartCron) || @@ -77,9 +79,17 @@ export default { this.setSelectedTimezone(selectedTimezone); }, }, + isEditing() { + return Boolean(this.selectedId); + }, + modalTitle() { + return this.isEditing + ? this.$options.translations.editTitle + : this.$options.translations.addTitle; + }, }, methods: { - ...mapActions(['addFreezePeriod', 'setSelectedTimezone', 'resetModal']), + ...mapActions(['addFreezePeriod', 'updateFreezePeriod', 'setSelectedTimezone', 'resetModal']), resetModalHandler() { this.resetModal(); }, @@ -89,6 +99,13 @@ export default { } return ''; }, + submit() { + if (this.isEditing) { + this.updateFreezePeriod(); + } else { + this.addFreezePeriod(); + } + }, }, }; </script> @@ -96,8 +113,9 @@ export default { <template> <gl-modal v-bind="$options.modalOptions" + :title="modalTitle" :action-primary="addDeployFreezeButton" - @primary="addFreezePeriod" + @primary="submit" @canceled="resetModalHandler" > <p> diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue index 0d6657973c3..6ce934dbaea 100644 --- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue @@ -1,7 +1,7 @@ <script> import { GlTable, GlButton, GlModalDirective, GlSprintf } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; -import { s__, __ } from '~/locale'; +import { s__ } from '~/locale'; export default { fields: [ @@ -17,9 +17,16 @@ export default { key: 'cronTimezone', label: s__('DeployFreeze|Time zone'), }, + { + key: 'edit', + label: s__('DeployFreeze|Edit'), + }, ], translations: { - addDeployFreeze: __('Add deploy freeze'), + addDeployFreeze: s__('DeployFreeze|Add deploy freeze'), + emptyStateText: s__( + 'DeployFreeze|No deploy freezes exist for this project. To add one, select %{strongStart}Add deploy freeze%{strongEnd}', + ), }, components: { GlTable, @@ -39,7 +46,7 @@ export default { this.fetchFreezePeriods(); }, methods: { - ...mapActions(['fetchFreezePeriods']), + ...mapActions(['fetchFreezePeriods', 'setFreezePeriod']), }, }; </script> @@ -53,15 +60,20 @@ export default { show-empty stacked="lg" > + <template #cell(cronTimezone)="{ item }"> + {{ item.cronTimezone.formattedTimezone }} + </template> + <template #cell(edit)="{ item }"> + <gl-button + v-gl-modal.deploy-freeze-modal + icon="pencil" + data-testid="edit-deploy-freeze" + @click="setFreezePeriod(item)" + /> + </template> <template #empty> <p data-testid="empty-freeze-periods" class="gl-text-center text-plain"> - <gl-sprintf - :message=" - s__( - 'DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}', - ) - " - > + <gl-sprintf :message="$options.translations.emptyStateText"> <template #strong="{ content }"> <strong>{{ content }}</strong> </template> @@ -73,7 +85,7 @@ export default { v-gl-modal.deploy-freeze-modal data-testid="add-deploy-freeze" category="primary" - variant="success" + variant="confirm" > {{ $options.translations.addDeployFreeze }} </gl-button> diff --git a/app/assets/javascripts/deploy_freeze/store/actions.js b/app/assets/javascripts/deploy_freeze/store/actions.js index 62045d2517d..56e45595dc5 100644 --- a/app/assets/javascripts/deploy_freeze/store/actions.js +++ b/app/assets/javascripts/deploy_freeze/store/actions.js @@ -3,37 +3,53 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; import { __ } from '~/locale'; import * as types from './mutation_types'; -export const requestAddFreezePeriod = ({ commit }) => { +export const requestFreezePeriod = ({ commit }) => { commit(types.REQUEST_ADD_FREEZE_PERIOD); }; -export const receiveAddFreezePeriodSuccess = ({ commit }) => { +export const receiveFreezePeriodSuccess = ({ commit }) => { commit(types.RECEIVE_ADD_FREEZE_PERIOD_SUCCESS); }; -export const receiveAddFreezePeriodError = ({ commit }, error) => { +export const receiveFreezePeriodError = ({ commit }, error) => { commit(types.RECEIVE_ADD_FREEZE_PERIOD_ERROR, error); }; -export const addFreezePeriod = ({ state, dispatch, commit }) => { - dispatch('requestAddFreezePeriod'); +const receiveFreezePeriod = (store, request) => { + const { dispatch, commit } = store; + dispatch('requestFreezePeriod'); - return Api.createFreezePeriod(state.projectId, { - freeze_start: state.freezeStartCron, - freeze_end: state.freezeEndCron, - cron_timezone: state.selectedTimezoneIdentifier, - }) + request(store) .then(() => { - dispatch('receiveAddFreezePeriodSuccess'); + dispatch('receiveFreezePeriodSuccess'); commit(types.RESET_MODAL); dispatch('fetchFreezePeriods'); }) .catch((error) => { createFlash(__('Error: Unable to create deploy freeze')); - dispatch('receiveAddFreezePeriodError', error); + dispatch('receiveFreezePeriodError', error); }); }; +export const addFreezePeriod = (store) => + receiveFreezePeriod(store, ({ state }) => + Api.createFreezePeriod(state.projectId, { + freeze_start: state.freezeStartCron, + freeze_end: state.freezeEndCron, + cron_timezone: state.selectedTimezoneIdentifier, + }), + ); + +export const updateFreezePeriod = (store) => + receiveFreezePeriod(store, ({ state }) => + Api.updateFreezePeriod(state.projectId, { + id: state.selectedId, + freeze_start: state.freezeStartCron, + freeze_end: state.freezeEndCron, + cron_timezone: state.selectedTimezoneIdentifier, + }), + ); + export const fetchFreezePeriods = ({ commit, state }) => { commit(types.REQUEST_FREEZE_PERIODS); @@ -46,6 +62,13 @@ export const fetchFreezePeriods = ({ commit, state }) => { }); }; +export const setFreezePeriod = ({ commit }, freezePeriod) => { + commit(types.SET_SELECTED_ID, freezePeriod.id); + commit(types.SET_SELECTED_TIMEZONE, freezePeriod.cronTimezone); + commit(types.SET_FREEZE_START_CRON, freezePeriod.freezeStart); + commit(types.SET_FREEZE_END_CRON, freezePeriod.freezeEnd); +}; + export const setSelectedTimezone = ({ commit }, timezone) => { commit(types.SET_SELECTED_TIMEZONE, timezone); }; diff --git a/app/assets/javascripts/deploy_freeze/store/mutation_types.js b/app/assets/javascripts/deploy_freeze/store/mutation_types.js index 47a4874a5cf..8e6fdfd4443 100644 --- a/app/assets/javascripts/deploy_freeze/store/mutation_types.js +++ b/app/assets/javascripts/deploy_freeze/store/mutation_types.js @@ -6,6 +6,7 @@ export const RECEIVE_ADD_FREEZE_PERIOD_SUCCESS = 'RECEIVE_ADD_FREEZE_PERIOD_SUCC export const RECEIVE_ADD_FREEZE_PERIOD_ERROR = 'RECEIVE_ADD_FREEZE_PERIOD_ERROR'; export const SET_SELECTED_TIMEZONE = 'SET_SELECTED_TIMEZONE'; +export const SET_SELECTED_ID = 'SET_SELECTED_ID'; export const SET_FREEZE_START_CRON = 'SET_FREEZE_START_CRON'; export const SET_FREEZE_END_CRON = 'SET_FREEZE_END_CRON'; diff --git a/app/assets/javascripts/deploy_freeze/store/mutations.js b/app/assets/javascripts/deploy_freeze/store/mutations.js index 3b34f3950e6..e62000c007c 100644 --- a/app/assets/javascripts/deploy_freeze/store/mutations.js +++ b/app/assets/javascripts/deploy_freeze/store/mutations.js @@ -4,7 +4,11 @@ import * as types from './mutation_types'; const formatTimezoneName = (freezePeriod, timezoneList) => convertObjectPropsToCamelCase({ ...freezePeriod, - cron_timezone: timezoneList.find((tz) => tz.identifier === freezePeriod.cron_timezone)?.name, + cron_timezone: { + formattedTimezone: timezoneList.find((tz) => tz.identifier === freezePeriod.cron_timezone) + ?.name, + identifier: freezePeriod.cronTimezone, + }, }); export default { @@ -45,10 +49,15 @@ export default { state.freezeEndCron = freezeEndCron; }, + [types.SET_SELECTED_ID](state, id) { + state.selectedId = id; + }, + [types.RESET_MODAL](state) { state.freezeStartCron = ''; state.freezeEndCron = ''; state.selectedTimezone = ''; state.selectedTimezoneIdentifier = ''; + state.selectedId = ''; }, }; diff --git a/app/assets/javascripts/deploy_freeze/store/state.js b/app/assets/javascripts/deploy_freeze/store/state.js index 4cc38c097b6..1b16b4c645b 100644 --- a/app/assets/javascripts/deploy_freeze/store/state.js +++ b/app/assets/javascripts/deploy_freeze/store/state.js @@ -6,6 +6,7 @@ export default ({ selectedTimezoneIdentifier = '', freezeStartCron = '', freezeEndCron = '', + selectedId = '', }) => ({ projectId, freezePeriods, @@ -14,4 +15,5 @@ export default ({ selectedTimezoneIdentifier, freezeStartCron, freezeEndCron, + selectedId, }); diff --git a/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue b/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue index 9d5f37dc3b7..0746725153d 100644 --- a/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue +++ b/app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue @@ -26,7 +26,10 @@ export default { class="settings no-animate qa-incident-management-settings" > <div class="settings-header"> - <h4 ref="sectionHeader"> + <h4 + ref="sectionHeader" + class="settings-title js-settings-toggle js-settings-toggle-trigger-only" + > {{ $options.i18n.headerText }} </h4> <gl-button ref="toggleBtn" class="js-settings-toggle">{{ diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js index e3c6b0f6f5b..a6e3a7dc08a 100644 --- a/app/assets/javascripts/pages/admin/application_settings/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/index.js @@ -4,9 +4,7 @@ import initSearchSettings from '~/search_settings'; import selfMonitor from '~/self_monitor'; import initSettingsPanels from '~/settings_panels'; -if (gon.features?.ciInstanceVariablesUi) { - initVariableList('js-instance-variables'); -} +initVariableList('js-instance-variables'); selfMonitor(); // Initialize expandable settings panels initSettingsPanels(); diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 5d507abdb6f..27f1e4a68cc 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -13,10 +13,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController before_action :disable_query_limiting, only: [:usage_data] - before_action only: [:ci_cd] do - push_frontend_feature_flag(:ci_instance_variables_ui, default_enabled: true) - end - feature_category :not_owned, [ :general, :reporting, :metrics_and_profiling, :network, :preferences, :update, :reset_health_check_token diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf1dfcd6416..32de4a0145c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -462,7 +462,7 @@ class ApplicationController < ActionController::Base feature_category: feature_category) do yield ensure - @current_context = Labkit::Context.current.to_h + @current_context = Gitlab::ApplicationContext.current end end diff --git a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb index 32ca6de9b96..ea1502d4b62 100644 --- a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb +++ b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb @@ -8,7 +8,7 @@ module Mutations ADMIN_MESSAGE = 'You must be an admin to use this mutation' - Labkit::Context::KNOWN_KEYS.each do |key| + Gitlab::ApplicationContext::KNOWN_KEYS.each do |key| argument key, GraphQL::STRING_TYPE, required: false, diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb index 7a030372591..15458caa9f3 100644 --- a/app/serializers/build_artifact_entity.rb +++ b/app/serializers/build_artifact_entity.rb @@ -21,11 +21,17 @@ class BuildArtifactEntity < Grape::Entity ) end - expose :keep_path, if: -> (*) { artifact.expiring? } do |artifact| + expose :keep_path, if: -> (*) { artifact.expiring? && show_duplicated_paths?(artifact.project) } do |artifact| fast_keep_project_job_artifacts_path(artifact.project, artifact.job) end - expose :browse_path do |artifact| + expose :browse_path, if: -> (*) { show_duplicated_paths?(artifact.project) } do |artifact| fast_browse_project_job_artifacts_path(artifact.project, artifact.job) end + + private + + def show_duplicated_paths?(project) + !Gitlab::Ci::Features.remove_duplicate_artifact_exposure_paths?(project) + end end diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml index 485fb71d111..28572fccb9d 100644 --- a/app/views/admin/application_settings/ci_cd.html.haml +++ b/app/views/admin/application_settings/ci_cd.html.haml @@ -2,16 +2,15 @@ - page_title _("CI/CD") - @content_class = "limit-container-width" unless fluid_layout -- if ::Gitlab::Ci::Features.instance_variables_ui_enabled? - %section.settings.no-animate#js-ci-cd-variables{ class: ('expanded' if expanded_by_default?) } - .settings-header - = render 'admin/application_settings/ci/header', expanded: expanded_by_default? - .settings-content - - if ci_variable_protected_by_default? - %p.settings-message.text-center - - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protect-a-custom-variable') } - = s_('Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } - #js-instance-variables{ data: { endpoint: admin_ci_variables_path, group: 'true', maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} } +%section.settings.no-animate#js-ci-cd-variables{ class: ('expanded' if expanded_by_default?) } + .settings-header + = render 'admin/application_settings/ci/header', expanded: expanded_by_default? + .settings-content + - if ci_variable_protected_by_default? + %p.settings-message.text-center + - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protect-a-custom-variable') } + = s_('Environment variables on this GitLab instance are configured to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } + #js-instance-variables{ data: { endpoint: admin_ci_variables_path, group: 'true', maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} } %section.settings.as-ci-cd.no-animate#js-ci-cd-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/projects/_service_desk_settings.html.haml b/app/views/projects/_service_desk_settings.html.haml index 53b9e7f3d65..7b345941cf7 100644 --- a/app/views/projects/_service_desk_settings.html.haml +++ b/app/views/projects/_service_desk_settings.html.haml @@ -2,7 +2,7 @@ %section.settings.js-service-desk-setting-wrapper.no-animate#js-service-desk{ class: ('expanded' if expanded), data: { qa_selector: 'service_desk_settings_content' } } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Service Desk') - %button.btn.gl-button.js-settings-toggle + %button.btn.gl-button.btn-default.js-settings-toggle = expanded ? _('Collapse') : _('Expand') - link_start = "<a href='#{help_page_path('user/project/service_desk')}' target='_blank' rel='noopener noreferrer'>".html_safe %p= _('Enable and disable Service Desk. Some additional configuration might be required. %{link_start}Learn more%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb index d101ef100d8..0de26e27631 100644 --- a/app/workers/concerns/application_worker.rb +++ b/app/workers/concerns/application_worker.rb @@ -18,7 +18,7 @@ module ApplicationWorker set_queue def structured_payload(payload = {}) - context = Labkit::Context.current.to_h.merge( + context = Gitlab::ApplicationContext.current.merge( 'class' => self.class.name, 'job_status' => 'running', 'queue' => self.class.queue, diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb index 955387b5ad4..b89d6bba72c 100644 --- a/app/workers/concerns/cronjob_queue.rb +++ b/app/workers/concerns/cronjob_queue.rb @@ -15,7 +15,7 @@ module CronjobQueue # Cronjobs never get scheduled with arguments, so this is safe to # override def context_for_arguments(_args) - return if Gitlab::ApplicationContext.current_context_include?('meta.caller_id') + return if Gitlab::ApplicationContext.current_context_include?(:caller_id) Gitlab::ApplicationContext.new(caller_id: "Cronjob") end diff --git a/changelogs/unreleased/205484-prj-set-ops-3-incidents.yml b/changelogs/unreleased/205484-prj-set-ops-3-incidents.yml new file mode 100644 index 00000000000..830bc130c76 --- /dev/null +++ b/changelogs/unreleased/205484-prj-set-ops-3-incidents.yml @@ -0,0 +1,5 @@ +--- +title: Project Settings Operations header Incidents expand/collapse on-click/on-tap +merge_request: 56273 +author: Daniel Schömer +type: changed diff --git a/changelogs/unreleased/afontaine-edit-deploy-freeze.yml b/changelogs/unreleased/afontaine-edit-deploy-freeze.yml new file mode 100644 index 00000000000..fdb1cf09c88 --- /dev/null +++ b/changelogs/unreleased/afontaine-edit-deploy-freeze.yml @@ -0,0 +1,5 @@ +--- +title: Add Ability to Edit Freeze Periods +merge_request: 56407 +author: +type: added diff --git a/changelogs/unreleased/btn-default-sd-toggle.yml b/changelogs/unreleased/btn-default-sd-toggle.yml new file mode 100644 index 00000000000..ded6ea651e7 --- /dev/null +++ b/changelogs/unreleased/btn-default-sd-toggle.yml @@ -0,0 +1,5 @@ +--- +title: Add btn-default class for Service Desk toggle in settings +merge_request: 56195 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/jivanvl-remove-ci-instance-variables-ui-ff.yml b/changelogs/unreleased/jivanvl-remove-ci-instance-variables-ui-ff.yml new file mode 100644 index 00000000000..dfea1fa4875 --- /dev/null +++ b/changelogs/unreleased/jivanvl-remove-ci-instance-variables-ui-ff.yml @@ -0,0 +1,5 @@ +--- +title: Enable the instance variables UI +merge_request: 56255 +author: +type: other diff --git a/config/feature_flags/development/ci_instance_variables_ui.yml b/config/feature_flags/development/ci_instance_variables_ui.yml deleted file mode 100644 index 73bc0346818..00000000000 --- a/config/feature_flags/development/ci_instance_variables_ui.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_instance_variables_ui -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33510 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299879 -milestone: '13.1' -type: development -group: group::continuous integration -default_enabled: true diff --git a/config/feature_flags/development/remove_duplicate_artifact_exposure_paths.yml b/config/feature_flags/development/remove_duplicate_artifact_exposure_paths.yml new file mode 100644 index 00000000000..3913590a6be --- /dev/null +++ b/config/feature_flags/development/remove_duplicate_artifact_exposure_paths.yml @@ -0,0 +1,8 @@ +--- +name: remove_duplicate_artifact_exposure_paths +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54611 +rollout_issue_url: +milestone: '13.10' +type: development +group: group::testing +default_enabled: false diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md index f935fa87d68..96b5a8513c5 100644 --- a/doc/user/packages/composer_repository/index.md +++ b/doc/user/packages/composer_repository/index.md @@ -268,7 +268,13 @@ To install a package: Without the `gitlab-domains` definition in `composer.json`, Composer uses the GitLab token as basic-auth, with the token as a username and a blank password. This results in a 401 error. -Output indicates that the package has been successfully installed. +1. With the `composer.json` and `auth.json` files configured, you can install the package by running: + + ```shell + composer update + ``` + + If successful, you should see output indicating that the package installed successfully. WARNING: Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, diff --git a/doc/user/project/img/issue_board_iteration_lists_v13_10.png b/doc/user/project/img/issue_board_iteration_lists_v13_10.png Binary files differnew file mode 100644 index 00000000000..be59c609a77 --- /dev/null +++ b/doc/user/project/img/issue_board_iteration_lists_v13_10.png diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 98e75ba40e1..24eee5c3018 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -340,6 +340,31 @@ As in other list types, click the trash icon to remove a list. ![Milestone lists](img/issue_board_milestone_lists_v13_6.png) +### Iteration lists **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.10. +> - It's [deployed behind the `board_new_lists` feature flag](../feature_flags.md), disabled by default. +> - It's disabled on GitLab.com. +> - It's recommended for production use. +> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-new-add-list-form). + +WARNING: +This feature might not be available to you. Check the **version history** note above for details. + +You're also able to create lists of an iteration. +These are lists that filter issues by the assigned +iteration. To add an iteration list: + +1. Select **Create list**. +1. Select the **Iteration**. +1. In the dropdown, select an iteration. +1. Select **Add to board**. + +Like the milestone lists, you're able to [drag issues](#drag-issues-between-lists) +to and from a iteration list to manipulate the iteration of the dragged issues. + +![Iteration lists](img/issue_board_iteration_lists_v13_10.png) + ### Group issues in swimlanes **(PREMIUM)** > - Grouping by epic [introduced](https://gitlab.com/groups/gitlab-org/-/epics/3352) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6. @@ -649,3 +674,22 @@ To disable it: ```ruby Feature.disable(:add_issues_button) ``` + +### Enable or disable new add list form **(FREE SELF)** + +The new form for adding lists is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:board_new_list) +``` + +To disable it: + +```ruby +Feature.disable(:board_new_list) +``` diff --git a/doc/user/project/releases/img/deploy_freeze_v13_10.png b/doc/user/project/releases/img/deploy_freeze_v13_10.png Binary files differnew file mode 100644 index 00000000000..5c4b2d983dd --- /dev/null +++ b/doc/user/project/releases/img/deploy_freeze_v13_10.png diff --git a/doc/user/project/releases/img/deploy_freeze_v13_2.png b/doc/user/project/releases/img/deploy_freeze_v13_2.png Binary files differdeleted file mode 100644 index 27d3a6044a1..00000000000 --- a/doc/user/project/releases/img/deploy_freeze_v13_2.png +++ /dev/null diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md index 7348e17a017..3aba5e7853f 100644 --- a/doc/user/project/releases/index.md +++ b/doc/user/project/releases/index.md @@ -217,11 +217,11 @@ To set a deploy freeze window in the UI, complete these steps: 1. Click **Add deploy freeze** to open the deploy freeze modal. 1. Enter the start time, end time, and timezone of the desired deploy freeze period. 1. Click **Add deploy freeze** in the modal. - -![Deploy freeze modal for setting a deploy freeze period](img/deploy_freeze_v13_2.png) +1. After the deploy freeze is saved, you can edit it by selecting the edit button (**{pencil}**). + ![Deploy freeze modal for setting a deploy freeze period](img/deploy_freeze_v13_10.png) WARNING: -To edit or delete a deploy freeze, use the [Freeze Periods API](../../../api/freeze_periods.md). +To delete a deploy freeze, use the [Freeze Periods API](../../../api/freeze_periods.md). If a project contains multiple freeze periods, all periods apply. If they overlap, the freeze covers the complete overlapping period. diff --git a/jest.config.base.js b/jest.config.base.js index 4e9b84d1d34..745a179af6d 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -45,7 +45,8 @@ module.exports = (path) => { 'emojis(/.*).json': '<rootDir>/fixtures/emojis$1.json', '^spec/test_constants$': '<rootDir>/spec/frontend/__helpers__/test_constants', '^jest/(.*)$': '<rootDir>/spec/frontend/$1', - 'test_helpers(/.*)$': '<rootDir>/spec/frontend_integration/test_helpers$1', + '^test_helpers(/.*)$': '<rootDir>/spec/frontend_integration/test_helpers$1', + '^ee_else_ce_test_helpers(/.*)$': '<rootDir>/spec/frontend_integration/test_helpers$1', }; const collectCoverageFrom = ['<rootDir>/app/assets/javascripts/**/*.{js,vue}']; @@ -56,6 +57,7 @@ module.exports = (path) => { '^ee(/.*)$': rootDirEE, '^ee_component(/.*)$': rootDirEE, '^ee_else_ce(/.*)$': rootDirEE, + '^ee_else_ce_test_helpers(/.*)$': '<rootDir>/ee/spec/frontend_integration/test_helpers$1', '^ee_jest/(.*)$': '<rootDir>/ee/spec/frontend/$1', [TEST_FIXTURES_PATTERN]: '<rootDir>/tmp/tests/frontend/fixtures-ee$1', }); diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb index 7e561783685..d91d4a0d4d5 100644 --- a/lib/api/admin/sidekiq.rb +++ b/lib/api/admin/sidekiq.rb @@ -12,11 +12,11 @@ module API namespace 'queues' do desc 'Drop jobs matching the given metadata from the Sidekiq queue' params do - Labkit::Context::KNOWN_KEYS.each do |key| + Gitlab::ApplicationContext::KNOWN_KEYS.each do |key| optional key, type: String, allow_blank: false end - at_least_one_of(*Labkit::Context::KNOWN_KEYS) + at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS) end delete ':queue_name' do result = diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb index a75da3a682b..ceda82cb6f6 100644 --- a/lib/gitlab/application_context.rb +++ b/lib/gitlab/application_context.rb @@ -8,6 +8,9 @@ module Gitlab Attribute = Struct.new(:name, :type) + LOG_KEY = Labkit::Context::LOG_KEY + KNOWN_KEYS = Labkit::Context::KNOWN_KEYS + APPLICATION_ATTRIBUTES = [ Attribute.new(:project, Project), Attribute.new(:namespace, Namespace), @@ -24,6 +27,10 @@ module Gitlab application_context.use(&block) end + def self.with_raw_context(attributes = {}, &block) + Labkit::Context.with_context(attributes, &block) + end + def self.push(args) application_context = new(**args) Labkit::Context.push(application_context.to_lazy_hash) diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index c811ef211d6..79139ceb419 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -10,10 +10,6 @@ module Gitlab ::Feature.enabled?(:ci_artifacts_exclude, default_enabled: true) end - def self.instance_variables_ui_enabled? - ::Feature.enabled?(:ci_instance_variables_ui, default_enabled: true) - end - def self.pipeline_latest? ::Feature.enabled?(:ci_pipeline_latest, default_enabled: true) end @@ -71,6 +67,10 @@ module Gitlab def self.ci_commit_pipeline_mini_graph_vue_enabled?(project) ::Feature.enabled?(:ci_commit_pipeline_mini_graph_vue, project, default_enabled: :yaml) end + + def self.remove_duplicate_artifact_exposure_paths?(project) + ::Feature.enabled?(:remove_duplicate_artifact_exposure_paths, project, default_enabled: :yaml) + end end end end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index e3788814dd5..f4a89edecd1 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -215,7 +215,7 @@ module Gitlab 'client_name' => CLIENT_NAME } - context_data = Labkit::Context.current&.to_h + context_data = Gitlab::ApplicationContext.current feature_stack = Thread.current[:gitaly_feature_stack] feature = feature_stack && feature_stack[0] diff --git a/lib/gitlab/grape_logging/loggers/context_logger.rb b/lib/gitlab/grape_logging/loggers/context_logger.rb index 0a8f0872fbe..468a296886e 100644 --- a/lib/gitlab/grape_logging/loggers/context_logger.rb +++ b/lib/gitlab/grape_logging/loggers/context_logger.rb @@ -6,7 +6,7 @@ module Gitlab module Loggers class ContextLogger < ::GrapeLogging::Loggers::Base def parameters(_, _) - Labkit::Context.current.to_h + Gitlab::ApplicationContext.current end end end diff --git a/lib/gitlab/sidekiq_queue.rb b/lib/gitlab/sidekiq_queue.rb index 807c27a71ff..4b71dfc0c1b 100644 --- a/lib/gitlab/sidekiq_queue.rb +++ b/lib/gitlab/sidekiq_queue.rb @@ -21,7 +21,7 @@ module Gitlab job_search_metadata = search_metadata .stringify_keys - .slice(*Labkit::Context::KNOWN_KEYS) + .slice(*Gitlab::ApplicationContext::KNOWN_KEYS) .transform_keys { |key| "meta.#{key}" } .compact diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9af2693ca1b..7e0d2eb2f6f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10245,13 +10245,19 @@ msgstr "" msgid "DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}" msgstr "" +msgid "DeployFreeze|Add deploy freeze" +msgstr "" + +msgid "DeployFreeze|Edit" +msgstr "" + msgid "DeployFreeze|Freeze end" msgstr "" msgid "DeployFreeze|Freeze start" msgstr "" -msgid "DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}" +msgid "DeployFreeze|No deploy freezes exist for this project. To add one, select %{strongStart}Add deploy freeze%{strongEnd}" msgstr "" msgid "DeployFreeze|Specify deploy freezes using %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}." @@ -11191,6 +11197,9 @@ msgstr "" msgid "Edit comment" msgstr "" +msgid "Edit deploy freeze" +msgstr "" + msgid "Edit description" msgstr "" @@ -26436,6 +26445,9 @@ msgstr "" msgid "Save comment" msgstr "" +msgid "Save deploy freeze" +msgstr "" + msgid "Save password" msgstr "" diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4809a07f6e9..3d34db6c2c0 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -898,7 +898,7 @@ RSpec.describe ApplicationController do feature_category :issue_tracking def index - Labkit::Context.with_context do |context| + Gitlab::ApplicationContext.with_raw_context do |context| render json: context.to_h end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index c31ba6fe156..5bee7698c3f 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -524,7 +524,7 @@ RSpec.describe SessionsController do it 'sets the username and caller_id in the context' do expect(controller).to receive(:destroy).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.user' => user.username, 'meta.caller_id' => 'SessionsController#destroy') @@ -538,9 +538,9 @@ RSpec.describe SessionsController do context 'when not signed in' do it 'sets the caller_id in the context' do expect(controller).to receive(:new).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#new') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) @@ -557,9 +557,9 @@ RSpec.describe SessionsController do it 'sets the caller_id in the context' do allow_any_instance_of(User).to receive(:lock_access!).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#create') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js index d6e1b170dd3..f875e3bdb07 100644 --- a/spec/frontend/api_spec.js +++ b/spec/frontend/api_spec.js @@ -1382,6 +1382,38 @@ describe('Api', () => { }); }); + describe('updateFreezePeriod', () => { + const options = { + id: 10, + freeze_start: '* * * * *', + freeze_end: '* * * * *', + cron_timezone: 'America/Juneau', + created_at: '2020-07-11T07:04:50.153Z', + updated_at: '2020-07-11T07:04:50.153Z', + }; + const projectId = 8; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/freeze_periods/${options.id}`; + + const expectedResult = { + id: 10, + freeze_start: '* * * * *', + freeze_end: '* * * * *', + cron_timezone: 'America/Juneau', + created_at: '2020-07-11T07:04:50.153Z', + updated_at: '2020-07-11T07:04:50.153Z', + }; + + describe('when the freeze period is successfully updated', () => { + it('resolves the Promise', () => { + mock.onPut(expectedUrl, options).replyOnce(httpStatus.OK, expectedResult); + + return Api.updateFreezePeriod(projectId, options).then(({ data }) => { + expect(data).toStrictEqual(expectedResult); + }); + }); + }); + }); + describe('createPipeline', () => { it('creates new pipeline', () => { const redirectUrl = 'ci-project/-/pipelines/95'; diff --git a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js index d8ce184940a..7c46c280d46 100644 --- a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js +++ b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js @@ -1,13 +1,16 @@ import { GlButton, GlModal } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; +import Api from '~/api'; import DeployFreezeModal from '~/deploy_freeze/components/deploy_freeze_modal.vue'; import createStore from '~/deploy_freeze/store'; import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue'; import { freezePeriodsFixture, timezoneDataFixture } from '../helpers'; -const localVue = createLocalVue(); -localVue.use(Vuex); +jest.mock('~/api'); + +Vue.use(Vuex); describe('Deploy freeze modal', () => { let wrapper; @@ -23,18 +26,19 @@ describe('Deploy freeze modal', () => { stubs: { GlModal, }, - localVue, store, }); }); - const findModal = () => wrapper.find(GlModal); - const addDeployFreezeButton = () => findModal().findAll(GlButton).at(1); + const findModal = () => wrapper.findComponent(GlModal); + const submitDeployFreezeButton = () => findModal().findAllComponents(GlButton).at(1); - const setInput = (freezeStartCron, freezeEndCron, selectedTimezone) => { + const setInput = (freezeStartCron, freezeEndCron, selectedTimezone, id = '') => { store.state.freezeStartCron = freezeStartCron; store.state.freezeEndCron = freezeEndCron; store.state.selectedTimezone = selectedTimezone; + store.state.selectedTimezoneIdentifier = selectedTimezone; + store.state.selectedId = id; wrapper.find('#deploy-freeze-start').trigger('input'); wrapper.find('#deploy-freeze-end').trigger('input'); @@ -48,18 +52,36 @@ describe('Deploy freeze modal', () => { describe('Basic interactions', () => { it('button is disabled when freeze period is invalid', () => { - expect(addDeployFreezeButton().attributes('disabled')).toBeTruthy(); + expect(submitDeployFreezeButton().attributes('disabled')).toBeTruthy(); }); }); describe('Adding a new deploy freeze', () => { + const { freeze_start, freeze_end, cron_timezone } = freezePeriodsFixture[0]; + beforeEach(() => { - const { freeze_start, freeze_end, cron_timezone } = freezePeriodsFixture[0]; setInput(freeze_start, freeze_end, cron_timezone); }); it('button is enabled when valid freeze period settings are present', () => { - expect(addDeployFreezeButton().attributes('disabled')).toBeUndefined(); + expect(submitDeployFreezeButton().attributes('disabled')).toBeUndefined(); + }); + + it('should display Add deploy freeze', () => { + expect(findModal().props('title')).toBe('Add deploy freeze'); + expect(submitDeployFreezeButton().text()).toBe('Add deploy freeze'); + }); + + it('should call the add deploy freze API', () => { + Api.createFreezePeriod.mockResolvedValue(); + findModal().vm.$emit('primary'); + + expect(Api.createFreezePeriod).toHaveBeenCalledTimes(1); + expect(Api.createFreezePeriod).toHaveBeenCalledWith(store.state.projectId, { + freeze_start, + freeze_end, + cron_timezone, + }); }); }); @@ -70,7 +92,7 @@ describe('Deploy freeze modal', () => { }); it('disables the add deploy freeze button', () => { - expect(addDeployFreezeButton().attributes('disabled')).toBeTruthy(); + expect(submitDeployFreezeButton().attributes('disabled')).toBeTruthy(); }); }); @@ -81,7 +103,32 @@ describe('Deploy freeze modal', () => { }); it('does not disable the submit button', () => { - expect(addDeployFreezeButton().attributes('disabled')).toBeFalsy(); + expect(submitDeployFreezeButton().attributes('disabled')).toBeFalsy(); + }); + }); + }); + + describe('Editing an existing deploy freeze', () => { + const { freeze_start, freeze_end, cron_timezone, id } = freezePeriodsFixture[0]; + beforeEach(() => { + setInput(freeze_start, freeze_end, cron_timezone, id); + }); + + it('should display Edit deploy freeze', () => { + expect(findModal().props('title')).toBe('Edit deploy freeze'); + expect(submitDeployFreezeButton().text()).toBe('Save deploy freeze'); + }); + + it('should call the update deploy freze API', () => { + Api.updateFreezePeriod.mockResolvedValue(); + findModal().vm.$emit('primary'); + + expect(Api.updateFreezePeriod).toHaveBeenCalledTimes(1); + expect(Api.updateFreezePeriod).toHaveBeenCalledWith(store.state.projectId, { + id, + freeze_start, + freeze_end, + cron_timezone, }); }); }); diff --git a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js index e4ee1b9ad26..168ddcfeacc 100644 --- a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js +++ b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js @@ -2,6 +2,7 @@ import { createLocalVue, mount } from '@vue/test-utils'; import Vuex from 'vuex'; import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vue'; import createStore from '~/deploy_freeze/store'; +import { RECEIVE_FREEZE_PERIODS_SUCCESS } from '~/deploy_freeze/store/mutation_types'; import { freezePeriodsFixture, timezoneDataFixture } from '../helpers'; const localVue = createLocalVue(); @@ -26,6 +27,7 @@ describe('Deploy freeze table', () => { const findEmptyFreezePeriods = () => wrapper.find('[data-testid="empty-freeze-periods"]'); const findAddDeployFreezeButton = () => wrapper.find('[data-testid="add-deploy-freeze"]'); + const findEditDeployFreezeButton = () => wrapper.find('[data-testid="edit-deploy-freeze"]'); const findDeployFreezeTable = () => wrapper.find('[data-testid="deploy-freeze-table"]'); beforeEach(() => { @@ -45,17 +47,31 @@ describe('Deploy freeze table', () => { it('displays empty', () => { expect(findEmptyFreezePeriods().exists()).toBe(true); expect(findEmptyFreezePeriods().text()).toBe( - 'No deploy freezes exist for this project. To add one, click Add deploy freeze', + 'No deploy freezes exist for this project. To add one, select Add deploy freeze', ); }); - it('displays data', () => { - store.state.freezePeriods = freezePeriodsFixture; + describe('with data', () => { + beforeEach(async () => { + store.commit(RECEIVE_FREEZE_PERIODS_SUCCESS, freezePeriodsFixture); + await wrapper.vm.$nextTick(); + }); - return wrapper.vm.$nextTick(() => { + it('displays data', () => { const tableRows = findDeployFreezeTable().findAll('tbody tr'); expect(tableRows.length).toBe(freezePeriodsFixture.length); expect(findEmptyFreezePeriods().exists()).toBe(false); + expect(findEditDeployFreezeButton().exists()).toBe(true); + }); + + it('allows user to edit deploy freeze', async () => { + findEditDeployFreezeButton().trigger('click'); + await wrapper.vm.$nextTick(); + + expect(store.dispatch).toHaveBeenCalledWith( + 'setFreezePeriod', + store.state.freezePeriods[0], + ); }); }); }); diff --git a/spec/frontend/deploy_freeze/store/actions_spec.js b/spec/frontend/deploy_freeze/store/actions_spec.js index f4d9802e39a..9c784f3c5a2 100644 --- a/spec/frontend/deploy_freeze/store/actions_spec.js +++ b/spec/frontend/deploy_freeze/store/actions_spec.js @@ -23,12 +23,46 @@ describe('deploy freeze store actions', () => { }); Api.freezePeriods.mockResolvedValue({ data: freezePeriodsFixture }); Api.createFreezePeriod.mockResolvedValue(); + Api.updateFreezePeriod.mockResolvedValue(); }); afterEach(() => { mock.restore(); }); + describe('setSelectedFreezePeriod', () => { + it('commits SET_SELECTED_TIMEZONE mutation', () => { + testAction( + actions.setFreezePeriod, + { + id: 3, + cronTimezone: 'UTC', + freezeStart: 'start', + freezeEnd: 'end', + }, + {}, + [ + { + payload: 3, + type: types.SET_SELECTED_ID, + }, + { + payload: 'UTC', + type: types.SET_SELECTED_TIMEZONE, + }, + { + payload: 'start', + type: types.SET_FREEZE_START_CRON, + }, + { + payload: 'end', + type: types.SET_FREEZE_END_CRON, + }, + ], + ); + }); + }); + describe('setSelectedTimezone', () => { it('commits SET_SELECTED_TIMEZONE mutation', () => { testAction(actions.setSelectedTimezone, {}, {}, [ @@ -68,10 +102,16 @@ describe('deploy freeze store actions', () => { state, [{ type: 'RESET_MODAL' }], [ - { type: 'requestAddFreezePeriod' }, - { type: 'receiveAddFreezePeriodSuccess' }, + { type: 'requestFreezePeriod' }, + { type: 'receiveFreezePeriodSuccess' }, { type: 'fetchFreezePeriods' }, ], + () => + expect(Api.createFreezePeriod).toHaveBeenCalledWith(state.projectId, { + freeze_start: state.freezeStartCron, + freeze_end: state.freezeEndCron, + cron_timezone: state.selectedTimezoneIdentifier, + }), ); }); @@ -83,7 +123,43 @@ describe('deploy freeze store actions', () => { {}, state, [], - [{ type: 'requestAddFreezePeriod' }, { type: 'receiveAddFreezePeriodError' }], + [{ type: 'requestFreezePeriod' }, { type: 'receiveFreezePeriodError' }], + () => expect(createFlash).toHaveBeenCalled(), + ); + }); + }); + + describe('updateFreezePeriod', () => { + it('dispatch correct actions on updating a freeze period', () => { + testAction( + actions.updateFreezePeriod, + {}, + state, + [{ type: 'RESET_MODAL' }], + [ + { type: 'requestFreezePeriod' }, + { type: 'receiveFreezePeriodSuccess' }, + { type: 'fetchFreezePeriods' }, + ], + () => + expect(Api.updateFreezePeriod).toHaveBeenCalledWith(state.projectId, { + id: state.selectedId, + freeze_start: state.freezeStartCron, + freeze_end: state.freezeEndCron, + cron_timezone: state.selectedTimezoneIdentifier, + }), + ); + }); + + it('should show flash error and set error in state on add failure', () => { + Api.updateFreezePeriod.mockRejectedValue(); + + testAction( + actions.updateFreezePeriod, + {}, + state, + [], + [{ type: 'requestFreezePeriod' }, { type: 'receiveFreezePeriodError' }], () => expect(createFlash).toHaveBeenCalled(), ); }); diff --git a/spec/frontend/deploy_freeze/store/mutations_spec.js b/spec/frontend/deploy_freeze/store/mutations_spec.js index 54cbdfcb64c..ce75e3b89c3 100644 --- a/spec/frontend/deploy_freeze/store/mutations_spec.js +++ b/spec/frontend/deploy_freeze/store/mutations_spec.js @@ -33,7 +33,10 @@ describe('Deploy freeze mutations', () => { const expectedFreezePeriods = freezePeriodsFixture.map((freezePeriod, index) => ({ ...convertObjectPropsToCamelCase(freezePeriod), - cronTimezone: timezoneNames[index], + cronTimezone: { + formattedTimezone: timezoneNames[index], + identifier: freezePeriod.cronTimezone, + }, })); expect(stateCopy.freezePeriods).toMatchObject(expectedFreezePeriods); @@ -62,11 +65,19 @@ describe('Deploy freeze mutations', () => { }); }); - describe('SET_FREEZE_ENDT_CRON', () => { + describe('SET_FREEZE_END_CRON', () => { it('should set freezeEndCron', () => { mutations[types.SET_FREEZE_END_CRON](stateCopy, '5 0 * 8 *'); expect(stateCopy.freezeEndCron).toBe('5 0 * 8 *'); }); }); + + describe('SET_SELECTED_ID', () => { + it('should set selectedId', () => { + mutations[types.SET_SELECTED_ID](stateCopy, 5); + + expect(stateCopy.selectedId).toBe(5); + }); + }); }); diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap index 4398d568501..07f90a12f0f 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap @@ -9,7 +9,9 @@ exports[`IncidentsSettingTabs should render the component 1`] = ` <div class="settings-header" > - <h4> + <h4 + class="settings-title js-settings-toggle js-settings-toggle-trigger-only" + > Incidents diff --git a/spec/frontend_integration/test_helpers/mock_server/index.js b/spec/frontend_integration/test_helpers/mock_server/index.js index 20cb441daa7..486c9452dbd 100644 --- a/spec/frontend_integration/test_helpers/mock_server/index.js +++ b/spec/frontend_integration/test_helpers/mock_server/index.js @@ -1,4 +1,5 @@ import { Server, Model, RestSerializer } from 'miragejs'; +import setupRoutes from 'ee_else_ce_test_helpers/mock_server/routes'; import { getProject, getEmptyProject, @@ -11,7 +12,6 @@ import { getBlobImage, getBlobZip, } from 'test_helpers/fixtures'; -import setupRoutes from './routes'; export const createMockServerOptions = () => ({ models: { diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 0fbbc67ef6a..c4fe2ebaba9 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -27,6 +27,20 @@ RSpec.describe Gitlab::ApplicationContext do end end + describe '.with_raw_context' do + it 'yields the block' do + expect { |b| described_class.with_raw_context({}, &b) }.to yield_control + end + + it 'passes the attributes unaltered on to labkit' do + attrs = { foo: :bar } + + expect(Labkit::Context).to receive(:with_context).with(attrs) + + described_class.with_raw_context(attrs) {} + end + end + describe '.push' do it 'passes the expected context on to labkit' do fake_proc = duck_type(:call) @@ -138,7 +152,7 @@ RSpec.describe Gitlab::ApplicationContext do it 'does not cause queries' do context = described_class.new(project: create(:project), namespace: create(:group, :nested), user: create(:user)) - expect { context.use { Labkit::Context.current.to_h } }.not_to exceed_query_limit(0) + expect { context.use { Gitlab::ApplicationContext.current } }.not_to exceed_query_limit(0) end end end diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb index b31a2f7549a..15e52f7b6c8 100644 --- a/spec/lib/gitlab/metrics/background_transaction_spec.rb +++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do describe '#labels' do it 'provides labels with endpoint_id and feature_category' do - Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do + Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects' }) end end @@ -41,7 +41,7 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do value = 1 expect(prometheus_metric).to receive(metric_method).with({ endpoint_id: 'TestWorker', feature_category: 'projects' }, value) - Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do + Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do transaction.send(metric_method, :test_metric, value) end end diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb index ca473462d2e..f736a7db774 100644 --- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do worker_context user: nil def perform(identifier, *args) - self.class.contexts.merge!(identifier => Labkit::Context.current.to_h) + self.class.contexts.merge!(identifier => Gitlab::ApplicationContext.current) end end end diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index 522030652bd..7e670724c11 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -105,7 +105,7 @@ RSpec.describe API::API do it 'logs all application context fields' do allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do - Labkit::Context.current.to_h.tap do |log_context| + Gitlab::ApplicationContext.current.tap do |log_context| expect(log_context).to match('correlation_id' => an_instance_of(String), 'meta.caller_id' => '/api/:version/projects/:id/issues', 'meta.remote_ip' => an_instance_of(String), @@ -122,7 +122,7 @@ RSpec.describe API::API do it 'skips fields that do not apply' do allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do - Labkit::Context.current.to_h.tap do |log_context| + Gitlab::ApplicationContext.current.tap do |log_context| expect(log_context).to match('correlation_id' => an_instance_of(String), 'meta.caller_id' => '/api/:version/users', 'meta.remote_ip' => an_instance_of(String), diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb index 02c172d723f..87c1874ec41 100644 --- a/spec/serializers/build_artifact_entity_spec.rb +++ b/spec/serializers/build_artifact_entity_spec.rb @@ -21,15 +21,30 @@ RSpec.describe BuildArtifactEntity do expect(subject).to include(:expired, :expire_at) end - it 'contains paths to the artifacts' do - expect(subject[:path]) - .to include "jobs/#{job.id}/artifacts/download?file_type=codequality" + it 'exposes the artifact download path' do + expect(subject[:path]).to include "jobs/#{job.id}/artifacts/download?file_type=codequality" + end + + context 'with remove_duplicate_artifact_exposure_paths enabled' do + before do + stub_feature_flags(remove_duplicate_artifact_exposure_paths: true) + end + + it 'has no keep or browse path' do + expect(subject).not_to include(:keep_path) + expect(subject).not_to include(:browse_path) + end + end - expect(subject[:keep_path]) - .to include "jobs/#{job.id}/artifacts/keep" + context 'with remove_duplicate_artifact_exposure_paths disabled' do + before do + stub_feature_flags(remove_duplicate_artifact_exposure_paths: false) + end - expect(subject[:browse_path]) - .to include "jobs/#{job.id}/artifacts/browse" + it 'has keep and browse paths' do + expect(subject[:keep_path]).to be_present + expect(subject[:browse_path]).to be_present + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b15d91b41e4..5f1513b1247 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -333,10 +333,20 @@ RSpec.configure do |config| RequestStore.clear! end - config.around do |example| - # Wrap each example in it's own context to make sure the contexts don't - # leak - Labkit::Context.with_context { example.run } + if ENV['SKIP_RSPEC_CONTEXT_WRAPPING'] + config.around(:example, :context_aware) do |example| + # Wrap each example in it's own context to make sure the contexts don't + # leak + Gitlab::ApplicationContext.with_raw_context { example.run } + end + else + config.around do |example| + if [:controller, :request, :feature].include?(example.metadata[:type]) || example.metadata[:context_aware] + Gitlab::ApplicationContext.with_raw_context { example.run } + else + example.run + end + end end config.around do |example| diff --git a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb index bdb0316bf5a..c775ca182e6 100644 --- a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb +++ b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true RSpec.shared_examples 'API::CI::Runner application context metadata' do |api_route| - it 'contains correct context metadata' do + it 'contains correct context metadata', :context_aware do # Avoids popping the context from the thread so we can # check its content after the request. allow(Labkit::Context).to receive(:pop) send_request - Labkit::Context.with_context do |context| + Gitlab::ApplicationContext.with_raw_context do |context| expected_context = { 'meta.caller_id' => api_route, 'meta.user' => job.user.username, diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb index 4a71b696d57..57e28e6df57 100644 --- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb @@ -1,21 +1,13 @@ # frozen_string_literal: true RSpec.shared_examples 'storing arguments in the application context' do - around do |example| - Labkit::Context.with_context { example.run } - end - - it 'places the expected params in the application context' do + it 'places the expected params in the application context', :context_aware do # Stub the clearing of the context so we can validate it later - # The `around` block above makes sure we do clean it up later allow(Labkit::Context).to receive(:pop) subject - Labkit::Context.with_context do |context| - expect(context.to_h) - .to include(log_hash(expected_params)) - end + expect(Gitlab::ApplicationContext.current).to include(log_hash(expected_params)) end def log_hash(hash) diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb index 8094efcaf04..4575c270042 100644 --- a/spec/workers/background_migration_worker_spec.rb +++ b/spec/workers/background_migration_worker_spec.rb @@ -101,7 +101,7 @@ RSpec.describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do it 'sets the class that will be executed as the caller_id' do expect(Gitlab::BackgroundMigration).to receive(:perform) do - expect(Labkit::Context.current.to_h).to include('meta.caller_id' => 'Foo') + expect(Gitlab::ApplicationContext.current).to include('meta.caller_id' => 'Foo') end worker.perform('Foo', [10, 20]) diff --git a/spec/workers/concerns/worker_context_spec.rb b/spec/workers/concerns/worker_context_spec.rb index 3de37b99aba..ebdb752d900 100644 --- a/spec/workers/concerns/worker_context_spec.rb +++ b/spec/workers/concerns/worker_context_spec.rb @@ -103,7 +103,7 @@ RSpec.describe WorkerContext do describe '#with_context' do it 'allows modifying context when the job is running' do worker.new.with_context(user: build_stubbed(:user, username: 'jane-doe')) do - expect(Labkit::Context.current.to_h).to include('meta.user' => 'jane-doe') + expect(Gitlab::ApplicationContext.current).to include('meta.user' => 'jane-doe') end end diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 259148fa18f..259148fa18f 100755..100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore index a1c2a238a96..a1c2a238a96 100755..100644 --- a/vendor/gitignore/Java.gitignore +++ b/vendor/gitignore/Java.gitignore |