Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-16 21:11:53 +00:00
parent 889bf7a0ee
commit 7ea46a9866
59 changed files with 525 additions and 139 deletions

View File

@ -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;

View File

@ -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);
};

View File

@ -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>

View File

@ -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>

View File

@ -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);
};

View File

@ -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';

View File

@ -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 = '';
},
};

View File

@ -6,6 +6,7 @@ export default ({
selectedTimezoneIdentifier = '',
freezeStartCron = '',
freezeEndCron = '',
selectedId = '',
}) => ({
projectId,
freezePeriods,
@ -14,4 +15,5 @@ export default ({
selectedTimezoneIdentifier,
freezeStartCron,
freezeEndCron,
selectedId,
});

View File

@ -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">{{

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Add Ability to Edit Freeze Periods
merge_request: 56407
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add btn-default class for Service Desk toggle in settings
merge_request: 56195
author: Yogi (@yo)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Enable the instance variables UI
merge_request: 56255
author:
type: other

View File

@ -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

View File

@ -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

View File

@ -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,

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -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)
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -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.

View File

@ -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',
});

View File

@ -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 =

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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)

View File

@ -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';

View File

@ -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,
});
});
});

View File

@ -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],
);
});
});
});

View File

@ -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(),
);
});

View File

@ -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);
});
});
});

View File

@ -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

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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
expect(subject[:keep_path])
.to include "jobs/#{job.id}/artifacts/keep"
context 'with remove_duplicate_artifact_exposure_paths enabled' do
before do
stub_feature_flags(remove_duplicate_artifact_exposure_paths: true)
end
expect(subject[:browse_path])
.to include "jobs/#{job.id}/artifacts/browse"
it 'has no keep or browse path' do
expect(subject).not_to include(:keep_path)
expect(subject).not_to include(:browse_path)
end
end
context 'with remove_duplicate_artifact_exposure_paths disabled' do
before do
stub_feature_flags(remove_duplicate_artifact_exposure_paths: false)
end
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

View File

@ -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|

View File

@ -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,

View File

@ -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)

View File

@ -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])

View File

@ -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

0
vendor/gitignore/C++.gitignore vendored Executable file → Normal file
View File

0
vendor/gitignore/Java.gitignore vendored Executable file → Normal file
View File