summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-03 09:08:53 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-03 09:08:53 +0000
commit1c75400c24137f603678d0ee3d497b0c9280e7f7 (patch)
treeccf2e8584d8b7efd3c648a276ebe5b456639da3b
parent1f23012963babbcc586e7025cc28e62385813fb6 (diff)
downloadgitlab-ce-1c75400c24137f603678d0ee3d497b0c9280e7f7.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue17
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue76
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/variables/dropdown_field.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/variables/text_field.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/variables_section.vue11
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js10
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js15
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js15
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js6
-rw-r--r--app/assets/javascripts/monitoring/stores/variable_mapping.js77
-rw-r--r--app/assets/javascripts/monitoring/utils.js6
-rw-r--r--app/assets/stylesheets/pages/boards.scss31
-rw-r--r--app/helpers/notify_helper.rb11
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/services/event_create_service.rb4
-rw-r--r--app/services/merge_requests/approval_service.rb48
-rw-r--r--app/services/system_note_service.rb20
-rw-r--r--app/services/system_notes/merge_requests_service.rb13
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/merge_request_status_email.html.haml3
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.html.haml2
-rw-r--r--app/views/notify/merge_request_unmergeable_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/new_issue_email.html.haml2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.html.haml2
-rw-r--r--app/views/notify/push_to_merge_request_email.html.haml2
-rw-r--r--app/views/notify/push_to_merge_request_email.text.haml4
-rw-r--r--app/views/notify/resolved_all_discussions_email.html.haml3
-rw-r--r--app/views/projects/milestones/_form.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/shared/milestones/_description.html.haml5
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml4
-rw-r--r--app/views/shared/milestones/_milestone.html.haml3
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml4
-rw-r--r--changelogs/unreleased/31099-MR-API-allow-NOT-params.yml5
-rw-r--r--changelogs/unreleased/add-link-to-mrs-references.yml5
-rw-r--r--changelogs/unreleased/tr-alert-issue-link.yml5
-rw-r--r--doc/administration/monitoring/prometheus/index.md96
-rw-r--r--doc/api/geo_nodes.md10
-rw-r--r--doc/api/issues.md2
-rw-r--r--doc/api/merge_requests.md1
-rw-r--r--doc/ci/caching/index.md56
-rw-r--r--doc/ci/docker/using_docker_images.md74
-rw-r--r--doc/ci/environments/index.md12
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md16
-rw-r--r--doc/ci/examples/deployment/README.md26
-rw-r--r--doc/ci/examples/deployment/composer-npm-deploy.md2
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md196
-rw-r--r--doc/ci/examples/php.md36
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md32
-rw-r--r--doc/ci/jenkins/index.md4
-rw-r--r--doc/ci/merge_request_pipelines/index.md6
-rw-r--r--doc/ci/migration/circleci.md4
-rw-r--r--doc/ci/pipelines/index.md6
-rw-r--r--doc/ci/pipelines/job_artifacts.md6
-rw-r--r--doc/ci/triggers/README.md28
-rw-r--r--doc/ci/yaml/README.md22
-rw-r--r--doc/development/documentation/site_architecture/index.md6
-rw-r--r--doc/development/ee_features.md76
-rw-r--r--doc/development/prometheus_metrics.md20
-rw-r--r--doc/integration/elasticsearch.md4
-rw-r--r--doc/topics/autodevops/customize.md2
-rw-r--r--doc/topics/autodevops/stages.md30
-rw-r--r--doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.pngbin79103 -> 70913 bytes
-rw-r--r--doc/user/clusters/crossplane.md24
-rw-r--r--doc/user/clusters/management_project.md2
-rw-r--r--doc/user/gitlab_com/index.md14
-rw-r--r--doc/user/group/clusters/index.md4
-rw-r--r--doc/user/project/clusters/add_remove_clusters.md6
-rw-r--r--doc/user/project/clusters/index.md4
-rw-r--r--doc/user/project/clusters/serverless/aws.md12
-rw-r--r--doc/user/project/clusters/serverless/index.md36
-rw-r--r--doc/user/project/integrations/prometheus.md101
-rw-r--r--doc/user/project/operations/alert_management.md1
-rw-r--r--doc/user/project/operations/img/alert_list_v13_1.pngbin24192 -> 38265 bytes
-rw-r--r--doc/user/project/pages/getting_started/pages_from_scratch.md86
-rw-r--r--doc/user/project/pages/introduction.md18
-rw-r--r--doc/user/project/requirements/index.md6
-rw-r--r--lib/api/helpers/merge_requests_helpers.rb40
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content/parameter.rb3
-rw-r--r--lib/gitlab/danger/sidekiq_queues.rb2
-rw-r--r--locale/gitlab.pot35
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/project/milestone/index.rb18
-rw-r--r--qa/qa/page/project/milestone/new.rb27
-rw-r--r--qa/qa/page/project/milestone/show.rb35
-rw-r--r--qa/qa/page/project/sub_menus/issues.rb8
-rw-r--r--qa/qa/resource/project_milestone.rb20
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb41
-rw-r--r--qa/qa/support/dates.rb5
-rw-r--r--spec/features/projects/navbar_spec.rb11
-rw-r--r--spec/finders/merge_requests_finder_spec.rb39
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_spec.js32
-rw-r--r--spec/frontend/alert_management/mocks/alerts.json1
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js6
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js2
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js4
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js30
-rw-r--r--spec/frontend/monitoring/mock_data.js349
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js39
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js16
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js26
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js88
-rw-r--r--spec/frontend/monitoring/store/variable_mapping_spec.js104
-rw-r--r--spec/frontend/monitoring/store_utils.js6
-rw-r--r--spec/frontend/monitoring/utils_spec.js39
-rw-r--r--spec/helpers/notify_helper_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb4
-rw-r--r--spec/lib/gitlab/danger/sidekiq_queues_spec.rb16
-rw-r--r--spec/mailers/notify_spec.rb5
-rw-r--r--spec/requests/api/merge_requests_spec.rb67
-rw-r--r--spec/services/ci/create_pipeline_service/parameter_content_spec.rb35
-rw-r--r--spec/services/event_create_service_spec.rb12
-rw-r--r--spec/services/merge_requests/approval_service_spec.rb60
-rw-r--r--spec/services/system_note_service_spec.rb10
-rw-r--r--spec/services/system_notes/merge_requests_service_spec.rb14
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb10
126 files changed, 1736 insertions, 1118 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
index 59c74d8cd27..44602882be2 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_list.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue
@@ -85,6 +85,13 @@ export default {
sortable: true,
},
{
+ key: 'issue',
+ label: s__('AlertManagement|Issue'),
+ thClass: 'gl-w-12 gl-pointer-events-none',
+ tdClass,
+ sortable: false,
+ },
+ {
key: 'assignees',
label: s__('AlertManagement|Assignees'),
thClass: 'gl-w-eighth gl-pointer-events-none',
@@ -278,6 +285,9 @@ export default {
? assignees.nodes[0]?.username
: s__('AlertManagement|Unassigned');
},
+ getIssueLink(item) {
+ return joinPaths('/', this.projectPath, '-', 'issues', item.issueIid);
+ },
handlePageChange(page) {
const { startCursor, endCursor } = this.alerts.pageInfo;
@@ -402,6 +412,13 @@ export default {
<div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div>
</template>
+ <template #cell(issue)="{ item }">
+ <gl-link v-if="item.issueIid" data-testid="issueField" :href="getIssueLink(item)">
+ #{{ item.issueIid }}
+ </gl-link>
+ <div v-else data-testid="issueField">{{ s__('AlertManagement|None') }}</div>
+ </template>
+
<template #cell(assignees)="{ item }">
<div class="gl-max-w-full text-truncate" data-testid="assigneesField">
{{ getAssignees(item.assignees) }}
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index eb12617a66e..e9d66954622 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -5,10 +5,11 @@ import {
GlLabel,
GlTooltip,
GlIcon,
+ GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits';
-import { s__, __, sprintf } from '~/locale';
+import { n__, s__ } from '~/locale';
import AccessorUtilities from '../../lib/utils/accessor';
import BoardDelete from './board_delete';
import IssueCount from './issue_count.vue';
@@ -25,6 +26,7 @@ export default {
GlLabel,
GlTooltip,
GlIcon,
+ GlSprintf,
IssueCount,
},
directives: {
@@ -82,10 +84,20 @@ export default {
this.listType !== ListType.promotion
);
},
- issuesTooltip() {
+ showMilestoneListDetails() {
+ return (
+ this.list.type === 'milestone' &&
+ this.list.milestone &&
+ (this.list.isExpanded || !this.isSwimlanesHeader)
+ );
+ },
+ showAssigneeListDetails() {
+ return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader);
+ },
+ issuesTooltipLabel() {
const { issuesSize } = this.list;
- return sprintf(__('%{issuesSize} issues'), { issuesSize });
+ return n__(`%d issue`, `%d issues`, issuesSize);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@@ -111,6 +123,9 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.listType}.${this.list.id}`;
},
+ collapsedTooltipTitle() {
+ return this.listTitle || this.listAssignee;
+ },
},
methods: {
showScopedLabels(label) {
@@ -147,7 +162,7 @@ export default {
'has-border': list.label && list.label.color,
'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded,
- 'board-inner gl-rounded-base gl-border-b-0': isSwimlanesHeader,
+ 'board-inner gl-rounded-base': isSwimlanesHeader,
}"
:style="{ borderTopColor: list.label && list.label.color ? list.label.color : null }"
class="board-header gl-relative"
@@ -157,7 +172,9 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
- 'gl-border-b-0': !list.isExpanded,
+ 'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
+ 'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
+ 'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
class="board-title gl-m-0 gl-display-flex js-board-handle"
>
@@ -167,21 +184,17 @@ export default {
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
- class="board-title-caret no-drag"
+ class="board-title-caret no-drag gl-cursor-pointer "
variant="link"
@click="toggleExpanded"
/>
<!-- The following is only true in EE and if it is a milestone -->
- <span
- v-if="list.type === 'milestone' && list.milestone"
- aria-hidden="true"
- class="gl-mr-2 milestone-icon"
- >
+ <span v-if="showMilestoneListDetails" aria-hidden="true" class="gl-mr-2 milestone-icon">
<gl-icon name="timer" />
</span>
<a
- v-if="list.type === 'assignee'"
+ v-if="showAssigneeListDetails"
:href="list.assignee.path"
class="user-avatar-link js-no-trigger"
>
@@ -195,7 +208,10 @@ export default {
width="20"
/>
</a>
- <div class="board-title-text">
+ <div
+ class="board-title-text"
+ :class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
+ >
<span
v-if="list.type !== 'label'"
v-gl-tooltip.hover
@@ -208,7 +224,7 @@ export default {
{{ list.title }}
</span>
<span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2">
- @{{ list.assignee.username }}
+ @{{ listAssignee }}
</span>
<gl-label
v-if="list.type === 'label'"
@@ -220,6 +236,33 @@ export default {
:title="list.label.title"
/>
</div>
+
+ <span
+ v-if="isSwimlanesHeader && !list.isExpanded"
+ ref="collapsedInfo"
+ aria-hidden="true"
+ class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-700"
+ >
+ <gl-icon name="information" />
+ </span>
+ <gl-tooltip v-if="isSwimlanesHeader && !list.isExpanded" :target="() => $refs.collapsedInfo">
+ <div class="gl-font-weight-bold gl-pb-2">{{ collapsedTooltipTitle }}</div>
+ <div v-if="list.maxIssueCount !== 0">
+ &#8226;
+ <gl-sprintf :message="__('%{issuesSize} with a limit of %{maxIssueCount}')">
+ <template #issuesSize>{{ issuesTooltipLabel }}</template>
+ <template #maxIssueCount>{{ list.maxIssueCount }}</template>
+ </gl-sprintf>
+ </div>
+ <div v-else>&#8226; {{ issuesTooltipLabel }}</div>
+ <div v-if="weightFeatureAvailable">
+ &#8226;
+ <gl-sprintf :message="__('%{totalWeight} total weight')">
+ <template #totalWeight>{{ list.totalWeight }}</template>
+ </gl-sprintf>
+ </div>
+ </gl-tooltip>
+
<board-delete
v-if="canAdminList && !list.preset && list.id"
:list="list"
@@ -229,7 +272,7 @@ export default {
v-gl-tooltip.hover.bottom
:class="{ 'gl-display-none': !list.isExpanded }"
:aria-label="__('Delete list')"
- class="board-delete no-drag gl-pr-0 gl-shadow-none gl-mr-3"
+ class="board-delete no-drag gl-pr-0 gl-shadow-none! gl-mr-3"
:title="__('Delete list')"
icon="remove"
size="small"
@@ -239,9 +282,10 @@ export default {
<div
v-if="showBoardListAndBoardInfo"
class="issue-count-badge gl-pr-0 no-drag text-secondary"
+ :class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
>
<span class="gl-display-inline-flex">
- <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltip" />
+ <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
<issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 3c52368acdd..a27c6aa0d1b 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -164,7 +164,7 @@ export default {
]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'getMetricStates']),
shouldShowVariablesSection() {
- return Object.keys(this.variables).length > 0;
+ return Boolean(this.variables.length);
},
shouldShowLinksSection() {
return Object.keys(this.links).length > 0;
diff --git a/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue b/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue
index d79b8284a65..4e48292c48d 100644
--- a/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue
+++ b/app/assets/javascripts/monitoring/components/variables/dropdown_field.vue
@@ -34,7 +34,7 @@ export default {
},
methods: {
onUpdate(value) {
- this.$emit('onUpdate', this.name, value);
+ this.$emit('input', value);
},
},
};
diff --git a/app/assets/javascripts/monitoring/components/variables/text_field.vue b/app/assets/javascripts/monitoring/components/variables/text_field.vue
index ce0d19760e2..a0418806e5f 100644
--- a/app/assets/javascripts/monitoring/components/variables/text_field.vue
+++ b/app/assets/javascripts/monitoring/components/variables/text_field.vue
@@ -22,7 +22,7 @@ export default {
},
methods: {
onUpdate(event) {
- this.$emit('onUpdate', this.name, event.target.value);
+ this.$emit('input', event.target.value);
},
},
};
diff --git a/app/assets/javascripts/monitoring/components/variables_section.vue b/app/assets/javascripts/monitoring/components/variables_section.vue
index 9d3159dfb6e..25d900b07ad 100644
--- a/app/assets/javascripts/monitoring/components/variables_section.vue
+++ b/app/assets/javascripts/monitoring/components/variables_section.vue
@@ -16,10 +16,9 @@ export default {
methods: {
...mapActions('monitoringDashboard', ['updateVariablesAndFetchData']),
refreshDashboard(variable, value) {
- if (this.variables[variable].value !== value) {
- const changedVariable = { key: variable, value };
+ if (variable.value !== value) {
+ this.updateVariablesAndFetchData({ name: variable.name, value });
// update the Vuex store
- this.updateVariablesAndFetchData(changedVariable);
// the below calls can ideally be moved out of the
// component and into the actions and let the
// mutation respond directly.
@@ -39,15 +38,15 @@ export default {
</script>
<template>
<div ref="variablesSection" class="d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 variables-section">
- <div v-for="(variable, key) in variables" :key="key" class="mb-1 pr-2 d-flex d-sm-block">
+ <div v-for="variable in variables" :key="variable.name" class="mb-1 pr-2 d-flex d-sm-block">
<component
:is="variableField(variable.type)"
class="mb-0 flex-grow-1"
:label="variable.label"
:value="variable.value"
- :name="key"
+ :name="variable.name"
:options="variable.options"
- @onUpdate="refreshDashboard"
+ @input="refreshDashboard(variable, $event)"
/>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index aaa7865f30a..2ed114f7868 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -77,10 +77,6 @@ export const setTimeRange = ({ commit }, timeRange) => {
commit(types.SET_TIME_RANGE, timeRange);
};
-export const setVariables = ({ commit }, variables) => {
- commit(types.SET_VARIABLES, variables);
-};
-
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
@@ -235,7 +231,7 @@ export const fetchPrometheusMetric = (
queryParams.step = metric.step;
}
- if (Object.keys(state.variables).length > 0) {
+ if (state.variables.length > 0) {
queryParams = {
...queryParams,
...getters.getCustomVariablesParams,
@@ -480,7 +476,7 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
const { start_time, end_time } = defaultQueryParams;
const optionsRequests = [];
- Object.entries(state.variables).forEach(([key, variable]) => {
+ state.variables.forEach(variable => {
if (variable.type === VARIABLE_TYPES.metric_label_values) {
const { prometheusEndpointPath, label } = variable.options;
@@ -496,7 +492,7 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
.catch(() => {
createFlash(
sprintf(s__('Metrics|There was an error getting options for variable "%{name}".'), {
- name: key,
+ name: variable.name,
}),
);
});
diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js
index 3003fda634c..3aa711a0509 100644
--- a/app/assets/javascripts/monitoring/stores/getters.js
+++ b/app/assets/javascripts/monitoring/stores/getters.js
@@ -133,8 +133,8 @@ export const linksWithMetadata = state => {
};
/**
- * Maps an variables object to an array along with stripping
- * the variable prefix.
+ * Maps a variables array to an object for replacement in
+ * prometheus queries.
*
* This method outputs an object in the below format
*
@@ -147,14 +147,17 @@ export const linksWithMetadata = state => {
* user-defined variables coming through the URL and differentiate
* from other variables used for Prometheus API endpoint.
*
- * @param {Object} variables - Custom variables provided by the user
- * @returns {Array} The custom variables array to be send to the API
+ * @param {Object} state - State containing variables provided by the user
+ * @returns {Array} The custom variables object to be send to the API
* in the format of {variables[key1]=value1, variables[key2]=value2}
*/
export const getCustomVariablesParams = state =>
- Object.keys(state.variables).reduce((acc, variable) => {
- acc[addPrefixToCustomVariableParams(variable)] = state.variables[variable]?.value;
+ state.variables.reduce((acc, variable) => {
+ const { name, value } = variable;
+ if (value !== null) {
+ acc[addPrefixToCustomVariableParams(name)] = value;
+ }
return acc;
}, {});
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index d43065749c1..72b48c251ae 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -2,7 +2,6 @@
export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
-export const SET_VARIABLES = 'SET_VARIABLES';
export const UPDATE_VARIABLE_VALUE = 'UPDATE_VARIABLE_VALUE';
export const UPDATE_VARIABLE_METRIC_LABEL_VALUES = 'UPDATE_VARIABLE_METRIC_LABEL_VALUES';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 53c8029e46b..2de1d40e258 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -203,14 +203,13 @@ export default {
state.expandedPanel.group = group;
state.expandedPanel.panel = panel;
},
- [types.SET_VARIABLES](state, variables) {
- state.variables = variables;
- },
- [types.UPDATE_VARIABLE_VALUE](state, { key, value }) {
- Object.assign(state.variables[key], {
- ...state.variables[key],
- value,
- });
+ [types.UPDATE_VARIABLE_VALUE](state, { name, value }) {
+ const variable = state.variables.find(v => v.name === name);
+ if (variable) {
+ Object.assign(variable, {
+ value,
+ });
+ }
},
[types.UPDATE_VARIABLE_METRIC_LABEL_VALUES](state, { variable, label, data = [] }) {
const values = optionsFromSeriesData({ label, data });
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index ebe5e24b8db..6568555b9b0 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -47,7 +47,7 @@ export default () => ({
* User-defined custom variables are passed
* via the dashboard yml file.
*/
- variables: {},
+ variables: [],
/**
* User-defined custom links are passed
* via the dashboard yml file.
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 59b345d74ff..a09d792c058 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -289,7 +289,7 @@ export const mapToDashboardViewModel = ({
}) => {
return {
dashboard,
- variables: mergeURLVariables(parseTemplatingVariables(templating)),
+ variables: mergeURLVariables(parseTemplatingVariables(templating.variables)),
links: links.map(mapLinksToViewModel),
panelGroups: panel_groups.map(mapToPanelGroupViewModel),
};
@@ -453,10 +453,10 @@ export const normalizeQueryResponseData = data => {
*
* This is currently only used by getters/getCustomVariablesParams
*
- * @param {String} key Variable key that needs to be prefixed
+ * @param {String} name Variable key that needs to be prefixed
* @returns {String}
*/
-export const addPrefixToCustomVariableParams = key => `variables[${key}]`;
+export const addPrefixToCustomVariableParams = name => `variables[${name}]`;
/**
* Normalize custom dashboard paths. This method helps support
diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js
index 0b268402992..9245ffdb3b9 100644
--- a/app/assets/javascripts/monitoring/stores/variable_mapping.js
+++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js
@@ -46,7 +46,7 @@ const textAdvancedVariableParser = advTextVar => ({
* @param {Object} custom variable option
* @returns {Object} normalized custom variable options
*/
-const normalizeVariableValues = ({ default: defaultOpt = false, text, value }) => ({
+const normalizeVariableValues = ({ default: defaultOpt = false, text, value = null }) => ({
default: defaultOpt,
text: text || value,
value,
@@ -68,10 +68,10 @@ const customAdvancedVariableParser = advVariable => {
return {
type: VARIABLE_TYPES.custom,
label: advVariable.label,
- value: defaultValue?.value,
options: {
values,
},
+ value: defaultValue?.value || null,
};
};
@@ -100,27 +100,24 @@ const customSimpleVariableParser = simpleVar => {
const values = (simpleVar || []).map(parseSimpleCustomValues);
return {
type: VARIABLE_TYPES.custom,
- value: values[0].value,
label: null,
+ value: values[0].value || null,
options: {
values: values.map(normalizeVariableValues),
},
};
};
-const metricLabelValuesVariableParser = variable => {
- const { label, options = {} } = variable;
- return {
- type: VARIABLE_TYPES.metric_label_values,
- value: null,
- label,
- options: {
- prometheusEndpointPath: options.prometheus_endpoint_path || '',
- label: options.label || null,
- values: [], // values are initially empty
- },
- };
-};
+const metricLabelValuesVariableParser = ({ label, options = {} }) => ({
+ type: VARIABLE_TYPES.metric_label_values,
+ label,
+ value: null,
+ options: {
+ prometheusEndpointPath: options.prometheus_endpoint_path || '',
+ label: options.label || null,
+ values: [], // values are initially empty
+ },
+});
/**
* Utility method to determine if a custom variable is
@@ -161,29 +158,26 @@ const getVariableParser = variable => {
* for the user to edit. The values from input elements are relayed to
* backend and eventually Prometheus API.
*
- * This method currently is not used anywhere. Once the issue
- * https://gitlab.com/gitlab-org/gitlab/-/issues/214536 is completed,
- * this method will have been used by the monitoring dashboard.
- *
- * @param {Object} templating templating variables from the dashboard yml file
- * @returns {Object} a map of processed templating variables
+ * @param {Object} templating variables from the dashboard yml file
+ * @returns {array} An array of variables to display as inputs
*/
-export const parseTemplatingVariables = ({ variables = {} } = {}) =>
- Object.entries(variables).reduce((acc, [key, variable]) => {
+export const parseTemplatingVariables = (ymlVariables = {}) =>
+ Object.entries(ymlVariables).reduce((acc, [name, ymlVariable]) => {
// get the parser
- const parser = getVariableParser(variable);
+ const parser = getVariableParser(ymlVariable);
// parse the variable
- const parsedVar = parser(variable);
+ const variable = parser(ymlVariable);
// for simple custom variable label is null and it should be
// replace with key instead
- if (parsedVar) {
- acc[key] = {
- ...parsedVar,
- label: parsedVar.label || key,
- };
+ if (variable) {
+ acc.push({
+ ...variable,
+ name,
+ label: variable.label || name,
+ });
}
return acc;
- }, {});
+ }, []);
/**
* Custom variables are defined in the dashboard yml file
@@ -201,23 +195,18 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) =>
* This method can be improved further. See the below issue
* https://gitlab.com/gitlab-org/gitlab/-/issues/217713
*
- * @param {Object} varsFromYML template variables from yml file
+ * @param {array} parsedYmlVariables - template variables from yml file
* @returns {Object}
*/
-export const mergeURLVariables = (varsFromYML = {}) => {
+export const mergeURLVariables = (parsedYmlVariables = []) => {
const varsFromURL = templatingVariablesFromUrl();
- const variables = {};
- Object.keys(varsFromYML).forEach(key => {
- if (Object.prototype.hasOwnProperty.call(varsFromURL, key)) {
- variables[key] = {
- ...varsFromYML[key],
- value: varsFromURL[key],
- };
- } else {
- variables[key] = varsFromYML[key];
+ parsedYmlVariables.forEach(variable => {
+ const { name } = variable;
+ if (Object.prototype.hasOwnProperty.call(varsFromURL, name)) {
+ Object.assign(variable, { value: varsFromURL[name] });
}
});
- return variables;
+ return parsedYmlVariables;
};
/**
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 561d1da9710..6206611adf7 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -201,8 +201,10 @@ export const removePrefixFromLabel = label =>
* @returns {Object}
*/
export const convertVariablesForURL = variables =>
- Object.keys(variables || {}).reduce((acc, key) => {
- acc[addPrefixToLabel(key)] = variables[key]?.value;
+ variables.reduce((acc, { name, value }) => {
+ if (value !== null) {
+ acc[addPrefixToLabel(name)] = value;
+ }
return acc;
}, {});
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 3e680c59910..c1f5b3a3c7b 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -82,7 +82,6 @@
}
.board-title-caret {
- cursor: pointer;
border-radius: $border-radius-default;
line-height: $gl-spacing-scale-5;
height: $gl-spacing-scale-5;
@@ -109,7 +108,6 @@
.board-title {
flex-direction: column;
height: 100%;
- padding: $gl-padding-8 0;
}
.board-title-caret {
@@ -203,8 +201,7 @@
flex-grow: 1;
}
-.board-delete {
- color: $gray-darkest;
+.board-delete.gl-button {
background-color: transparent;
outline: 0;
@@ -581,5 +578,29 @@
.board-epics-swimlanes {
overflow-x: auto;
- min-height: 600px;
+ min-height: calc(100vh - #{$issue-board-list-difference-xs});
+
+ @include media-breakpoint-only(sm) {
+ min-height: calc(100vh - #{$issue-board-list-difference-sm});
+ }
+
+ @include media-breakpoint-up(md) {
+ min-height: calc(100vh - #{$issue-board-list-difference-md});
+ }
+
+ .with-performance-bar & {
+ min-height: calc(100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height});
+
+ @include media-breakpoint-only(sm) {
+ min-height: calc(100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height});
+ }
+
+ @include media-breakpoint-up(md) {
+ min-height: calc(100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height});
+ }
+ }
+}
+
+.board-header-collapsed-info-icon:hover {
+ color: $gray-900;
}
diff --git a/app/helpers/notify_helper.rb b/app/helpers/notify_helper.rb
new file mode 100644
index 00000000000..fb68029928c
--- /dev/null
+++ b/app/helpers/notify_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module NotifyHelper
+ def merge_request_reference_link(entity, *args)
+ link_to(entity.to_reference, merge_request_url(entity, *args))
+ end
+
+ def issue_reference_link(entity, *args)
+ link_to(entity.to_reference, issue_url(entity, *args))
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8df3b8356e7..2470312d8ac 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -750,6 +750,10 @@ module ProjectsHelper
::Feature.enabled?(:resource_access_token, project)
end
+
+ def render_service_desk_menu?
+ false
+ end
end
ProjectsHelper.prepend_if_ee('EE::ProjectsHelper')
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 4e14bb4e92c..3f960af24d5 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -18,7 +18,7 @@ class SystemNoteMetadata < ApplicationRecord
designs_added designs_modified designs_removed designs_discussion_added
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked outdated
- tag due_date pinned_embed cherry_pick health_status
+ tag due_date pinned_embed cherry_pick health_status approved
].freeze
validates :note, presence: true
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index fdf777f2bbe..7f71906bc89 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -138,6 +138,10 @@ class EventCreateService
event
end
+ def approve_mr(merge_request, current_user)
+ create_record_event(merge_request, current_user, :approved)
+ end
+
private
def existing_wiki_event(wiki_page_meta, action)
diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb
new file mode 100644
index 00000000000..0fe165303f2
--- /dev/null
+++ b/app/services/merge_requests/approval_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class ApprovalService < MergeRequests::BaseService
+ def execute(merge_request)
+ approval = merge_request.approvals.new(user: current_user)
+
+ return unless save_approval(approval)
+
+ reset_approvals_cache(merge_request)
+ create_event(merge_request)
+ create_approval_note(merge_request)
+ mark_pending_todos_as_done(merge_request)
+ execute_approval_hooks(merge_request, current_user)
+ end
+
+ private
+
+ def reset_approvals_cache(merge_request)
+ merge_request.approvals.reset
+ end
+
+ def execute_approval_hooks(merge_request, current_user)
+ # Only one approval is required for a merge request to be approved
+ execute_hooks(merge_request, 'approved')
+ end
+
+ def save_approval(approval)
+ Approval.safe_ensure_unique do
+ approval.save
+ end
+ end
+
+ def create_approval_note(merge_request)
+ SystemNoteService.approve_mr(merge_request, current_user)
+ end
+
+ def mark_pending_todos_as_done(merge_request)
+ todo_service.resolve_todos_for_target(merge_request, current_user)
+ end
+
+ def create_event(merge_request)
+ event_service.approve_mr(merge_request, current_user)
+ end
+ end
+end
+
+MergeRequests::ApprovalService.prepend_if_ee('EE::MergeRequests::ApprovalService')
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 6bf04c55415..41e9d624ec6 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -273,6 +273,26 @@ module SystemNoteService
::SystemNotes::DesignManagementService.new(noteable: design.issue, project: design.project, author: discussion_note.author).design_discussion_added(discussion_note)
end
+
+ # Called when the merge request is approved by user
+ #
+ # noteable - Noteable object
+ # user - User performing approve
+ #
+ # Example Note text:
+ #
+ # "approved this merge request"
+ #
+ # Returns the created Note object
+ def approve_mr(noteable, user)
+ merge_requests_service(noteable, noteable.project, user).approve_mr
+ end
+
+ private
+
+ def merge_requests_service(noteable, project, author)
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author)
+ end
end
SystemNoteService.prepend_if_ee('EE::SystemNoteService')
diff --git a/app/services/system_notes/merge_requests_service.rb b/app/services/system_notes/merge_requests_service.rb
index baf26245eb9..0c7016c0d23 100644
--- a/app/services/system_notes/merge_requests_service.rb
+++ b/app/services/system_notes/merge_requests_service.rb
@@ -150,6 +150,19 @@ module SystemNotes
create_note(summary)
end
+
+ # Called when the merge request is approved by user
+ #
+ # Example Note text:
+ #
+ # "approved this merge request"
+ #
+ # Returns the created Note object
+ def approve_mr
+ body = "approved this merge request"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'approved'))
+ end
end
end
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 2aa753e0d55..6caa0e59e8f 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,3 @@
%p
- Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
+ Merge Request #{merge_request_reference_link(@merge_request)}
+ was closed by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 6e84f9fb355..8546da2d7f0 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index ffb416abf72..a15c5a752d4 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,3 @@
%p
- Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
+ Merge Request #{merge_request_reference_link(@merge_request)}
+ was #{@mr_status} by #{sanitize_name(@updated_by.name)}
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index e3b24bbd405..3d7115856d4 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merge_request_unmergeable_email.html.haml b/app/views/notify/merge_request_unmergeable_email.html.haml
index 7ec0c1ef390..ee459a26551 100644
--- a/app/views/notify/merge_request_unmergeable_email.html.haml
+++ b/app/views/notify/merge_request_unmergeable_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} can no longer be merged due to conflict.
+ Merge Request #{merge_request_reference_link(@merge_request)} can no longer be merged due to conflict.
diff --git a/app/views/notify/merge_request_unmergeable_email.text.haml b/app/views/notify/merge_request_unmergeable_email.text.haml
index e9708a297d7..412a0887186 100644
--- a/app/views/notify/merge_request_unmergeable_email.text.haml
+++ b/app/views/notify/merge_request_unmergeable_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} can no longer be merged due to conflict.
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index 341aa6f8103..c84c0d1d14b 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} was merged
+ Merge Request #{merge_request_reference_link(@merge_request)} was merged
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 52e110a98f6..7f0a50e9248 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
%p.details
- #{link_to @issue.author_name, user_url(@issue.author)} created an issue #{link_to @issue.to_reference(full: false), issue_url(@issue)}:
+ #{link_to @issue.author_name, user_url(@issue.author)} created an issue #{issue_reference_link(@issue)}:
- if @issue.assignees.any?
%p
diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml
index b061f9c106e..ddcf287e501 100644
--- a/app/views/notify/new_mention_in_merge_request_email.html.haml
+++ b/app/views/notify/new_mention_in_merge_request_email.html.haml
@@ -1,4 +1,4 @@
%p
- You have been mentioned in Merge Request #{@merge_request.to_reference}
+ You have been mentioned in Merge Request #{merge_request_reference_link(@merge_request)}
= render template: 'notify/new_merge_request_email'
diff --git a/app/views/notify/push_to_merge_request_email.html.haml b/app/views/notify/push_to_merge_request_email.html.haml
index 97258833cfc..3e9f9b442e0 100644
--- a/app/views/notify/push_to_merge_request_email.html.haml
+++ b/app/views/notify/push_to_merge_request_email.html.haml
@@ -1,7 +1,7 @@
%h3
= sanitize_name(@updated_by_user.name)
pushed new commits to merge request
- = link_to(@merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request))
+ = merge_request_reference_link(@merge_request)
- if @existing_commits.any?
- count = @existing_commits.size
diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml
index 10c8e158846..55cbd62b7e8 100644
--- a/app/views/notify/push_to_merge_request_email.text.haml
+++ b/app/views/notify/push_to_merge_request_email.text.haml
@@ -1,6 +1,4 @@
-#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference}
-\
-#{url_for(project_merge_request_url(@merge_request.target_project, @merge_request))}
+#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{merge_request_reference_link(@merge_request)}
\
- if @existing_commits.any?
- count = @existing_commits.size
diff --git a/app/views/notify/resolved_all_discussions_email.html.haml b/app/views/notify/resolved_all_discussions_email.html.haml
index 502b8f21e35..0b3c56c9bd1 100644
--- a/app/views/notify/resolved_all_discussions_email.html.haml
+++ b/app/views/notify/resolved_all_discussions_email.html.haml
@@ -1,2 +1,3 @@
%p
- All discussions on Merge Request #{@merge_request.to_reference} were resolved by #{sanitize_name(@resolved_by.name)}
+ All discussions on Merge Request #{merge_request_reference_link(@merge_request)}
+ were resolved by #{sanitize_name(@resolved_by.name)}
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index a3083fa2081..eeff91f631c 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -7,13 +7,13 @@
.col-form-label.col-sm-2
= f.label :title, _('Title')
.col-sm-10
- = f.text_field :title, maxlength: 255, class: 'qa-milestone-title form-control', required: true, autofocus: true
+ = f.text_field :title, maxlength: 255, class: 'form-control', data: { qa_selector: 'milestone_title_field' }, required: true, autofocus: true
.form-group.row.milestone-description
.col-form-label.col-sm-2
= f.label :description, _('Description')
.col-sm-10
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
- = render 'shared/zen', f: f, attr: :description, classes: 'qa-milestone-description note-textarea', placeholder: _('Write milestone description...')
+ = render 'shared/zen', f: f, attr: :description, classes: 'note-textarea', qa_selector: 'milestone_description_field', placeholder: _('Write milestone description...')
= render 'shared/notes/hints'
.clearfix
.error-alert
@@ -21,7 +21,7 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'btn-success btn qa-milestone-create-button'
+ = f.submit _('Create milestone'), class: 'btn-success btn', data: { qa_selector: 'create_milestone_button' }
= link_to _('Cancel'), project_milestones_path(@project), class: 'btn btn-cancel'
- else
= f.submit _('Save changes'), class: 'btn-success btn'
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index c89566dac90..2bab2a0fb03 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -7,7 +7,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do
+ = link_to new_project_milestone_path(@project), class: 'btn btn-success', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
.milestones
diff --git a/app/views/shared/milestones/_description.html.haml b/app/views/shared/milestones/_description.html.haml
index 5ff110bf94b..76d6c765ed6 100644
--- a/app/views/shared/milestones/_description.html.haml
+++ b/app/views/shared/milestones/_description.html.haml
@@ -1,8 +1,9 @@
.detail-page-description.milestone-detail
- %h2.title
+ %h2{ data: { qa_selector: "milestone_title_content" } }
+ .title
= markdown_field(milestone, :title)
- if milestone.try(:description).present?
- %div
+ %div{ data: { qa_selector: "milestone_description_content" } }
.description.md
= markdown_field(milestone, :description)
diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml
index 6dbc460d9bf..46e96cf8edf 100644
--- a/app/views/shared/milestones/_form_dates.html.haml
+++ b/app/views/shared/milestones/_form_dates.html.haml
@@ -3,11 +3,11 @@
.col-form-label.col-sm-2
= f.label :start_date, _('Start Date')
.col-sm-10
- = f.text_field :start_date, class: "datepicker form-control", placeholder: _('Select start date'), autocomplete: 'off'
+ = f.text_field :start_date, class: "datepicker form-control", data: { qa_selector: "start_date_field" }, placeholder: _('Select start date'), autocomplete: 'off'
%a.inline.float-right.prepend-top-5.js-clear-start-date{ href: "#" }= _('Clear start date')
.form-group.row
.col-form-label.col-sm-2
= f.label :due_date, _('Due Date')
.col-sm-10
- = f.text_field :due_date, class: "datepicker form-control", placeholder: _('Select due date'), autocomplete: 'off'
+ = f.text_field :due_date, class: "datepicker form-control", data: { qa_selector: "due_date_field" }, placeholder: _('Select due date'), autocomplete: 'off'
%a.inline.float-right.prepend-top-5.js-clear-due-date{ href: "#" }= _('Clear due date')
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 6460d1a46e8..ae5bf9572bd 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -6,7 +6,8 @@
.row
.col-sm-6
.gl-mb-2
- %strong= link_to truncate(milestone.title, length: 100), milestone_path(milestone)
+ %strong{ data: { qa_selector: "milestone_link", qa_milestone_title: milestone.title } }
+ = link_to truncate(milestone.title, length: 100), milestone_path(milestone)
- if @group
= " - #{milestone_type}"
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 160f6487439..7fd657ec2dd 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -24,7 +24,7 @@
- if @project && can?(current_user, :admin_milestone, @project)
= link_to s_('MilestoneSidebar|Edit'), edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value
- %span.value-content
+ %span.value-content{ data: { qa_selector: 'start_date_content' } }
- if milestone.start_date
%span.bold= milestone.start_date.to_s(:medium)
- else
@@ -60,7 +60,7 @@
- if @project && can?(current_user, :admin_milestone, @project)
= link_to s_('MilestoneSidebar|Edit'), edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
- %span.value-content
+ %span.value-content{ data: { qa_selector: 'due_date_content' } }
- if milestone.due_date
%span.bold= milestone.due_date.to_s(:medium)
- else
diff --git a/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml b/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml
new file mode 100644
index 00000000000..2e7f9994cbe
--- /dev/null
+++ b/changelogs/unreleased/31099-MR-API-allow-NOT-params.yml
@@ -0,0 +1,5 @@
+---
+title: Add 'not' params to MergeRequests API endpoint
+merge_request: 35391
+author:
+type: added
diff --git a/changelogs/unreleased/add-link-to-mrs-references.yml b/changelogs/unreleased/add-link-to-mrs-references.yml
new file mode 100644
index 00000000000..13686060b14
--- /dev/null
+++ b/changelogs/unreleased/add-link-to-mrs-references.yml
@@ -0,0 +1,5 @@
+---
+title: Render Merge request reference as link in email templates
+merge_request: 33316
+author:
+type: changed
diff --git a/changelogs/unreleased/tr-alert-issue-link.yml b/changelogs/unreleased/tr-alert-issue-link.yml
new file mode 100644
index 00000000000..3ec3a3a5ce1
--- /dev/null
+++ b/changelogs/unreleased/tr-alert-issue-link.yml
@@ -0,0 +1,5 @@
+---
+title: Add issue column to alert list
+merge_request: 35291
+author:
+type: added
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 1e233b890a2..2a00f7dc4e8 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -165,54 +165,54 @@ To use an external Prometheus server:
```yaml
scrape_configs:
- - job_name: nginx
- static_configs:
- - targets:
- - 1.1.1.1:8060
- - job_name: redis
- static_configs:
- - targets:
- - 1.1.1.1:9121
- - job_name: postgres
- static_configs:
- - targets:
- - 1.1.1.1:9187
- - job_name: node
- static_configs:
- - targets:
- - 1.1.1.1:9100
- - job_name: gitlab-workhorse
- static_configs:
- - targets:
- - 1.1.1.1:9229
- - job_name: gitlab-rails
- metrics_path: "/-/metrics"
- static_configs:
- - targets:
- - 1.1.1.1:8080
- - job_name: gitlab-sidekiq
- static_configs:
- - targets:
- - 1.1.1.1:8082
- - job_name: gitlab_exporter_database
- metrics_path: "/database"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitlab_exporter_sidekiq
- metrics_path: "/sidekiq"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitlab_exporter_process
- metrics_path: "/process"
- static_configs:
- - targets:
- - 1.1.1.1:9168
- - job_name: gitaly
- static_configs:
- - targets:
- - 1.1.1.1:9236
+ - job_name: nginx
+ static_configs:
+ - targets:
+ - 1.1.1.1:8060
+ - job_name: redis
+ static_configs:
+ - targets:
+ - 1.1.1.1:9121
+ - job_name: postgres
+ static_configs:
+ - targets:
+ - 1.1.1.1:9187
+ - job_name: node
+ static_configs:
+ - targets:
+ - 1.1.1.1:9100
+ - job_name: gitlab-workhorse
+ static_configs:
+ - targets:
+ - 1.1.1.1:9229
+ - job_name: gitlab-rails
+ metrics_path: "/-/metrics"
+ static_configs:
+ - targets:
+ - 1.1.1.1:8080
+ - job_name: gitlab-sidekiq
+ static_configs:
+ - targets:
+ - 1.1.1.1:8082
+ - job_name: gitlab_exporter_database
+ metrics_path: "/database"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_exporter_sidekiq
+ metrics_path: "/sidekiq"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_exporter_process
+ metrics_path: "/process"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitaly
+ static_configs:
+ - targets:
+ - 1.1.1.1:9236
```
1. Reload the Prometheus server.
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index 12f785a3e3d..402170fba37 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -367,8 +367,9 @@ Example response:
"package_files_count": 10,
"package_files_checksummed_count": 10,
"package_files_checksum_failed_count": 0,
- "package_files_synced_count": 10,
- "package_files_failed_count": 5
+ "package_files_registry_count": 10,
+ "package_files_synced_count": 6,
+ "package_files_failed_count": 3
},
{
"geo_node_id": 2,
@@ -440,8 +441,9 @@ Example response:
"package_files_count": 10,
"package_files_checksummed_count": 10,
"package_files_checksum_failed_count": 0,
- "package_files_synced_count": 10,
- "package_files_failed_count": 5
+ "package_files_registry_count": 10,
+ "package_files_synced_count": 6,
+ "package_files_failed_count": 3
}
]
```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index b216be03ac3..6078c77e45a 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -70,7 +70,7 @@ GET /issues?confidential=true
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential` | boolean | no | Filter confidential or public issues. |
-| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` |
+| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
| `non_archived` | boolean | no | Return issues only from non-archived projects. If `false`, response will return issues from both archived and non-archived projects. Default is `true`. _(Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/197170))_ |
```shell
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 41c0428485f..5f28ff8d69e 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -64,6 +64,7 @@ Parameters:
| `search` | string | no | Search merge requests against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
| `wip` | string | no | Filter merge requests against their `wip` status. `yes` to return *only* WIP merge requests, `no` to return *non* WIP merge requests |
+| `not` | Hash | no | Return merge requests that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji` |
NOTE: **Note:**
[Starting in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31890),
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 392dd4fdeba..b6bd01ecf58 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -226,14 +226,14 @@ image: node:latest
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- - .npm/
+ - .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- - node ./specs/start.js ./specs/async.spec.js
+ - node ./specs/start.js ./specs/async.spec.js
```
### Caching PHP dependencies
@@ -253,16 +253,16 @@ image: php:7.2
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- - vendor/
+ - vendor/
before_script:
-# Install and run Composer
-- curl --show-error --silent https://getcomposer.org/installer | php
-- php composer.phar install
+ # Install and run Composer
+ - curl --show-error --silent https://getcomposer.org/installer | php
+ - php composer.phar install
test:
script:
- - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
+ - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
```
### Caching Python dependencies
@@ -301,9 +301,9 @@ before_script:
test:
script:
- - python setup.py test
- - pip install flake8
- - flake8 .
+ - python setup.py test
+ - pip install flake8
+ - flake8 .
```
### Caching Ruby dependencies
@@ -330,7 +330,7 @@ before_script:
rspec:
script:
- - rspec spec
+ - rspec spec
```
### Caching Go dependencies
@@ -354,7 +354,7 @@ test:
image: golang:1.13
extends: .go-cache
script:
- - go test ./... -v -short
+ - go test ./... -v -short
```
## Availability of the cache
@@ -391,28 +391,28 @@ stages:
```yaml
stages:
-- build
-- test
+ - build
+ - test
before_script:
-- echo "Hello"
+ - echo "Hello"
job A:
stage: build
script:
- - mkdir vendor/
- - echo "build" > vendor/hello.txt
+ - mkdir vendor/
+ - echo "build" > vendor/hello.txt
cache:
key: build-cache
paths:
- - vendor/
+ - vendor/
after_script:
- - echo "World"
+ - echo "World"
job B:
stage: test
script:
- - cat vendor/hello.txt
+ - cat vendor/hello.txt
cache:
key: build-cache
```
@@ -483,8 +483,8 @@ cache when the pipeline is run for a second time.
```yaml
stages:
-- build
-- test
+ - build
+ - test
job A:
stage: build
@@ -492,7 +492,7 @@ job A:
cache:
key: same-key
paths:
- - public/
+ - public/
job B:
stage: test
@@ -500,7 +500,7 @@ job B:
cache:
key: same-key
paths:
- - vendor/
+ - vendor/
```
1. `job A` runs.
@@ -520,8 +520,8 @@ will be different):
```yaml
stages:
-- build
-- test
+ - build
+ - test
job A:
stage: build
@@ -529,7 +529,7 @@ job A:
cache:
key: keyA
paths:
- - vendor/
+ - vendor/
job B:
stage: test
@@ -537,7 +537,7 @@ job B:
cache:
key: keyB
paths:
- - vendor/
+ - vendor/
```
In that case, even if the `key` is different (no fear of overwriting), you
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 2448bb536ab..692ad12eb59 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -149,14 +149,14 @@ the job will fail:
```yaml
job:
services:
- - php:7
- - node:latest
- - golang:1.10
+ - php:7
+ - node:latest
+ - golang:1.10
image: alpine:3.7
script:
- - php -v
- - node -v
- - go version
+ - php -v
+ - node -v
+ - go version
```
If you need to have `php`, `node` and `go` available for your script, you should
@@ -176,7 +176,7 @@ You can then use for example the [tutum/wordpress](https://hub.docker.com/r/tutu
```yaml
services:
-- tutum/wordpress:latest
+ - tutum/wordpress:latest
```
If you don't [specify a service alias](#available-settings-for-services),
@@ -219,7 +219,7 @@ default:
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
The image name must be in one of the following formats:
@@ -238,16 +238,16 @@ default:
test:2.6:
image: ruby:2.6
services:
- - postgres:11.7
+ - postgres:11.7
script:
- - bundle exec rake spec
+ - bundle exec rake spec
test:2.7:
image: ruby:2.7
services:
- - postgres:12.2
+ - postgres:12.2
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
Or you can pass some [extended configuration options](#extended-docker-configuration-options)
@@ -260,17 +260,17 @@ default:
entrypoint: ["/bin/bash"]
services:
- - name: my-postgres:11.7
- alias: db-postgres
- entrypoint: ["/usr/local/bin/db-postgres"]
- command: ["start"]
+ - name: my-postgres:11.7
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
before_script:
- - bundle install
+ - bundle install
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
## Passing environment variables to services
@@ -292,21 +292,21 @@ variables:
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --data-checksums"
services:
-- name: postgres:11.7
- alias: db
- entrypoint: ["docker-entrypoint.sh"]
- command: ["postgres"]
+ - name: postgres:11.7
+ alias: db
+ entrypoint: ["docker-entrypoint.sh"]
+ command: ["postgres"]
image:
name: ruby:2.6
entrypoint: ["/bin/bash"]
before_script:
-- bundle install
+ - bundle install
test:
script:
- - bundle exec rake spec
+ - bundle exec rake spec
```
## Extended Docker configuration options
@@ -330,8 +330,8 @@ For example, the following two definitions are equal:
image: "registry.example.com/my/image:latest"
services:
- - postgresql:9.4
- - redis:latest
+ - postgresql:9.4
+ - redis:latest
```
1. Using a map as an option to `image` and `services`. The use of `image:name` is
@@ -342,8 +342,8 @@ For example, the following two definitions are equal:
name: "registry.example.com/my/image:latest"
services:
- - name: postgresql:9.4
- - name: redis:latest
+ - name: postgresql:9.4
+ - name: redis:latest
```
### Available settings for `image`
@@ -378,8 +378,8 @@ would not work properly:
```yaml
services:
-- mysql:latest
-- mysql:latest
+ - mysql:latest
+ - mysql:latest
```
The Runner would start two containers using the `mysql:latest` image, but both
@@ -392,10 +392,10 @@ look like:
```yaml
services:
-- name: mysql:latest
- alias: mysql-1
-- name: mysql:latest
- alias: mysql-2
+ - name: mysql:latest
+ alias: mysql-1
+ - name: mysql:latest
+ alias: mysql-2
```
The Runner will still start two containers using the `mysql:latest` image,
@@ -427,7 +427,7 @@ CMD ["/usr/bin/super-sql", "run"]
# .gitlab-ci.yml
services:
-- my-super-sql:latest
+ - my-super-sql:latest
```
After the new extended Docker configuration options, you can now simply
@@ -437,8 +437,8 @@ set a `command` in `.gitlab-ci.yml`, like:
# .gitlab-ci.yml
services:
-- name: super/sql:latest
- command: ["/usr/bin/super-sql", "run"]
+ - name: super/sql:latest
+ command: ["/usr/bin/super-sql", "run"]
```
As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`](https://docs.docker.com/engine/reference/builder/#cmd).
diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md
index fa070ec68f6..501ebafba07 100644
--- a/doc/ci/environments/index.md
+++ b/doc/ci/environments/index.md
@@ -94,7 +94,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
```
We have defined three [stages](../yaml/README.md#stages):
@@ -259,7 +259,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
deploy_prod:
stage: deploy
@@ -270,7 +270,7 @@ deploy_prod:
url: https://example.com
when: manual
only:
- - master
+ - master
```
The `when: manual` action:
@@ -402,7 +402,7 @@ deploy:
kubernetes:
namespace: production
only:
- - master
+ - master
```
When deploying to a Kubernetes cluster using GitLab's Kubernetes integration,
@@ -483,7 +483,7 @@ deploy_staging:
name: staging
url: https://staging.example.com
only:
- - master
+ - master
deploy_prod:
stage: deploy
@@ -494,7 +494,7 @@ deploy_prod:
url: https://example.com
when: manual
only:
- - master
+ - master
```
A more realistic example would also include copying files to a location where a
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 87cee1820bc..e0d4f3f2402 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -61,10 +61,10 @@ content:
```yaml
---
applications:
-- name: gitlab-hello-world
- random-route: true
- memory: 1G
- path: target/demo-0.0.1-SNAPSHOT.jar
+ - name: gitlab-hello-world
+ random-route: true
+ memory: 1G
+ path: target/demo-0.0.1-SNAPSHOT.jar
```
## Configure GitLab CI/CD to deploy your application
@@ -96,11 +96,11 @@ build:
production:
stage: deploy
script:
- - curl --location "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar zx
- - ./cf login -u $CF_USERNAME -p $CF_PASSWORD -a api.run.pivotal.io
- - ./cf push
+ - curl --location "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar zx
+ - ./cf login -u $CF_USERNAME -p $CF_PASSWORD -a api.run.pivotal.io
+ - ./cf push
only:
- - master
+ - master
```
We've used the `java:8` [Docker
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index ec02fb6dd43..3192b365c83 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -45,8 +45,8 @@ All possible parameters can be found here: <https://github.com/travis-ci/dpl#her
staging:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
```
In the above example we use Dpl to deploy `my-app-staging` to Heroku server with API key stored in `HEROKU_STAGING_API_KEY` secure variable.
@@ -64,12 +64,12 @@ You will have to install it:
staging:
stage: deploy
script:
- - apt-get update -yq
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - apt-get update -yq
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
```
The first line `apt-get update -yq` updates the list of available packages,
@@ -89,18 +89,18 @@ The final `.gitlab-ci.yml` for that setup would look like this:
staging:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
production:
stage: deploy
script:
- - gem install dpl
- - dpl --provider=heroku --app=my-app-production --api-key=$HEROKU_PRODUCTION_API_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=my-app-production --api-key=$HEROKU_PRODUCTION_API_KEY
only:
- - tags
+ - tags
```
We created two deploy jobs that are executed on different events:
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index cea6f26181f..067d92c2275 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -150,7 +150,7 @@ before_script:
stage_deploy:
artifacts:
paths:
- - build/
+ - build/
only:
- dev
script:
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 51d9f169939..35a35d97a4b 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -274,19 +274,19 @@ just pack them up in the cache. Here is the full `build` job:
```yaml
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules
- artifacts:
- paths:
- - built
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules
+ artifacts:
+ paths:
+ - built
```
### Test your game with GitLab CI/CD
@@ -301,18 +301,18 @@ Following the YAML structure, the `test` job should look like this:
```yaml
test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
```
We have added unit tests for a `Weapon` class that shoots on a specified interval.
@@ -325,33 +325,33 @@ Our entire `.gitlab-ci.yml` file should now look like this:
image: node:10
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
test:
- stage: test
- script:
- - npm i gulp -g
- - npm i
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
```
### Run your CI/CD pipeline
@@ -445,18 +445,18 @@ trigger the `deploy` job of our pipeline. Put these together to get the followin
```yaml
deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
```
Be sure to update the region and S3 URL in that last script command to fit your setup.
@@ -466,46 +466,46 @@ Our final configuration file `.gitlab-ci.yml` looks like:
image: node:10
build:
- stage: build
- script:
- - npm i gulp -g
- - npm i
- - gulp
- - gulp build-test
- cache:
- policy: push
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
test:
- stage: test
- script:
- - npm i gulp -g
- - gulp run-test
- cache:
- policy: pull
- paths:
- - node_modules/
- artifacts:
- paths:
- - built/
+ stage: test
+ script:
+ - npm i gulp -g
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
deploy:
- stage: deploy
- variables:
- AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
- AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
- script:
- - apt-get update
- - apt-get install -y python3-dev python3-pip
- - easy_install3 -U pip
- - pip3 install --upgrade awscli
- - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
- only:
- - master
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
```
## Conclusion
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index e7768868c15..cc62e9316f2 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -76,7 +76,7 @@ environment, let's add it in `.gitlab-ci.yml`:
...
before_script:
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
...
```
@@ -88,7 +88,7 @@ Last step, run the actual tests using `phpunit`:
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
...
```
@@ -104,11 +104,11 @@ image: php:5.6
before_script:
# Install dependencies
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
### Test against different PHP versions in Docker builds
@@ -119,19 +119,19 @@ with a different Docker image version and the runner will do the rest:
```yaml
before_script:
# Install dependencies
-- bash ci/docker_install.sh > /dev/null
+ - bash ci/docker_install.sh > /dev/null
# We test PHP5.6
test:5.6:
image: php:5.6
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
# We test PHP7.0 (good luck with that)
test:7.0:
image: php:7.0
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
### Custom PHP configuration in Docker builds
@@ -142,7 +142,7 @@ add a `before_script` action:
```yaml
before_script:
-- cp my_php.ini /usr/local/etc/php/conf.d/test.ini
+ - cp my_php.ini /usr/local/etc/php/conf.d/test.ini
```
Of course, `my_php.ini` must be present in the root directory of your repository.
@@ -166,7 +166,7 @@ Next, add the following snippet to your `.gitlab-ci.yml`:
```yaml
test:app:
script:
- - phpunit --configuration phpunit_myapp.xml
+ - phpunit --configuration phpunit_myapp.xml
```
Finally, push to GitLab and let the tests begin!
@@ -217,11 +217,11 @@ you can use [atoum](https://github.com/atoum/atoum):
```yaml
before_script:
-- wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar
+ - wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar
test:atoum:
script:
- - php mageekguy.atoum.phar
+ - php mageekguy.atoum.phar
```
### Using Composer
@@ -238,16 +238,16 @@ following in your `.gitlab-ci.yml`:
# your git repository.
cache:
paths:
- - vendor/
+ - vendor/
before_script:
# Install composer dependencies
-- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
-- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
-- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
-- php composer-setup.php
-- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
-- php composer.phar install
+ - wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
+ - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+ - php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+ - php composer-setup.php
+ - php -r "unlink('composer-setup.php'); unlink('installer.sig');"
+ - php composer.phar install
...
```
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 30a64e65607..d01e9663795 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -23,32 +23,32 @@ stages:
test:
stage: test
script:
- # this configures Django application to use attached postgres database that is run on `postgres` host
- - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
- - apt-get update -qy
- - apt-get install -y python-dev python-pip
- - pip install -r requirements.txt
- - python manage.py test
+ # this configures Django application to use attached postgres database that is run on `postgres` host
+ - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
+ - apt-get update -qy
+ - apt-get install -y python-dev python-pip
+ - pip install -r requirements.txt
+ - python manage.py test
staging:
stage: deploy
script:
- - apt-get update -qy
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=gitlab-ci-python-test-staging --api-key=$HEROKU_STAGING_API_KEY
+ - apt-get update -qy
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=gitlab-ci-python-test-staging --api-key=$HEROKU_STAGING_API_KEY
only:
- - master
+ - master
production:
stage: deploy
script:
- - apt-get update -qy
- - apt-get install -y ruby-dev
- - gem install dpl
- - dpl --provider=heroku --app=gitlab-ci-python-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY
+ - apt-get update -qy
+ - apt-get install -y ruby-dev
+ - gem install dpl
+ - dpl --provider=heroku --app=gitlab-ci-python-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY
only:
- - tags
+ - tags
```
This project has three jobs:
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index 2c327181461..1d029dcdd14 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -181,8 +181,8 @@ pdf:
script: xelatex mycv.tex
artifacts:
paths:
- - ./mycv.pdf
- - ./output/
+ - ./mycv.pdf
+ - ./output/
expire_in: 1 week
```
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index 683edc50cb7..444569a6eaf 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -65,19 +65,19 @@ build:
stage: build
script: ./build
only:
- - master
+ - master
test:
stage: test
script: ./test
only:
- - merge_requests
+ - merge_requests
deploy:
stage: deploy
script: ./deploy
only:
- - master
+ - master
```
#### Excluding certain jobs
diff --git a/doc/ci/migration/circleci.md b/doc/ci/migration/circleci.md
index f6868abc334..625b15ca4fb 100644
--- a/doc/ci/migration/circleci.md
+++ b/doc/ci/migration/circleci.md
@@ -250,14 +250,14 @@ image: node:latest
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- - .npm/
+ - .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- - node ./specs/start.js ./specs/async.spec.js
+ - node ./specs/start.js ./specs/async.spec.js
```
## Contexts and variables
diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md
index 48aaebc3456..18b3fe10bec 100644
--- a/doc/ci/pipelines/index.md
+++ b/doc/ci/pipelines/index.md
@@ -348,17 +348,17 @@ For example, these three jobs will be in a group named `build ruby`:
build ruby 1/3:
stage: build
script:
- - echo "ruby1"
+ - echo "ruby1"
build ruby 2/3:
stage: build
script:
- - echo "ruby2"
+ - echo "ruby2"
build ruby 3/3:
stage: build
script:
- - echo "ruby3"
+ - echo "ruby3"
```
In the pipeline, the result is a group named `build ruby` with three jobs:
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index 24eab4f5c61..b56c3ce7ded 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -33,7 +33,7 @@ pdf:
script: xelatex mycv.tex
artifacts:
paths:
- - mycv.pdf
+ - mycv.pdf
expire_in: 1 week
```
@@ -87,8 +87,8 @@ Below is an example of collecting a JUnit XML file from Ruby's RSpec test tool:
rspec:
stage: test
script:
- - bundle install
- - rspec --format RspecJunitFormatter --out rspec.xml
+ - bundle install
+ - rspec --format RspecJunitFormatter --out rspec.xml
artifacts:
reports:
junit: rspec.xml
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index c6ac87ef888..47f11a6228c 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -62,9 +62,9 @@ and it creates a dependent pipeline relation visible on the
build_docs:
stage: deploy
script:
- - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
+ - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
only:
- - tags
+ - tags
```
Pipelines triggered that way also expose a special variable:
@@ -86,11 +86,11 @@ build_submodule:
image: debian
stage: test
script:
- - apt update && apt install -y unzip
- - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"
- - unzip artifacts.zip
+ - apt update && apt install -y unzip
+ - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"
+ - unzip artifacts.zip
only:
- - tags
+ - tags
```
This allows you to use that for multi-project pipelines and download artifacts
@@ -179,9 +179,9 @@ need to add in project A's `.gitlab-ci.yml`:
build_docs:
stage: deploy
script:
- - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
+ - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only:
- - tags
+ - tags
```
This means that whenever a new tag is pushed on project A, the job will run and the
@@ -235,24 +235,24 @@ variable is non-zero, `make upload` is run.
```yaml
stages:
-- test
-- build
-- package
+ - test
+ - build
+ - package
run_tests:
stage: test
script:
- - make test
+ - make test
build_package:
stage: build
script:
- - make build
+ - make build
upload_package:
stage: package
script:
- - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
+ - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
```
You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 72d10dfbf2b..b6dfcfa6aa5 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2914,10 +2914,10 @@ For example, to match a single file:
```yaml
test:
- script: [ 'echo 1' ]
+ script: [ "echo 'test' > file.txt" ]
artifacts:
expose_as: 'artifact 1'
- paths: ['path/to/file.txt']
+ paths: ['file.txt']
```
With this configuration, GitLab will add a link **artifact 1** to the relevant merge request
@@ -2927,10 +2927,10 @@ An example that will match an entire directory:
```yaml
test:
- script: [ 'echo 1' ]
+ script: [ "mkdir test && echo 'test' > test/file.txt" ]
artifacts:
expose_as: 'artifact 1'
- paths: ['path/to/directory/']
+ paths: ['test/']
```
Note the following:
@@ -3744,9 +3744,9 @@ Combining the individual examples given above for `release`, we'd have the follo
```yaml
stages:
-- build
-- test
-- release-stg
+ - build
+ - test
+ - release-stg
release_job:
stage: release
@@ -4332,11 +4332,11 @@ Example:
```yaml
.something_before: &something_before
-- echo 'something before'
+ - echo 'something before'
.something_after: &something_after
-- echo 'something after'
-- echo 'another thing after'
+ - echo 'something after'
+ - echo 'another thing after'
job_name:
before_script:
@@ -4358,7 +4358,7 @@ For example:
```yaml
.something: &something
-- echo 'something'
+ - echo 'something'
job_name:
script:
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 942b202a3ec..027c383bc6c 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -152,9 +152,9 @@ Suppose we have the `content/_data/versions.yaml` file with the content:
```yaml
versions:
-- 10.6
-- 10.5
-- 10.4
+ - 10.6
+ - 10.5
+ - 10.4
```
We can then loop over the `versions` array with something like:
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index e2cbcd6cc22..ac544113cbd 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -900,27 +900,79 @@ export default {
</template>
```
-#### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
+#### For JS code that is EE only, like props, computed properties, methods, etc
-- Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack.
- - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
+- Please do not use mixins unless ABSOLUTELY NECESSARY. Please try to find an alternative pattern.
-##### Example
+##### Reccomended alternative approach (named/scoped slots)
-```javascript
-import mixin from 'ee_else_ce/path/mixin';
+- We can use slots and/or scoped slots to achieve the same thing as we did with mixins. If you only need an EE component there is no need to create the CE component.
+
+1. First, we have a CE component that can render a slot incase we need EE template and functionality to be decorated on top of the CE base.
+
+```vue
+// ./ce/my_component.vue
+
+<script>
+export default {
+ props: {
+ tooltipDefaultText: {
+ type: String,
+ },
+ },
+ computed: {
+ tooltipText() {
+ return this.tooltipDefaultText || "5 issues please";
+ }
+ },
+}
+</script>
+
+<template>
+ <span v-gl-tooltip :title="tooltipText" class="ce-text">Community Edition Only Text</span>
+ <slot name="ee-specific-component">
+</template>
+```
+
+1. Next, we render the EE component, and inside of the EE component we render the CE component and add additional content in the slot.
-{
- mixins: [mixin]
+```vue
+// ./ee/my_component.vue
+
+<script>
+export default {
+ computed: {
+ tooltipText() {
+ if (this.weight) {
+ return "5 issues with weight 10";
+ }
+ }
+ },
+ methods: {
+ submit() {
+ // do something.
+ }
+ },
}
+</script>
+
+<template>
+ <my-component :tooltipDefaultText="tooltipText">
+ <template #ee-specific-component>
+ <span class="some-ee-specific">EE Specific Value</span>
+ <button @click="submit">Click Me</button>
+ </template>
+ </my-component>
+</template>
```
-- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
+1. Finally, wherever the component is needed we can require it like so
+
+`import MyComponent from 'ee_else_ce/path/my_component'.vue`
-- For store modules, we will need a CE counterpart too.
-- You can see an MR with an example [here](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9762)
+- this way the correct component will be included for either the ce or ee implementation
-#### `template` tag
+**For EE components that need different results for the same computed values, we can pass in props to the CE wrapper as seen in the example.**
- **EE Child components**
- Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee).
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 004b1884bf0..024da5cc943 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -11,16 +11,16 @@ The requirement for adding a new metric is to make each query to have an unique
```yaml
- group: Response metrics (NGINX Ingress)
metrics:
- - title: "Throughput"
- y_axis:
- name: "Requests / Sec"
- format: "number"
- precision: 2
- queries:
- - id: response_metrics_nginx_ingress_throughput_status_code
- query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
- unit: req / sec
- label: Status Code
+ - title: "Throughput"
+ y_axis:
+ name: "Requests / Sec"
+ format: "number"
+ precision: 2
+ queries:
+ - id: response_metrics_nginx_ingress_throughput_status_code
+ query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
+ unit: req / sec
+ label: Status Code
```
### Update existing metrics
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index d82f0b8de4a..a8e56feb34f 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -245,7 +245,7 @@ This will delete your existing indexes.
If the database size is less than 500 MiB, and the size of all hosted repos is less than 5 GiB:
-1. [Enable **Elasticsearch indexing** and configure your host and port](#enabling-elasticsearch).
+1. [Configure your Elasticsearch host and port](#enabling-elasticsearch).
1. Index your data:
```shell
@@ -424,7 +424,7 @@ The following are some available Rake tasks:
| Task | Description |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Wrapper task for `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, and `gitlab:elastic:index_snippets`. |
+| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enables Elasticsearch Indexing and run `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, and `gitlab:elastic:index_snippets`. |
| [`sudo gitlab-rake gitlab:elastic:index_projects`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Iterates over all projects and queues Sidekiq jobs to index them in the background. |
| [`sudo gitlab-rake gitlab:elastic:index_projects_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100. |
| [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Deletes all instances of IndexStatus for all projects. |
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index 679edbdfe40..57851565e23 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -451,7 +451,7 @@ QA testing:
environment:
name: qa
script:
- - deploy foo
+ - deploy foo
```
The track `foo` being referenced must also be defined in the application's Helm chart, like:
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 0c7c4919431..3058afe9b50 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -469,16 +469,16 @@ workers:
sidekiq:
replicaCount: 1
command:
- - /bin/herokuish
- - procfile
- - exec
- - sidekiq
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiq
preStopCommand:
- - /bin/herokuish
- - procfile
- - exec
- - sidekiqctl
- - quiet
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiqctl
+ - quiet
terminationGracePeriodSeconds: 60
```
@@ -524,12 +524,12 @@ networkPolicy:
matchLabels:
app.gitlab.com/env: staging
ingress:
- - from:
- - podSelector:
- matchLabels: {}
- - namespaceSelector:
- matchLabels:
- app.gitlab.com/managed_by: gitlab
+ - from:
+ - podSelector:
+ matchLabels: {}
+ - namespaceSelector:
+ matchLabels:
+ app.gitlab.com/managed_by: gitlab
```
For more information on installing Network Policies, see
diff --git a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
index d935af96212..44fa8dc0a58 100644
--- a/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
+++ b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png
Binary files differ
diff --git a/doc/user/clusters/crossplane.md b/doc/user/clusters/crossplane.md
index 3a430ad55bd..e3c71f9f313 100644
--- a/doc/user/clusters/crossplane.md
+++ b/doc/user/clusters/crossplane.md
@@ -55,18 +55,18 @@ export REGION=us-central1 # the GCP region where the GKE cluster is provisioned.
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- - apiGroups:
- - database.crossplane.io
- resources:
- - postgresqlinstances
- verbs:
- - get
- - list
- - create
- - update
- - delete
- - patch
- - watch
+ - apiGroups:
+ - database.crossplane.io
+ resources:
+ - postgresqlinstances
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
```
1. Apply the cluster role to the cluster:
diff --git a/doc/user/clusters/management_project.md b/doc/user/clusters/management_project.md
index c8755af29a3..892d2bce184 100644
--- a/doc/user/clusters/management_project.md
+++ b/doc/user/clusters/management_project.md
@@ -97,7 +97,7 @@ Development, Staging, and Production cluster respectively.
```yaml
stages:
-- deploy
+ - deploy
configure development cluster:
stage: deploy
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index a59b9cf80e5..ae16e176bab 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -337,9 +337,9 @@ Windows Shared Runners:
```yaml
.shared_windows_runners:
tags:
- - shared-windows
- - windows
- - windows-1809
+ - shared-windows
+ - windows
+ - windows-1809
stages:
- build
@@ -352,17 +352,17 @@ before_script:
build:
extends:
- - .shared_windows_runners
+ - .shared_windows_runners
stage: build
script:
- - echo "running scripts in the build job"
+ - echo "running scripts in the build job"
test:
extends:
- - .shared_windows_runners
+ - .shared_windows_runners
stage: test
script:
- - echo "running scripts in the test job"
+ - echo "running scripts in the test job"
```
#### Limitations and known issues
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 5cdac7ae892..8dcc08bce46 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -127,8 +127,8 @@ And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/RE
```yaml
stages:
-- test
-- deploy
+ - test
+ - deploy
test:
stage: test
diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md
index fbd2814ea75..d2de512c62b 100644
--- a/doc/user/project/clusters/add_remove_clusters.md
+++ b/doc/user/project/clusters/add_remove_clusters.md
@@ -227,9 +227,9 @@ To add a Kubernetes cluster to your project, group, or instance:
kind: ClusterRole
name: cluster-admin
subjects:
- - kind: ServiceAccount
- name: gitlab-admin
- namespace: kube-system
+ - kind: ServiceAccount
+ name: gitlab-admin
+ namespace: kube-system
```
1. Apply the service account and cluster role binding to your cluster:
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 2c532ac24c8..16d78751f40 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -99,8 +99,8 @@ And the following environments are set in
```yaml
stages:
-- test
-- deploy
+ - test
+ - deploy
test:
stage: test
diff --git a/doc/user/project/clusters/serverless/aws.md b/doc/user/project/clusters/serverless/aws.md
index 15f7e14fda9..595d8fb3895 100644
--- a/doc/user/project/clusters/serverless/aws.md
+++ b/doc/user/project/clusters/serverless/aws.md
@@ -392,29 +392,19 @@ want to store your package:
image: python:latest
stages:
-
- deploy
production:
-
stage: deploy
-
before_script:
-
- pip3 install awscli --upgrade
-
- pip3 install aws-sam-cli --upgrade
-
script:
-
- sam build
-
- sam package --output-template-file packaged.yaml --s3-bucket <S3_bucket_name>
-
- sam deploy --template-file packaged.yaml --stack-name gitlabpoc --s3-bucket <S3_bucket_name> --capabilities CAPABILITY_IAM --region us-east-1
-
environment: production
- ```
+```
Let’s examine the configuration file more closely:
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index d00f05fca66..71d03653a32 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -143,24 +143,24 @@ You must do the following:
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- - apiGroups:
- - serving.knative.dev
- resources:
- - configurations
- - configurationgenerations
- - routes
- - revisions
- - revisionuids
- - autoscalers
- - services
- verbs:
- - get
- - list
- - create
- - update
- - delete
- - patch
- - watch
+ - apiGroups:
+ - serving.knative.dev
+ resources:
+ - configurations
+ - configurationgenerations
+ - routes
+ - revisions
+ - revisionuids
+ - autoscalers
+ - services
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
```
Then run the following command:
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 337f9461e9a..1ebba7b2871 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -290,17 +290,17 @@ For example:
panel_groups:
- group: 'Group Title'
panels:
- - type: area-chart
- title: "Chart Title"
- y_label: "Y-Axis"
- y_axis:
- format: number
- precision: 0
- metrics:
- - id: my_metric_id
- query_range: 'http_requests_total'
- label: "Instance: {{instance}}, method: {{method}}"
- unit: "count"
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ y_axis:
+ format: number
+ precision: 0
+ metrics:
+ - id: my_metric_id
+ query_range: 'http_requests_total'
+ label: "Instance: {{instance}}, method: {{method}}"
+ unit: "count"
```
The above sample dashboard would display a single area chart. Each file should
@@ -641,25 +641,24 @@ To add a stacked column panel type to a dashboard, look at the following sample
dashboard: 'Dashboard title'
priority: 1
panel_groups:
-- group: 'Group Title'
- priority: 5
- panels:
- - type: 'stacked-column'
- title: "Stacked column"
- y_label: "y label"
- x_label: 'x label'
- metrics:
- - id: memory_1
- query_range: 'memory_query'
- label: "memory query 1"
- unit: "count"
- series_name: 'group 1'
- - id: memory_2
- query_range: 'memory_query_2'
- label: "memory query 2"
- unit: "count"
- series_name: 'group 2'
-
+ - group: 'Group Title'
+ priority: 5
+ panels:
+ - type: 'stacked-column'
+ title: "Stacked column"
+ y_label: "y label"
+ x_label: 'x label'
+ metrics:
+ - id: memory_1
+ query_range: 'memory_query'
+ label: "memory query 1"
+ unit: "count"
+ series_name: 'group 1'
+ - id: memory_2
+ query_range: 'memory_query_2'
+ label: "memory query 2"
+ unit: "count"
+ series_name: 'group 2'
```
![stacked column panel type](img/prometheus_dashboard_stacked_column_panel_type_v12_8.png)
@@ -681,10 +680,10 @@ panel_groups:
- title: "Single Stat"
type: "single-stat"
metrics:
- - id: 10
- query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
- unit: MB
- label: "Total"
+ - id: 10
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
+ unit: MB
+ label: "Total"
```
Note the following properties:
@@ -711,10 +710,10 @@ panel_groups:
type: "single-stat"
max_value: 100
metrics:
- - id: 10
- query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
- unit: '%'
- label: "Total"
+ - id: 10
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
+ unit: '%'
+ label: "Total"
```
For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display.
@@ -733,10 +732,10 @@ panel_groups:
- title: "Heatmap"
type: "heatmap"
metrics:
- - id: 10
- query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
- unit: req/sec
- label: "Status code"
+ - id: 10
+ query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)'
+ unit: req/sec
+ label: "Status code"
```
Note the following properties:
@@ -846,11 +845,11 @@ templating:
type: custom
options:
values:
- - value: 'value option 1' # The value that will replace the variable in queries.
- text: 'Option 1' # (Optional) Text that will appear in the UI dropdown.
- - value: 'value_option_2'
- text: 'Option 2'
- default: true # (Optional) This option should be the default value of this variable.
+ - value: 'value option 1' # The value that will replace the variable in queries.
+ text: 'Option 1' # (Optional) Text that will appear in the UI dropdown.
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true # (Optional) This option should be the default value of this variable.
```
##### `metric_label_values` variable type
@@ -1030,10 +1029,10 @@ To send GitLab alert notifications, copy the *URL* and *Authorization Key* into
receivers:
name: gitlab
webhook_configs:
- - http_config:
- bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
- send_resolved: true
- url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
+ - http_config:
+ bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
+ send_resolved: true
+ url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
...
```
diff --git a/doc/user/project/operations/alert_management.md b/doc/user/project/operations/alert_management.md
index 7b6e40c7179..75ca5a7e74b 100644
--- a/doc/user/project/operations/alert_management.md
+++ b/doc/user/project/operations/alert_management.md
@@ -85,6 +85,7 @@ Each alert contains the following metrics:
- **Start time** - How long ago the alert fired. This field uses the standard GitLab pattern of `X time ago`, but is supported by a granular date/time tooltip depending on the user's locale.
- **Alert description** - The description of the alert, which attempts to capture the most meaningful data.
- **Event count** - The number of times that an alert has fired.
+- **Issue** - A link to the incident issue that has been created for the alert.
- **Status** - The [current status](#alert-management-statuses) of the alert.
### Alert Management list sorting
diff --git a/doc/user/project/operations/img/alert_list_v13_1.png b/doc/user/project/operations/img/alert_list_v13_1.png
index 7cda00a25ad..7a1a5f5191e 100644
--- a/doc/user/project/operations/img/alert_list_v13_1.png
+++ b/doc/user/project/operations/img/alert_list_v13_1.png
Binary files differ
diff --git a/doc/user/project/pages/getting_started/pages_from_scratch.md b/doc/user/project/pages/getting_started/pages_from_scratch.md
index 23d22e9fa66..86523ab9d10 100644
--- a/doc/user/project/pages/getting_started/pages_from_scratch.md
+++ b/doc/user/project/pages/getting_started/pages_from_scratch.md
@@ -101,9 +101,9 @@ with GitLab Pages:
```yaml
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build
```
## Specify the `public` directory for output
@@ -116,9 +116,9 @@ Jekyll uses destination (`-d`) to specify an output directory for the built webs
```yaml
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
```
## Specify the `public` directory for artifacts
@@ -130,12 +130,12 @@ in the `public` directory:
```yaml
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
```
Paste this into `.gitlab-ci.yml` file, so it now looks like this:
@@ -145,12 +145,12 @@ image: ruby:2.7
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
```
Now save and commit the `.gitlab-ci.yml` file. You can watch the pipeline run
@@ -181,12 +181,12 @@ workflow:
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
```
Then configure the pipeline to run the job for the master branch only.
@@ -200,12 +200,12 @@ workflow:
pages:
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
```
@@ -232,12 +232,12 @@ workflow:
pages:
stage: deploy
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
```
@@ -255,24 +255,24 @@ workflow:
pages:
stage: deploy
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d public
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
test:
stage: test
script:
- - gem install bundler
- - bundle install
- - bundle exec jekyll build -d test
+ - gem install bundler
+ - bundle install
+ - bundle exec jekyll build -d test
artifacts:
paths:
- - test
+ - test
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
```
@@ -310,20 +310,20 @@ before_script:
pages:
stage: deploy
script:
- - bundle exec jekyll build -d public
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
test:
stage: test
script:
- - bundle exec jekyll build -d test
+ - bundle exec jekyll build -d test
artifacts:
paths:
- - test
+ - test
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
```
@@ -345,7 +345,7 @@ workflow:
cache:
paths:
- - vendor/
+ - vendor/
before_script:
- gem install bundler
@@ -354,20 +354,20 @@ before_script:
pages:
stage: deploy
script:
- - bundle exec jekyll build -d public
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
test:
stage: test
script:
- - bundle exec jekyll build -d test
+ - bundle exec jekyll build -d test
artifacts:
paths:
- - test
+ - test
rules:
- if: '$CI_COMMIT_BRANCH != "master"'
```
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 6177a81dbea..a6923779f24 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -118,14 +118,14 @@ is so `cp` doesn't also copy `public/` to itself in an infinite loop:
```yaml
pages:
script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
```
### `.gitlab-ci.yml` for a static site generator
@@ -161,13 +161,13 @@ image: ruby:2.6
pages:
script:
- - gem install jekyll
- - jekyll build -d public/
+ - gem install jekyll
+ - jekyll build -d public/
artifacts:
paths:
- - public
+ - public
only:
- - pages
+ - pages
```
See an example that has different files in the [`master` branch](https://gitlab.com/pages/jekyll-branched/tree/master)
diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md
index 53eda76aa37..ae22dbc7e72 100644
--- a/doc/user/project/requirements/index.md
+++ b/doc/user/project/requirements/index.md
@@ -162,9 +162,9 @@ requirements, add a rule which checks `CI_HAS_OPEN_REQUIREMENTS` CI variable.
```yaml
requirements_confirmation:
rules:
- - if: "$CI_HAS_OPEN_REQUIREMENTS" == "true"
- when: manual
- - when: never
+ - if: "$CI_HAS_OPEN_REQUIREMENTS" == "true"
+ when: manual
+ - when: never
allow_failure: false
script:
- mkdir tmp
diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb
index 939f29a04b3..4d5350498a7 100644
--- a/lib/api/helpers/merge_requests_helpers.rb
+++ b/lib/api/helpers/merge_requests_helpers.rb
@@ -5,7 +5,30 @@ module API
module MergeRequestsHelpers
extend Grape::API::Helpers
+ params :merge_requests_negatable_params do
+ optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
+ optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username'
+ mutually_exclusive :author_id, :author_username
+
+ optional :assignee_id,
+ types: [Integer, String],
+ integer_none_any: true,
+ desc: 'Return merge requests which are assigned to the user with the given ID'
+ optional :assignee_username, type: Array[String], check_assignees_count: true,
+ coerce_with: Validations::Validators::CheckAssigneesCount.coerce,
+ desc: 'Return merge requests which are assigned to the user with the given username'
+ mutually_exclusive :assignee_id, :assignee_username
+
+ optional :labels,
+ type: Array[String],
+ coerce_with: Validations::Types::CommaSeparatedToArray.coerce,
+ desc: 'Comma-separated list of label names'
+ optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
+ end
+
params :merge_requests_base_params do
+ use :merge_requests_negatable_params
optional :state,
type: String,
values: %w[opened closed locked merged all],
@@ -21,11 +44,6 @@ module API
values: %w[asc desc],
default: 'desc',
desc: 'Return merge requests sorted in `asc` or `desc` order.'
- optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
- optional :labels,
- type: Array[String],
- coerce_with: Validations::Types::CommaSeparatedToArray.coerce,
- desc: 'Comma-separated list of label names'
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
optional :with_merge_status_recheck, type: Boolean, desc: 'Request that stale merge statuses be rechecked asynchronously', default: false
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
@@ -37,19 +55,10 @@ module API
values: %w[simple],
desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
- optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
- optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username'
- mutually_exclusive :author_id, :author_username
-
- optional :assignee_id,
- types: [Integer, String],
- integer_none_any: true,
- desc: 'Return merge requests which are assigned to the user with the given ID'
optional :scope,
type: String,
values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`'
- optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :source_branch, type: String, desc: 'Return merge requests with the given source branch'
optional :source_project_id, type: Integer, desc: 'Return merge requests with the given source project id'
optional :target_branch, type: String, desc: 'Return merge requests with the given target branch'
@@ -58,6 +67,9 @@ module API
desc: 'Search merge requests for text present in the title, description, or any combination of these'
optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma'
optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title'
+ optional :not, type: Hash, desc: 'Parameters to negate' do
+ use :merge_requests_negatable_params
+ end
end
params :optional_scope_param do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 82f103ff0ae..49493b36d10 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -44,7 +44,9 @@ module API
def find_merge_requests(args = {})
args = declared_params.merge(args)
args[:milestone_title] = args.delete(:milestone)
+ args[:not][:milestone_title] = args[:not]&.delete(:milestone)
args[:label_name] = args.delete(:labels)
+ args[:not][:label_name] = args[:not]&.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
merge_requests = MergeRequestsFinder.new(current_user, args).execute
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 8b116c1c30d..f36f199ab77 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -90,6 +90,10 @@ module Gitlab
metrics.pipeline_size_histogram
.observe({ source: pipeline.source.to_s }, pipeline.total_size)
end
+
+ def dangling_build?
+ %i[ondemand_dast_scan webide].include?(source)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb b/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb
index b93479b4142..3dd216b33d1 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content/parameter.rb
@@ -7,9 +7,12 @@ module Gitlab
module Config
class Content
class Parameter < Source
+ UnsupportedSourceError = Class.new(StandardError)
+
def content
strong_memoize(:content) do
next unless command.content.present?
+ raise UnsupportedSourceError, "#{command.source} not a dangling build" unless command.dangling_build?
command.content
end
diff --git a/lib/gitlab/danger/sidekiq_queues.rb b/lib/gitlab/danger/sidekiq_queues.rb
index 5bf6057d5ba..726b6134abf 100644
--- a/lib/gitlab/danger/sidekiq_queues.rb
+++ b/lib/gitlab/danger/sidekiq_queues.rb
@@ -14,7 +14,7 @@ module Gitlab
def changed_queue_names
@changed_queue_names ||=
(new_queues.values_at(*old_queues.keys) - old_queues.values)
- .map { |queue| queue[:name] }
+ .compact.map { |queue| queue[:name] }
end
private
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3c5670d61c7..d8bbaa33b40 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -177,6 +177,11 @@ msgid_plural "%d issues"
msgstr[0] ""
msgstr[1] ""
+msgid "%d issue in this group"
+msgid_plural "%d issues in this group"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d issue selected"
msgid_plural "%d issues selected"
msgstr[0] ""
@@ -389,13 +394,10 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
-msgid "%{issuesCount} issues in this group"
-msgstr ""
-
-msgid "%{issuesSize} issues"
+msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
msgstr ""
-msgid "%{issuesSize} issues with a limit of %{maxIssueCount}"
+msgid "%{issuesSize} with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
@@ -1966,6 +1968,9 @@ msgstr ""
msgid "AlertManagement|Info"
msgstr ""
+msgid "AlertManagement|Issue"
+msgstr ""
+
msgid "AlertManagement|Low"
msgstr ""
@@ -4762,6 +4767,9 @@ msgstr ""
msgid "Closed"
msgstr ""
+msgid "Closed %{epicTimeagoDate}"
+msgstr ""
+
msgid "Closed issues"
msgstr ""
@@ -11015,6 +11023,9 @@ msgstr ""
msgid "Go to environments"
msgstr ""
+msgid "Go to epic"
+msgstr ""
+
msgid "Go to file"
msgstr ""
@@ -12707,6 +12718,9 @@ msgstr ""
msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
msgstr ""
+msgid "Issues with no epics assigned"
+msgstr ""
+
msgid "Issues, merge requests, pushes, and comments."
msgstr ""
@@ -15832,6 +15846,9 @@ msgstr ""
msgid "Opened"
msgstr ""
+msgid "Opened %{epicTimeagoDate}"
+msgstr ""
+
msgid "Opened MRs"
msgstr ""
@@ -20247,7 +20264,7 @@ msgstr ""
msgid "SecurityReports|Scan details"
msgstr ""
-msgid "SecurityReports|Scanner type"
+msgid "SecurityReports|Scanner"
msgstr ""
msgid "SecurityReports|Security Dashboard"
@@ -25609,10 +25626,10 @@ msgstr ""
msgid "Vulnerability|Project"
msgstr ""
-msgid "Vulnerability|Scanner Provider"
+msgid "Vulnerability|Scanner"
msgstr ""
-msgid "Vulnerability|Scanner Type"
+msgid "Vulnerability|Scanner Provider"
msgstr ""
msgid "Vulnerability|Severity"
@@ -26843,7 +26860,7 @@ msgstr ""
msgid "ciReport|All projects"
msgstr ""
-msgid "ciReport|All scanner types"
+msgid "ciReport|All scanners"
msgstr ""
msgid "ciReport|All severities"
diff --git a/qa/qa.rb b/qa/qa.rb
index 3f93d79ee51..eec7c10b2eb 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -320,6 +320,7 @@ module QA
module Milestone
autoload :New, 'qa/page/project/milestone/new'
autoload :Index, 'qa/page/project/milestone/index'
+ autoload :Show, 'qa/page/project/milestone/show'
end
module Operations
diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb
index 6895c44f72f..20f73fdd545 100644
--- a/qa/qa/page/project/milestone/index.rb
+++ b/qa/qa/page/project/milestone/index.rb
@@ -6,11 +6,23 @@ module QA
module Milestone
class Index < Page::Base
view 'app/views/projects/milestones/index.html.haml' do
- element :new_project_milestone
+ element :new_project_milestone_link
end
- def click_new_milestone
- click_element :new_project_milestone
+ view 'app/views/shared/milestones/_milestone.html.haml' do
+ element :milestone_link
+ end
+
+ def click_new_milestone_link
+ click_element :new_project_milestone_link
+ end
+
+ def has_milestone?(milestone)
+ has_element? :milestone_link, milestone_title: milestone.title
+ end
+
+ def click_milestone(milestone)
+ click_element :milestone_link, milestone_title: milestone.title
end
end
end
diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb
index 751fb141684..9453585f199 100644
--- a/qa/qa/page/project/milestone/new.rb
+++ b/qa/qa/page/project/milestone/new.rb
@@ -6,21 +6,34 @@ module QA
module Milestone
class New < Page::Base
view 'app/views/projects/milestones/_form.html.haml' do
- element :milestone_create_button
- element :milestone_title
- element :milestone_description
+ element :create_milestone_button
+ element :milestone_description_field
+ element :milestone_title_field
+ end
+
+ view 'app/views/shared/milestones/_form_dates.html.haml' do
+ element :due_date_field
+ element :start_date_field
+ end
+
+ def click_create_milestone_button
+ click_element :create_milestone_button
end
def set_title(title)
- fill_element :milestone_title, title
+ fill_element :milestone_title_field, title
end
def set_description(description)
- fill_element :milestone_description, description
+ fill_element :milestone_description_field, description
+ end
+
+ def set_due_date(due_date)
+ fill_element :due_date_field, due_date.to_s + "\n"
end
- def click_milestone_create_button
- click_element :milestone_create_button
+ def set_start_date(start_date)
+ fill_element :start_date_field, start_date.to_s + "\n"
end
end
end
diff --git a/qa/qa/page/project/milestone/show.rb b/qa/qa/page/project/milestone/show.rb
new file mode 100644
index 00000000000..ebcf9347113
--- /dev/null
+++ b/qa/qa/page/project/milestone/show.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Milestone
+ class Show < ::QA::Page::Base
+ include Support::Dates
+
+ view 'app/views/shared/milestones/_description.html.haml' do
+ element :milestone_title_content, required: true
+ element :milestone_description_content
+ end
+
+ view 'app/views/shared/milestones/_sidebar.html.haml' do
+ element :due_date_content
+ element :start_date_content
+ end
+
+ def has_due_date?(due_date)
+ formatted_due_date = format_date(due_date)
+ has_element?(:due_date_content, text: formatted_due_date)
+ end
+
+ def has_start_date?(start_date)
+ formatted_start_date = format_date(start_date)
+ has_element?(:start_date_content, text: formatted_start_date)
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Milestone::Show.prepend_if_ee('QA::EE::Page::Project::Milestone::Show')
diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb
index c15a8ec4cc7..124faf0d346 100644
--- a/qa/qa/page/project/sub_menus/issues.rb
+++ b/qa/qa/page/project/sub_menus/issues.rb
@@ -50,6 +50,14 @@ module QA
end
end
+ def go_to_milestones
+ hover_issues do
+ within_submenu do
+ click_element(:milestones_link)
+ end
+ end
+ end
+
private
def hover_issues
diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb
index 385b9f0c96b..c9218e03e35 100644
--- a/qa/qa/resource/project_milestone.rb
+++ b/qa/qa/resource/project_milestone.rb
@@ -7,6 +7,7 @@ module QA
attribute :id
attribute :title
+ attribute :description
attribute :project do
Project.fabricate_via_api! do |resource|
@@ -16,6 +17,7 @@ module QA
def initialize
@title = "project-milestone-#{SecureRandom.hex(4)}"
+ @description = "My awesome project milestone."
end
def api_get_path
@@ -28,12 +30,28 @@ module QA
def api_post_body
{
- title: title
+ title: title,
+ description: description
}.tap do |hash|
hash[:start_date] = @start_date if @start_date
hash[:due_date] = @due_date if @due_date
end
end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_milestones)
+ Page::Project::Milestone::Index.perform(&:click_new_milestone_link)
+
+ Page::Project::Milestone::New.perform do |new_milestone|
+ new_milestone.set_title(@title)
+ new_milestone.set_description(@description)
+ new_milestone.set_start_date(@start_date) if @start_date
+ new_milestone.set_due_date(@due_date) if @due_date
+ new_milestone.click_create_milestone_button
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
new file mode 100644
index 00000000000..b62d7a83eea
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'Project milestone' do
+ include Support::Dates
+
+ let(:title) { 'Project milestone' }
+ let(:description) { 'This issue tests out project milestones.' }
+ let(:start_date) { current_date_yyyy_mm_dd }
+ let(:due_date) { next_month_yyyy_mm_dd }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'creates a project milestone' do
+ project_milestone = Resource::ProjectMilestone.fabricate_via_browser_ui! do |milestone|
+ milestone.title = title
+ milestone.description = description
+ milestone.start_date = start_date
+ milestone.due_date = due_date
+ end
+
+ Page::Project::Menu.perform(&:go_to_milestones)
+ Page::Project::Milestone::Index.perform do |milestone_list|
+ expect(milestone_list).to have_milestone(project_milestone)
+
+ milestone_list.click_milestone(project_milestone)
+ end
+
+ Page::Project::Milestone::Show.perform do |milestone|
+ expect(milestone).to have_element(:milestone_title_content, text: title)
+ expect(milestone).to have_element(:milestone_description_content, text: description)
+ expect(milestone).to have_start_date(start_date)
+ expect(milestone).to have_due_date(due_date)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/dates.rb b/qa/qa/support/dates.rb
index 47fc721afc1..3d1f146730b 100644
--- a/qa/qa/support/dates.rb
+++ b/qa/qa/support/dates.rb
@@ -11,6 +11,11 @@ module QA
current_date.next_month.strftime("%Y/%m/%d")
end
+ def format_date(date)
+ new_date = DateTime.strptime(date, "%Y/%m/%d")
+ new_date.strftime("%b %-d, %Y")
+ end
+
private
def current_date
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 94d79d60aeb..0193712aeea 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -12,7 +12,16 @@ RSpec.describe 'Project navbar' do
let_it_be(:project) { create(:project, :repository) }
before do
- stub_licensed_features(service_desk: false)
+ # TODO - This can be moved into 'project navbar structure' shared
+ # context when service desk feature gets moved to core.
+ # More information in: https://gitlab.com/gitlab-org/gitlab/-/issues/215364
+ if Gitlab.ee?
+ insert_after_sub_nav_item(
+ _('Labels'),
+ within: _('Issues'),
+ new_sub_nav_item_name: _('Service Desk')
+ )
+ end
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index f76110e3d85..e3643698012 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -53,6 +53,21 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to be_empty
end
+ context 'filtering by not author ID' do
+ let(:params) { { not: { author_id: user2.id } } }
+
+ before do
+ merge_request2.update!(author: user2)
+ merge_request3.update!(author: user2)
+ end
+
+ it 'returns merge requests not created by that user' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
+
it 'filters by projects' do
params = { projects: [project2.id, project3.id] }
@@ -258,6 +273,11 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request1, merge_request2] }
end
+ it_behaves_like 'assignee NOT ID filter' do
+ let(:params) { { not: { assignee_id: user.id } } }
+ let(:expected_issuables) { [merge_request3, merge_request4, merge_request5] }
+ end
+
it_behaves_like 'assignee username filter' do
before do
project2.add_developer(user3)
@@ -269,6 +289,15 @@ RSpec.describe MergeRequestsFinder do
let(:expected_issuables) { [merge_request3] }
end
+ it_behaves_like 'assignee NOT username filter' do
+ before do
+ merge_request2.assignees = [user2]
+ end
+
+ let(:params) { { not: { assignee_username: [user.username, user2.username] } } }
+ let(:expected_issuables) { [merge_request4, merge_request5] }
+ end
+
it_behaves_like 'no assignee filter' do
let_it_be(:user3) { create(:user) }
let(:expected_issuables) { [merge_request4, merge_request5] }
@@ -294,6 +323,16 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
end
+
+ context 'using NOT' do
+ let(:params) { { not: { milestone_title: group_milestone.title } } }
+
+ it 'returns MRs not assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
+ end
+ end
end
end
diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js
index 284fd078ab3..e2dc7d79a85 100644
--- a/spec/frontend/alert_management/components/alert_management_list_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_list_spec.js
@@ -48,6 +48,7 @@ describe('AlertManagementList', () => {
const findSeverityColumnHeader = () => wrapper.findAll('th').at(0);
const findPagination = () => wrapper.find(GlPagination);
const findSearch = () => wrapper.find(GlSearchBoxByType);
+ const findIssueFields = () => wrapper.findAll('[data-testid="issueField"]');
const alertsCount = {
open: 14,
triggered: 10,
@@ -278,6 +279,37 @@ describe('AlertManagementList', () => {
expect(visitUrl).toHaveBeenCalledWith('/1527542/details');
});
+ describe('alert issue links', () => {
+ beforeEach(() => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
+ loading: false,
+ });
+ });
+
+ it('shows "None" when no link exists', () => {
+ expect(
+ findIssueFields()
+ .at(0)
+ .text(),
+ ).toBe('None');
+ });
+
+ it('renders a link when one exists', () => {
+ expect(
+ findIssueFields()
+ .at(1)
+ .text(),
+ ).toBe('#1');
+ expect(
+ findIssueFields()
+ .at(1)
+ .attributes('href'),
+ ).toBe('/gitlab-org/gitlab/-/issues/1');
+ });
+ });
+
describe('handle date fields', () => {
it('should display time ago dates when values provided', () => {
mountComponent({
diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json
index 312d1756790..34eed7ae024 100644
--- a/spec/frontend/alert_management/mocks/alerts.json
+++ b/spec/frontend/alert_management/mocks/alerts.json
@@ -20,6 +20,7 @@
"endedAt": "2020-04-17T23:18:14.996Z",
"status": "ACKNOWLEDGED",
"assignees": { "nodes": [{ "username": "root" }] },
+ "issueIid": "1",
"notes": {
"nodes": [
{
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index f894b2af5cf..cbcd8305c10 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -26,10 +26,9 @@ import {
setMetricResult,
setupStoreWithData,
setupStoreWithDataForPanelCount,
- setupStoreWithVariable,
setupStoreWithLinks,
} from '../store_utils';
-import { environmentData, dashboardGitResponse } from '../mock_data';
+import { environmentData, dashboardGitResponse, storeVariables } from '../mock_data';
import {
metricsDashboardViewModel,
metricsDashboardPanelCount,
@@ -604,8 +603,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
- setupStoreWithVariable(store);
-
+ store.state.monitoringDashboard.variables = storeVariables;
return wrapper.vm.$nextTick();
});
diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
index 623125d18f1..cc384aef231 100644
--- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -59,7 +59,7 @@ describe('Custom variable component', () => {
.vm.$emit('click');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
});
});
});
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index 68bfb8ec695..99c6facac38 100644
--- a/spec/frontend/monitoring/components/variables/text_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -40,7 +40,7 @@ describe('Text variable component', () => {
findInput().trigger('keyup.enter');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
});
});
@@ -53,7 +53,7 @@ describe('Text variable component', () => {
findInput().trigger('blur');
return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
});
});
});
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
index 9fb93a18e0b..d8e63917eec 100644
--- a/spec/frontend/monitoring/components/variables_section_spec.js
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -6,8 +6,7 @@ import TextField from '~/monitoring/components/variables/text_field.vue';
import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import { createStore } from '~/monitoring/stores';
import { convertVariablesForURL } from '~/monitoring/utils';
-import * as types from '~/monitoring/stores/mutation_types';
-import { mockTemplatingDataResponses } from '../mock_data';
+import { storeVariables } from '../mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
@@ -17,12 +16,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('Metrics dashboard/variables section component', () => {
let store;
let wrapper;
- const sampleVariables = {
- label1: mockTemplatingDataResponses.simpleText.simpleText,
- label2: mockTemplatingDataResponses.advText.advText,
- label3: mockTemplatingDataResponses.simpleCustom.simpleCustom,
- label4: mockTemplatingDataResponses.metricLabelValues.simple,
- };
const createShallowWrapper = () => {
wrapper = shallowMount(VariablesSection, {
@@ -48,22 +41,23 @@ describe('Metrics dashboard/variables section component', () => {
describe('when variables are set', () => {
beforeEach(() => {
+ store.state.monitoringDashboard.variables = storeVariables;
createShallowWrapper();
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
+
return wrapper.vm.$nextTick;
});
it('shows the variables section', () => {
const allInputs = findTextInputs().length + findCustomInputs().length;
- expect(allInputs).toBe(Object.keys(sampleVariables).length);
+ expect(allInputs).toBe(storeVariables.length);
});
it('shows the right custom variable inputs', () => {
const customInputs = findCustomInputs();
- expect(customInputs.at(0).props('name')).toBe('label3');
- expect(customInputs.at(1).props('name')).toBe('label4');
+ expect(customInputs.at(0).props('name')).toBe('customSimple');
+ expect(customInputs.at(1).props('name')).toBe('customAdvanced');
});
});
@@ -77,7 +71,7 @@ describe('Metrics dashboard/variables section component', () => {
namespaced: true,
state: {
showEmptyState: false,
- variables: sampleVariables,
+ variables: storeVariables,
},
actions: {
updateVariablesAndFetchData,
@@ -92,12 +86,12 @@ describe('Metrics dashboard/variables section component', () => {
it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => {
const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -107,12 +101,12 @@ describe('Metrics dashboard/variables section component', () => {
it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => {
const firstInput = findCustomInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'test');
+ firstInput.vm.$emit('input', 'test');
return wrapper.vm.$nextTick(() => {
expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(sampleVariables),
+ convertVariablesForURL(storeVariables),
window.location.href,
);
expect(updateHistory).toHaveBeenCalled();
@@ -122,7 +116,7 @@ describe('Metrics dashboard/variables section component', () => {
it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => {
const firstInput = findTextInputs().at(0);
- firstInput.vm.$emit('onUpdate', 'label1', 'Simple text');
+ firstInput.vm.$emit('input', 'My default value');
expect(updateVariablesAndFetchData).not.toHaveBeenCalled();
expect(mergeUrlParams).not.toHaveBeenCalled();
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index e5b25a37976..8ccae1228e8 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -627,81 +627,79 @@ export const mockLinks = [
},
];
-const templatingVariableTypes = {
+export const templatingVariablesExamples = {
text: {
- simple: 'Simple text',
- advanced: {
- label: 'Variable 4',
+ textSimple: 'My default value',
+ textAdvanced: {
+ label: 'Advanced text variable',
type: 'text',
options: {
- default_value: 'default',
+ default_value: 'A default value',
},
},
},
custom: {
- simple: ['value1', 'value2', 'value3'],
- advanced: {
- normal: {
- label: 'Advanced Var',
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
- },
- withoutOpts: {
- type: 'custom',
- options: {},
+ customSimple: ['value1', 'value2', 'value3'],
+ customAdvanced: {
+ label: 'Advanced Var',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutLabel: {
- type: 'custom',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOpts: {
+ type: 'custom',
+ options: {},
+ },
+ customAdvancedWithoutLabel: {
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutType: {
- label: 'Variable 2',
- options: {
- values: [
- { value: 'value1', text: 'Var 1 Option 1' },
- {
- value: 'value2',
- text: 'Var 1 Option 2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutType: {
+ label: 'Variable 2',
+ options: {
+ values: [
+ { value: 'value1', text: 'Var 1 Option 1' },
+ {
+ value: 'value2',
+ text: 'Var 1 Option 2',
+ default: true,
+ },
+ ],
},
- withoutOptText: {
- label: 'Options without text',
- type: 'custom',
- options: {
- values: [
- { value: 'value1' },
- {
- value: 'value2',
- default: true,
- },
- ],
- },
+ },
+ customAdvancedWithoutOptText: {
+ label: 'Options without text',
+ type: 'custom',
+ options: {
+ values: [
+ { value: 'value1' },
+ {
+ value: 'value2',
+ default: true,
+ },
+ ],
},
},
},
metricLabelValues: {
- simple: {
+ metricLabelValuesSimple: {
label: 'Metric Label Values',
type: 'metric_label_values',
options: {
@@ -713,205 +711,92 @@ const templatingVariableTypes = {
},
};
-const generateMockTemplatingData = data => {
- const vars = data
- ? {
- variables: {
- ...data,
- },
- }
- : {};
- return {
- dashboard: {
- templating: vars,
- },
- };
-};
-
-const responseForSimpleTextVariable = {
- simpleText: {
- label: 'simpleText',
+export const storeTextVariables = [
+ {
type: 'text',
- value: 'Simple text',
+ name: 'textSimple',
+ label: 'textSimple',
+ value: 'My default value',
},
-};
-
-const responseForAdvTextVariable = {
- advText: {
- label: 'Variable 4',
+ {
type: 'text',
- value: 'default',
+ name: 'textAdvanced',
+ label: 'Advanced text variable',
+ value: 'A default value',
},
-};
+];
-const responseForSimpleCustomVariable = {
- simpleCustom: {
- label: 'simpleCustom',
- value: 'value1',
+export const storeCustomVariables = [
+ {
+ type: 'custom',
+ name: 'customSimple',
+ label: 'customSimple',
options: {
values: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: false,
- text: 'value2',
- value: 'value2',
- },
- {
- default: false,
- text: 'value3',
- value: 'value3',
- },
+ { default: false, text: 'value1', value: 'value1' },
+ { default: false, text: 'value2', value: 'value2' },
+ { default: false, text: 'value3', value: 'value3' },
],
},
- type: 'custom',
+ value: 'value1',
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptions = {
- advCustomWithoutOpts: {
- label: 'advCustomWithoutOpts',
+ {
+ type: 'custom',
+ name: 'customAdvanced',
+ label: 'Advanced Var',
options: {
- values: [],
+ values: [
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
+ ],
},
+ value: 'value2',
+ },
+ {
type: 'custom',
+ name: 'customAdvancedWithoutOpts',
+ label: 'customAdvancedWithoutOpts',
+ options: { values: [] },
+ value: null,
},
-};
-
-const responseForAdvancedCustomVariableWithoutLabel = {
- advCustomWithoutLabel: {
- label: 'advCustomWithoutLabel',
+ {
+ type: 'custom',
+ name: 'customAdvancedWithoutLabel',
+ label: 'customAdvancedWithoutLabel',
value: 'value2',
options: {
values: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
+ { default: false, text: 'Var 1 Option 1', value: 'value1' },
+ { default: true, text: 'Var 1 Option 2', value: 'value2' },
],
},
- type: 'custom',
},
-};
-
-const responseForAdvancedCustomVariableWithoutOptText = {
- advCustomWithoutOptText: {
+ {
+ type: 'custom',
+ name: 'customAdvancedWithoutOptText',
label: 'Options without text',
- value: 'value2',
options: {
values: [
- {
- default: false,
- text: 'value1',
- value: 'value1',
- },
- {
- default: true,
- text: 'value2',
- value: 'value2',
- },
+ { default: false, text: 'value1', value: 'value1' },
+ { default: true, text: 'value2', value: 'value2' },
],
},
- type: 'custom',
+ value: 'value2',
},
-};
+];
-const responseForMetricLabelValues = {
- simple: {
- label: 'Metric Label Values',
+export const storeMetricLabelValuesVariables = [
+ {
type: 'metric_label_values',
+ name: 'metricLabelValuesSimple',
+ label: 'Metric Label Values',
+ options: { prometheusEndpointPath: '/series', label: 'backend', values: [] },
value: null,
- options: {
- prometheusEndpointPath: '/series',
- label: 'backend',
- values: [],
- },
},
-};
-
-const responseForAdvancedCustomVariable = {
- ...responseForSimpleCustomVariable,
- advCustomNormal: {
- label: 'Advanced Var',
- value: 'value2',
- options: {
- values: [
- {
- default: false,
- text: 'Var 1 Option 1',
- value: 'value1',
- },
- {
- default: true,
- text: 'Var 1 Option 2',
- value: 'value2',
- },
- ],
- },
- type: 'custom',
- },
-};
-
-const responsesForAllVariableTypes = {
- ...responseForSimpleTextVariable,
- ...responseForAdvTextVariable,
- ...responseForSimpleCustomVariable,
- ...responseForAdvancedCustomVariable,
-};
-
-export const mockTemplatingData = {
- emptyTemplatingProp: generateMockTemplatingData(),
- emptyVariablesProp: generateMockTemplatingData({}),
- simpleText: generateMockTemplatingData({ simpleText: templatingVariableTypes.text.simple }),
- advText: generateMockTemplatingData({ advText: templatingVariableTypes.text.advanced }),
- simpleCustom: generateMockTemplatingData({ simpleCustom: templatingVariableTypes.custom.simple }),
- advCustomWithoutOpts: generateMockTemplatingData({
- advCustomWithoutOpts: templatingVariableTypes.custom.advanced.withoutOpts,
- }),
- advCustomWithoutType: generateMockTemplatingData({
- advCustomWithoutType: templatingVariableTypes.custom.advanced.withoutType,
- }),
- advCustomWithoutLabel: generateMockTemplatingData({
- advCustomWithoutLabel: templatingVariableTypes.custom.advanced.withoutLabel,
- }),
- advCustomWithoutOptText: generateMockTemplatingData({
- advCustomWithoutOptText: templatingVariableTypes.custom.advanced.withoutOptText,
- }),
- simpleAndAdv: generateMockTemplatingData({
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
- metricLabelValues: generateMockTemplatingData({
- simple: templatingVariableTypes.metricLabelValues.simple,
- }),
- allVariableTypes: generateMockTemplatingData({
- simpleText: templatingVariableTypes.text.simple,
- advText: templatingVariableTypes.text.advanced,
- simpleCustom: templatingVariableTypes.custom.simple,
- advCustomNormal: templatingVariableTypes.custom.advanced.normal,
- }),
-};
+];
-export const mockTemplatingDataResponses = {
- emptyTemplatingProp: {},
- emptyVariablesProp: {},
- simpleText: responseForSimpleTextVariable,
- advText: responseForAdvTextVariable,
- simpleCustom: responseForSimpleCustomVariable,
- advCustomWithoutOpts: responseForAdvancedCustomVariableWithoutOptions,
- advCustomWithoutType: {},
- advCustomWithoutLabel: responseForAdvancedCustomVariableWithoutLabel,
- advCustomWithoutOptText: responseForAdvancedCustomVariableWithoutOptText,
- simpleAndAdv: responseForAdvancedCustomVariable,
- allVariableTypes: responsesForAllVariableTypes,
- metricLabelValues: responseForMetricLabelValues,
-};
+export const storeVariables = [
+ ...storeTextVariables,
+ ...storeCustomVariables,
+ ...storeMetricLabelValuesVariables,
+];
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 0e28b70a760..ad01e4c3a9b 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -44,7 +44,6 @@ import {
deploymentData,
environmentData,
annotationsData,
- mockTemplatingData,
dashboardGitResponse,
mockDashboardsErrorResponse,
} from '../mock_data';
@@ -305,32 +304,6 @@ describe('Monitoring store actions', () => {
expect(dispatch).toHaveBeenCalledWith('fetchDashboardData');
});
- it('stores templating variables', () => {
- const response = {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- };
-
- receiveMetricsDashboardSuccess(
- { state, commit, dispatch },
- {
- response: {
- ...metricsDashboardResponse,
- dashboard: {
- ...metricsDashboardResponse.dashboard,
- ...mockTemplatingData.allVariableTypes.dashboard,
- },
- },
- },
- );
-
- expect(commit).toHaveBeenCalledWith(
- types.RECEIVE_METRICS_DASHBOARD_SUCCESS,
-
- response,
- );
- });
-
it('sets the dashboards loaded from the repository', () => {
const params = {};
const response = metricsDashboardResponse;
@@ -1144,11 +1117,13 @@ describe('Monitoring store actions', () => {
describe('fetchVariableMetricLabelValues', () => {
const variable = {
type: 'metric_label_values',
+ name: 'label1',
options: {
- prometheusEndpointPath: '/series',
+ prometheusEndpointPath: '/series?match[]=metric_name',
label: 'job',
},
};
+
const defaultQueryParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
@@ -1158,9 +1133,7 @@ describe('Monitoring store actions', () => {
state = {
...state,
timeRange: defaultTimeRange,
- variables: {
- label1: variable,
- },
+ variables: [variable],
};
});
@@ -1176,7 +1149,7 @@ describe('Monitoring store actions', () => {
},
];
- mock.onGet('/series').reply(200, {
+ mock.onGet('/series?match[]=metric_name').reply(200, {
status: 'success',
data,
});
@@ -1196,7 +1169,7 @@ describe('Monitoring store actions', () => {
});
it('should notify the user that dynamic options were not loaded', () => {
- mock.onGet('/series').reply(500);
+ mock.onGet('/series?match[]=metric_name').reply(500);
return testAction(fetchVariableMetricLabelValues, { defaultQueryParams }, state, [], []).then(
() => {
diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js
index 1275686de58..a69f5265ea7 100644
--- a/spec/frontend/monitoring/store/getters_spec.js
+++ b/spec/frontend/monitoring/store/getters_spec.js
@@ -8,7 +8,7 @@ import {
environmentData,
metricsResult,
dashboardGitResponse,
- mockTemplatingDataResponses,
+ storeVariables,
mockLinks,
} from '../mock_data';
import {
@@ -344,19 +344,21 @@ describe('Monitoring store Getters', () => {
});
it('transforms the variables object to an array in the [variable, variable_value] format for all variable types', () => {
- mutations[types.SET_VARIABLES](state, mockTemplatingDataResponses.allVariableTypes);
+ state.variables = storeVariables;
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({
- 'variables[advCustomNormal]': 'value2',
- 'variables[advText]': 'default',
- 'variables[simpleCustom]': 'value1',
- 'variables[simpleText]': 'Simple text',
+ 'variables[textSimple]': 'My default value',
+ 'variables[textAdvanced]': 'A default value',
+ 'variables[customSimple]': 'value1',
+ 'variables[customAdvanced]': 'value2',
+ 'variables[customAdvancedWithoutLabel]': 'value2',
+ 'variables[customAdvancedWithoutOptText]': 'value2',
});
});
it('transforms the variables object to an empty array when no keys are present', () => {
- mutations[types.SET_VARIABLES](state, {});
+ state.variables = [];
const variablesArray = getters.getCustomVariablesParams(state);
expect(variablesArray).toEqual({});
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index fb5e6156daf..37da5ea96d9 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -5,7 +5,7 @@ import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
import { metricStates } from '~/monitoring/constants';
-import { deploymentData, dashboardGitResponse } from '../mock_data';
+import { deploymentData, dashboardGitResponse, storeTextVariables } from '../mock_data';
import { metricsDashboardPayload } from '../fixture_data';
describe('Monitoring mutations', () => {
@@ -427,30 +427,12 @@ describe('Monitoring mutations', () => {
});
});
- describe('SET_VARIABLES', () => {
- it('stores an empty variables array when no custom variables are given', () => {
- mutations[types.SET_VARIABLES](stateCopy, {});
-
- expect(stateCopy.variables).toEqual({});
- });
-
- it('stores variables in the key key_value format in the array', () => {
- mutations[types.SET_VARIABLES](stateCopy, { pod: 'POD', stage: 'main ops' });
-
- expect(stateCopy.variables).toEqual({ pod: 'POD', stage: 'main ops' });
- });
- });
-
describe('UPDATE_VARIABLE_VALUE', () => {
- afterEach(() => {
- mutations[types.SET_VARIABLES](stateCopy, {});
- });
-
it('updates only the value of the variable in variables', () => {
- mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } });
- mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, { key: 'environment', value: 'new prod' });
+ stateCopy.variables = storeTextVariables;
+ mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, { name: 'textSimple', value: 'New Value' });
- expect(stateCopy.variables).toEqual({ environment: { value: 'new prod', type: 'text' } });
+ expect(stateCopy.variables[0].value).toEqual('New Value');
});
});
diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js
index 8ee37a529dd..b97948fa1bf 100644
--- a/spec/frontend/monitoring/store/utils_spec.js
+++ b/spec/frontend/monitoring/store/utils_spec.js
@@ -22,7 +22,7 @@ describe('mapToDashboardViewModel', () => {
dashboard: '',
panelGroups: [],
links: [],
- variables: {},
+ variables: [],
});
});
@@ -52,7 +52,7 @@ describe('mapToDashboardViewModel', () => {
expect(mapToDashboardViewModel(response)).toEqual({
dashboard: 'Dashboard Name',
links: [],
- variables: {},
+ variables: [],
panelGroups: [
{
group: 'Group 1',
@@ -424,22 +424,20 @@ describe('mapToDashboardViewModel', () => {
urlUtils.queryToObject.mockReturnValueOnce();
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ name: 'pod',
+ label: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ name: 'pod_2',
+ label: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('sets variables as-is from yml file if URL has no matching variables', () => {
@@ -458,22 +456,20 @@ describe('mapToDashboardViewModel', () => {
'var-environment': 'POD',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'kubernetes',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'kubernetes-2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'kubernetes',
},
- });
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'kubernetes-2',
+ },
+ ]);
});
it('merges variables from URL with the ones from yml file', () => {
@@ -494,22 +490,20 @@ describe('mapToDashboardViewModel', () => {
'var-pod_2': 'POD2',
});
- expect(mapToDashboardViewModel(response)).toMatchObject({
- dashboard: 'Dashboard Name',
- links: [],
- variables: {
- pod: {
- label: 'pod',
- type: 'text',
- value: 'POD1',
- },
- pod_2: {
- label: 'pod_2',
- type: 'text',
- value: 'POD2',
- },
+ expect(mapToDashboardViewModel(response).variables).toEqual([
+ {
+ label: 'pod',
+ name: 'pod',
+ type: 'text',
+ value: 'POD1',
},
- });
+ {
+ label: 'pod_2',
+ name: 'pod_2',
+ type: 'text',
+ value: 'POD2',
+ },
+ ]);
});
});
});
diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js
index 390cb2d8eac..de124b0313c 100644
--- a/spec/frontend/monitoring/store/variable_mapping_spec.js
+++ b/spec/frontend/monitoring/store/variable_mapping_spec.js
@@ -3,29 +3,31 @@ import {
mergeURLVariables,
optionsFromSeriesData,
} from '~/monitoring/stores/variable_mapping';
+import {
+ templatingVariablesExamples,
+ storeTextVariables,
+ storeCustomVariables,
+ storeMetricLabelValuesVariables,
+} from '../mock_data';
import * as urlUtils from '~/lib/utils/url_utility';
-import { mockTemplatingData, mockTemplatingDataResponses } from '../mock_data';
describe('Monitoring variable mapping', () => {
describe('parseTemplatingVariables', () => {
it.each`
- case | input | expected
- ${'Returns empty object for no dashboard input'} | ${{}} | ${{}}
- ${'Returns empty object for empty dashboard input'} | ${{ dashboard: {} }} | ${{}}
- ${'Returns empty object for empty templating prop'} | ${mockTemplatingData.emptyTemplatingProp} | ${{}}
- ${'Returns empty object for empty variables prop'} | ${mockTemplatingData.emptyVariablesProp} | ${{}}
- ${'Returns parsed object for simple text variable'} | ${mockTemplatingData.simpleText} | ${mockTemplatingDataResponses.simpleText}
- ${'Returns parsed object for advanced text variable'} | ${mockTemplatingData.advText} | ${mockTemplatingDataResponses.advText}
- ${'Returns parsed object for simple custom variable'} | ${mockTemplatingData.simpleCustom} | ${mockTemplatingDataResponses.simpleCustom}
- ${'Returns parsed object for advanced custom variable without options'} | ${mockTemplatingData.advCustomWithoutOpts} | ${mockTemplatingDataResponses.advCustomWithoutOpts}
- ${'Returns parsed object for advanced custom variable for option without text'} | ${mockTemplatingData.advCustomWithoutOptText} | ${mockTemplatingDataResponses.advCustomWithoutOptText}
- ${'Returns parsed object for advanced custom variable without type'} | ${mockTemplatingData.advCustomWithoutType} | ${{}}
- ${'Returns parsed object for advanced custom variable without label'} | ${mockTemplatingData.advCustomWithoutLabel} | ${mockTemplatingDataResponses.advCustomWithoutLabel}
- ${'Returns parsed object for simple and advanced custom variables'} | ${mockTemplatingData.simpleAndAdv} | ${mockTemplatingDataResponses.simpleAndAdv}
- ${'Returns parsed object for metricLabelValues'} | ${mockTemplatingData.metricLabelValues} | ${mockTemplatingDataResponses.metricLabelValues}
- ${'Returns parsed object for all variable types'} | ${mockTemplatingData.allVariableTypes} | ${mockTemplatingDataResponses.allVariableTypes}
- `('$case', ({ input, expected }) => {
- expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected);
+ case | input
+ ${'For undefined templating object'} | ${undefined}
+ ${'For empty templating object'} | ${{}}
+ `('$case, returns an empty array', ({ input }) => {
+ expect(parseTemplatingVariables(input)).toEqual([]);
+ });
+
+ it.each`
+ case | input | output
+ ${'Returns parsed object for text variables'} | ${templatingVariablesExamples.text} | ${storeTextVariables}
+ ${'Returns parsed object for custom variables'} | ${templatingVariablesExamples.custom} | ${storeCustomVariables}
+ ${'Returns parsed object for metric label value variables'} | ${templatingVariablesExamples.metricLabelValues} | ${storeMetricLabelValuesVariables}
+ `('$case, returns an empty array', ({ input, output }) => {
+ expect(parseTemplatingVariables(input)).toEqual(output);
});
});
@@ -41,7 +43,7 @@ describe('Monitoring variable mapping', () => {
it('returns empty object if variables are not defined in yml or URL', () => {
urlUtils.queryToObject.mockReturnValueOnce({});
- expect(mergeURLVariables({})).toEqual({});
+ expect(mergeURLVariables([])).toEqual([]);
});
it('returns empty object if variables are defined in URL but not in yml', () => {
@@ -50,18 +52,24 @@ describe('Monitoring variable mapping', () => {
'var-instance': 'localhost',
});
- expect(mergeURLVariables({})).toEqual({});
+ expect(mergeURLVariables([])).toEqual([]);
});
it('returns yml variables if variables defined in yml but not in the URL', () => {
urlUtils.queryToObject.mockReturnValueOnce({});
- const params = {
- env: 'one',
- instance: 'localhost',
- };
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ ];
- expect(mergeURLVariables(params)).toEqual(params);
+ expect(mergeURLVariables(variables)).toEqual(variables);
});
it('returns yml variables if variables defined in URL do not match with yml variables', () => {
@@ -69,13 +77,19 @@ describe('Monitoring variable mapping', () => {
'var-env': 'one',
'var-instance': 'localhost',
};
- const ymlParams = {
- pod: { value: 'one' },
- service: { value: 'database' },
- };
+ const variables = [
+ {
+ name: 'env',
+ value: 'one',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
urlUtils.queryToObject.mockReturnValueOnce(urlParams);
- expect(mergeURLVariables(ymlParams)).toEqual(ymlParams);
+ expect(mergeURLVariables(variables)).toEqual(variables);
});
it('returns merged yml and URL variables if there is some match', () => {
@@ -83,19 +97,29 @@ describe('Monitoring variable mapping', () => {
'var-env': 'one',
'var-instance': 'localhost:8080',
};
- const ymlParams = {
- instance: { value: 'localhost' },
- service: { value: 'database' },
- };
-
- const merged = {
- instance: { value: 'localhost:8080' },
- service: { value: 'database' },
- };
+ const variables = [
+ {
+ name: 'instance',
+ value: 'localhost',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ];
urlUtils.queryToObject.mockReturnValueOnce(urlParams);
- expect(mergeURLVariables(ymlParams)).toEqual(merged);
+ expect(mergeURLVariables(variables)).toEqual([
+ {
+ name: 'instance',
+ value: 'localhost:8080',
+ },
+ {
+ name: 'service',
+ value: 'database',
+ },
+ ]);
});
});
diff --git a/spec/frontend/monitoring/store_utils.js b/spec/frontend/monitoring/store_utils.js
index 740dbaaa2e3..6c8267e6a3c 100644
--- a/spec/frontend/monitoring/store_utils.js
+++ b/spec/frontend/monitoring/store_utils.js
@@ -35,12 +35,6 @@ export const setupStoreWithDashboard = store => {
);
};
-export const setupStoreWithVariable = store => {
- store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
- label1: 'pod',
- });
-};
-
export const setupStoreWithLinks = store => {
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`, {
...metricsDashboardPayload,
diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js
index 039cf275eea..1b4df286868 100644
--- a/spec/frontend/monitoring/utils_spec.js
+++ b/spec/frontend/monitoring/utils_spec.js
@@ -429,14 +429,41 @@ describe('monitoring/utils', () => {
describe('convertVariablesForURL', () => {
it.each`
- input | expected
- ${undefined} | ${{}}
- ${null} | ${{}}
- ${{}} | ${{}}
- ${{ env: { value: 'prod' } }} | ${{ 'var-env': 'prod' }}
- ${{ 'var-env': { value: 'prod' } }} | ${{ 'var-var-env': 'prod' }}
+ input | expected
+ ${[]} | ${{}}
+ ${[{ name: 'env', value: 'prod' }]} | ${{ 'var-env': 'prod' }}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${{ 'var-env1': 'prod' }}
+ ${[{ name: 'var-env', value: 'prod' }]} | ${{ 'var-var-env': 'prod' }}
`('convertVariablesForURL returns $expected with input $input', ({ input, expected }) => {
expect(monitoringUtils.convertVariablesForURL(input)).toEqual(expected);
});
});
+
+ describe('setCustomVariablesFromUrl', () => {
+ beforeEach(() => {
+ jest.spyOn(urlUtils, 'updateHistory');
+ });
+
+ afterEach(() => {
+ urlUtils.updateHistory.mockRestore();
+ });
+
+ it.each`
+ input | urlParams
+ ${[]} | ${''}
+ ${[{ name: 'env', value: 'prod' }]} | ${'?var-env=prod'}
+ ${[{ name: 'env1', value: 'prod' }, { name: 'env2', value: null }]} | ${'?var-env=prod&var-env1=prod'}
+ `(
+ 'setCustomVariablesFromUrl updates history with query "$urlParams" with input $input',
+ ({ input, urlParams }) => {
+ monitoringUtils.setCustomVariablesFromUrl(input);
+
+ expect(urlUtils.updateHistory).toHaveBeenCalledTimes(1);
+ expect(urlUtils.updateHistory).toHaveBeenCalledWith({
+ url: `http://localhost/${urlParams}`,
+ title: '',
+ });
+ },
+ );
+ });
});
diff --git a/spec/helpers/notify_helper_spec.rb b/spec/helpers/notify_helper_spec.rb
new file mode 100644
index 00000000000..5b2a06b11e9
--- /dev/null
+++ b/spec/helpers/notify_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe NotifyHelper do
+ include ActionView::Helpers::UrlHelper
+
+ describe 'merge_request_reference_link' do
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'returns link to merge request with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/merge_requests/#{merge_request.iid}"
+
+ expect(merge_request_reference_link(merge_request)).to eq(reference_link(merge_request, url))
+ end
+ end
+
+ describe 'issue_reference_link' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+
+ it 'returns link to issue with the text reference' do
+ url = "http://test.host/#{project.full_path}/-/issues/#{issue.iid}"
+
+ expect(issue_reference_link(issue)).to eq(reference_link(issue, url))
+ end
+ end
+
+ def reference_link(entity, url)
+ "<a href=\"#{url}\">#{entity.to_reference}</a>"
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index f9daff91871..bc2012e83bd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -270,4 +270,29 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected. to eq(true) }
end
end
+
+ describe '#dangling_build?' do
+ let(:project) { create(:project, :repository) }
+ let(:command) { described_class.new(project: project, source: source) }
+
+ subject { command.dangling_build? }
+
+ context 'when source is :webide' do
+ let(:source) { :webide }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source is :ondemand_dast_scan' do
+ let(:source) { :ondemand_dast_scan }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when source something else' do
+ let(:source) { :web }
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index 6adf2a59b82..42ec9ab6f5d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -6,7 +6,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
let(:project) { create(:project, ci_config_path: ci_config_path) }
let(:pipeline) { build(:ci_pipeline, project: project) }
let(:content) { nil }
- let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content) }
+ let(:source) { :push }
+ let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, content: content, source: source) }
subject { described_class.new(pipeline, command) }
@@ -143,6 +144,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content do
end
context 'when config is passed as a parameter' do
+ let(:source) { :ondemand_dast_scan }
let(:ci_config_path) { nil }
let(:content) do
<<~EOY
diff --git a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
index afae22eda20..7dd1a2e6924 100644
--- a/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
+++ b/spec/lib/gitlab/danger/sidekiq_queues_spec.rb
@@ -62,5 +62,21 @@ RSpec.describe Gitlab::Danger::SidekiqQueues do
expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive, :process_commit)
end
+
+ it 'ignores removed queues' do
+ old_queues = {
+ merge: { name: :merge, urgency: :low },
+ post_receive: { name: :post_receive, urgency: :high }
+ }
+
+ new_queues = {
+ post_receive: { name: :post_receive, urgency: :low }
+ }
+
+ allow(sidekiq_queues).to receive(:old_queues).and_return(old_queues)
+ allow(sidekiq_queues).to receive(:new_queues).and_return(new_queues)
+
+ expect(sidekiq_queues.changed_queue_names).to contain_exactly(:post_receive)
+ end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 9e613749bdf..7c1eb66b543 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -105,6 +105,7 @@ RSpec.describe Notify do
it 'contains a link to issue author' do
is_expected.to have_body_text(issue.author_name)
is_expected.to have_body_text 'created an issue'
+ is_expected.to have_link(issue.to_reference, href: project_issue_url(issue.project, issue))
end
it 'contains a link to the issue' do
@@ -467,6 +468,7 @@ RSpec.describe Notify do
is_expected.to have_body_text(status)
is_expected.to have_body_text(current_user_sanitized)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -497,6 +499,7 @@ RSpec.describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('merged')
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -534,6 +537,7 @@ RSpec.describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text('due to conflict.')
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
@@ -567,6 +571,7 @@ RSpec.describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text("#{push_user.name} pushed new commits")
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
+ is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index d71cd843a47..68f1a0f1ba1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -425,6 +425,73 @@ RSpec.describe API::MergeRequests do
end
end
+ context 'NOT params' do
+ let(:merge_request2) do
+ create(
+ :merge_request,
+ :simple,
+ milestone: milestone,
+ author: user,
+ assignees: [user],
+ merge_request_context_commits: [merge_request_context_commit],
+ source_project: project,
+ target_project: project,
+ source_branch: 'what',
+ title: "What",
+ created_at: base_time
+ )
+ end
+
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request2)
+ end
+
+ it 'returns merge requests without any of the labels given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { labels: ["#{label.title}, #{label2.title}"] } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(3)
+ json_response.each do |mr|
+ expect(mr['labels']).not_to include(label2.title, label.title)
+ end
+ end
+
+ it 'returns merge requests without any of the milestones given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { milestone: milestone.title } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(4)
+ json_response.each do |mr|
+ expect(mr['milestone']).not_to eq(milestone.title)
+ end
+ end
+
+ it 'returns merge requests without the author given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { author_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['author']['id']).not_to eq(user2.id)
+ end
+ end
+
+ it 'returns merge requests without the assignee given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { assignee_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['assignee']['id']).not_to eq(user2.id)
+ end
+ end
+ end
+
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
diff --git a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
index a0b62ad5194..5157574ea04 100644
--- a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb
@@ -28,23 +28,34 @@ RSpec.describe Ci::CreatePipelineService do
end
describe '#execute' do
- subject { service.execute(:web, content: content) }
+ context 'when source is a dangling build' do
+ subject { service.execute(:ondemand_dast_scan, content: content) }
- context 'parameter config content' do
- it 'creates a pipeline' do
- expect(subject).to be_persisted
- end
+ context 'parameter config content' do
+ it 'creates a pipeline' do
+ expect(subject).to be_persisted
+ end
- it 'creates builds with the correct names' do
- expect(subject.builds.pluck(:name)).to match_array %w[dast]
- end
+ it 'creates builds with the correct names' do
+ expect(subject.builds.pluck(:name)).to match_array %w[dast]
+ end
+
+ it 'creates stages with the correct names' do
+ expect(subject.stages.pluck(:name)).to match_array %w[dast]
+ end
- it 'creates stages with the correct names' do
- expect(subject.stages.pluck(:name)).to match_array %w[dast]
+ it 'sets the correct config source' do
+ expect(subject.config_source).to eq 'parameter_source'
+ end
end
+ end
+
+ context 'when source is not a dangling build' do
+ subject { service.execute(:web, content: content) }
- it 'sets the correct config source' do
- expect(subject.config_source).to eq 'parameter_source'
+ it 'raises an exception' do
+ klass = Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter::UnsupportedSourceError
+ expect { subject }.to raise_error(klass)
end
end
end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 6a9d8857b32..95ee6fe556c 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -87,6 +87,18 @@ RSpec.describe EventCreateService do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { ResourceStateEvent.count }
end
end
+
+ describe '#approve_mr' do
+ let(:merge_request) { create(:merge_request) }
+
+ it { expect(service.approve_mr(merge_request, user)).to be_truthy }
+
+ it 'creates new event' do
+ service.approve_mr(merge_request, user)
+
+ change { Event.approved_action.where(target: merge_request).count }.by(1)
+ end
+ end
end
describe 'Milestone' do
diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb
new file mode 100644
index 00000000000..68b9caa30ab
--- /dev/null
+++ b/spec/services/merge_requests/approval_service_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::ApprovalService do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let!(:todo) { create(:todo, user: user, project: project, target: merge_request) }
+
+ subject(:service) { described_class.new(project, user) }
+
+ context 'with invalid approval' do
+ before do
+ allow(merge_request.approvals).to receive(:new).and_return(double(save: false))
+ end
+
+ it 'does not create an approval note' do
+ expect(SystemNoteService).not_to receive(:approve_mr)
+
+ service.execute(merge_request)
+ end
+
+ it 'does not mark pending todos as done' do
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_pending
+ end
+ end
+
+ context 'with valid approval' do
+ it 'creates an approval note and marks pending todos as done' do
+ expect(SystemNoteService).to receive(:approve_mr).with(merge_request, user)
+ expect(merge_request.approvals).to receive(:reset)
+
+ service.execute(merge_request)
+
+ expect(todo.reload).to be_done
+ end
+
+ it 'creates approve MR event' do
+ expect_next_instance_of(EventCreateService) do |instance|
+ expect(instance).to receive(:approve_mr)
+ .with(merge_request, user)
+ end
+
+ service.execute(merge_request)
+ end
+
+ context 'with remaining approvals' do
+ it 'fires an approval webhook' do
+ expect(service).to receive(:execute_hooks).with(merge_request, 'approved')
+
+ service.execute(merge_request)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 2a1cb3e0585..4d265258449 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -661,4 +661,14 @@ RSpec.describe SystemNoteService do
described_class.design_discussion_added(discussion_note)
end
end
+
+ describe '.approve_mr' do
+ it 'calls MergeRequestsService' do
+ expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
+ expect(service).to receive(:approve_mr)
+ end
+
+ described_class.approve_mr(noteable, author)
+ end
+ end
end
diff --git a/spec/services/system_notes/merge_requests_service_spec.rb b/spec/services/system_notes/merge_requests_service_spec.rb
index 1c378123797..17155969a33 100644
--- a/spec/services/system_notes/merge_requests_service_spec.rb
+++ b/spec/services/system_notes/merge_requests_service_spec.rb
@@ -261,4 +261,18 @@ RSpec.describe ::SystemNotes::MergeRequestsService do
expect(subject.commit_id).to eq(commit_sha)
end
end
+
+ describe '#approve_mr' do
+ subject { described_class.new(noteable: noteable, project: project, author: author).approve_mr }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'approved' }
+ end
+
+ context 'when merge request approved' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "approved this merge request"
+ end
+ end
+ end
end
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index 2b8daa80ab4..07b6b98222f 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -23,12 +23,12 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
# We cannot use `let_it_be` here otherwise we get:
# Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
# The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
- let(:project2) do
+ let!(:project2) do
allow_gitaly_n_plus_1 do
fork_project(project1, user)
end
end
- let(:project3) do
+ let!(:project3) do
allow_gitaly_n_plus_1 do
fork_project(project1, user).tap do |project|
project.update!(archived: true)
@@ -45,6 +45,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
+ let!(:label) { create(:label, project: project1) }
+ let!(:label2) { create(:label, project: project1) }
+
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user,
source_project: project2, target_project: project1,
@@ -72,6 +75,9 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
title: '[WIP]')
end
+ let!(:label_link) { create(:label_link, label: label, target: merge_request2) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request3) }
+
before do
project1.add_maintainer(user)
project2.add_developer(user)