Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-08 21:09:13 +00:00
parent 383ec6808b
commit 7aa988fed2
58 changed files with 595 additions and 312 deletions

View file

@ -491,10 +491,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- ee/spec/graphql/mutations/issues/set_epic_spec.rb - ee/spec/graphql/mutations/issues/set_epic_spec.rb
- ee/spec/graphql/mutations/requirements_management/export_requirements_spec.rb - ee/spec/graphql/mutations/requirements_management/export_requirements_spec.rb
- ee/spec/graphql/mutations/vulnerabilities/create_external_issue_link_spec.rb - ee/spec/graphql/mutations/vulnerabilities/create_external_issue_link_spec.rb
- ee/spec/graphql/resolvers/ci/code_coverage_activities_resolver_spec.rb
- ee/spec/graphql/resolvers/epics_resolver_spec.rb
- ee/spec/graphql/resolvers/geo/geo_node_resolver_spec.rb
- ee/spec/graphql/resolvers/incident_management/oncall_shifts_resolver_spec.rb
- ee/spec/graphql/types/issue_type_spec.rb - ee/spec/graphql/types/issue_type_spec.rb
- ee/spec/helpers/ee/graph_helper_spec.rb - ee/spec/helpers/ee/graph_helper_spec.rb
- ee/spec/helpers/ee/issues_helper_spec.rb - ee/spec/helpers/ee/issues_helper_spec.rb
@ -801,8 +797,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb - spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
- spec/lib/gitlab/auth_spec.rb - spec/lib/gitlab/auth_spec.rb
- spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb - spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
- spec/lib/gitlab/checks/project_created_spec.rb
- spec/lib/gitlab/checks/project_moved_spec.rb
- spec/lib/gitlab/ci/build/policy/changes_spec.rb - spec/lib/gitlab/ci/build/policy/changes_spec.rb
- spec/lib/gitlab/ci/config/external/file/local_spec.rb - spec/lib/gitlab/ci/config/external/file/local_spec.rb
- spec/lib/gitlab/ci/config/external/file/project_spec.rb - spec/lib/gitlab/ci/config/external/file/project_spec.rb
@ -840,12 +834,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb - spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
- spec/lib/gitlab/github_import/milestone_finder_spec.rb - spec/lib/gitlab/github_import/milestone_finder_spec.rb
- spec/lib/gitlab/gl_repository/repo_type_spec.rb - spec/lib/gitlab/gl_repository/repo_type_spec.rb
- spec/lib/gitlab/graphql/batch_key_spec.rb
- spec/lib/gitlab/graphql/docs/renderer_spec.rb
- spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
- spec/lib/gitlab/graphql/markdown_field_spec.rb
- spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
- spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
- spec/lib/gitlab/group_search_results_spec.rb - spec/lib/gitlab/group_search_results_spec.rb
- spec/lib/gitlab/hook_data/issue_builder_spec.rb - spec/lib/gitlab/hook_data/issue_builder_spec.rb
- spec/lib/gitlab/hook_data/merge_request_builder_spec.rb - spec/lib/gitlab/hook_data/merge_request_builder_spec.rb

View file

@ -129,7 +129,7 @@ export default {
<template #help> <template #help>
{{ {{
s__( s__(
'JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below.', 'JiraService|Warning: All GitLab users that have access to this GitLab project are able to view all issues from the Jira project specified below.',
) )
}} }}
</template> </template>
@ -169,7 +169,7 @@ export default {
id="service_project_key" id="service_project_key"
v-model="projectKey" v-model="projectKey"
name="service[project_key]" name="service[project_key]"
:placeholder="s__('JiraService|e.g. AB')" :placeholder="s__('JiraService|For example, AB')"
:required="enableJiraIssues" :required="enableJiraIssues"
:state="validProjectKey" :state="validProjectKey"
:disabled="!enableJiraIssues" :disabled="!enableJiraIssues"

View file

@ -144,7 +144,7 @@ export default {
label-for="service[trigger]" label-for="service[trigger]"
:description=" :description="
s__( s__(
'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created.', 'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) is created.',
) )
" "
> >

View file

@ -121,6 +121,7 @@ export default {
:is="componentName(firstNote)" :is="componentName(firstNote)"
:note="componentData(firstNote)" :note="componentData(firstNote)"
:line="line || diffLine" :line="line || diffLine"
:discussion-file="discussion.diff_file"
:commit="commit" :commit="commit"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:show-reply-button="userCanReply" :show-reply-button="userCanReply"
@ -167,6 +168,7 @@ export default {
v-for="(note, index) in discussion.notes" v-for="(note, index) in discussion.notes"
:key="note.id" :key="note.id"
:note="componentData(note)" :note="componentData(note)"
:discussion-file="discussion.diff_file"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:line="diffLine" :line="diffLine"
:discussion-root="index === 0" :discussion-root="index === 0"

View file

@ -48,6 +48,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
discussionFile: {
type: Object,
required: false,
default: null,
},
helpPagePath: { helpPagePath: {
type: String, type: String,
required: false, required: false,
@ -167,12 +172,18 @@ export default {
return commentLineOptions(lines, this.commentLineStart, this.line.line_code); return commentLineOptions(lines, this.commentLineStart, this.line.line_code);
}, },
diffFile() { diffFile() {
let fileResolvedFromAvailableSource;
if (this.commentLineStart.line_code) { if (this.commentLineStart.line_code) {
const lineCode = this.commentLineStart.line_code.split('_')[0]; const lineCode = this.commentLineStart.line_code.split('_')[0];
return this.getDiffFileByHash(lineCode); fileResolvedFromAvailableSource = this.getDiffFileByHash(lineCode);
} }
return null; if (!fileResolvedFromAvailableSource && this.discussionFile) {
fileResolvedFromAvailableSource = this.discussionFile;
}
return fileResolvedFromAvailableSource || null;
}, },
}, },
created() { created() {

View file

@ -2,7 +2,7 @@
import { reportToSentry } from '../../utils'; import { reportToSentry } from '../../utils';
import LinkedGraphWrapper from '../graph_shared/linked_graph_wrapper.vue'; import LinkedGraphWrapper from '../graph_shared/linked_graph_wrapper.vue';
import LinksLayer from '../graph_shared/links_layer.vue'; import LinksLayer from '../graph_shared/links_layer.vue';
import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH } from './constants'; import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH, STAGE_VIEW } from './constants';
import LinkedPipelinesColumn from './linked_pipelines_column.vue'; import LinkedPipelinesColumn from './linked_pipelines_column.vue';
import StageColumnComponent from './stage_column_component.vue'; import StageColumnComponent from './stage_column_component.vue';
import { validateConfigPaths } from './utils'; import { validateConfigPaths } from './utils';
@ -25,11 +25,20 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
viewType: {
type: String,
required: true,
},
isLinkedPipeline: { isLinkedPipeline: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
pipelineLayers: {
type: Array,
required: false,
default: () => [],
},
type: { type: {
type: String, type: String,
required: false, required: false,
@ -63,8 +72,8 @@ export default {
downstreamPipelines() { downstreamPipelines() {
return this.hasDownstreamPipelines ? this.pipeline.downstream : []; return this.hasDownstreamPipelines ? this.pipeline.downstream : [];
}, },
graph() { layout() {
return this.pipeline.stages; return this.isStageView ? this.pipeline.stages : this.generateColumnsFromLayersList();
}, },
hasDownstreamPipelines() { hasDownstreamPipelines() {
return Boolean(this.pipeline?.downstream?.length > 0); return Boolean(this.pipeline?.downstream?.length > 0);
@ -72,12 +81,18 @@ export default {
hasUpstreamPipelines() { hasUpstreamPipelines() {
return Boolean(this.pipeline?.upstream?.length > 0); return Boolean(this.pipeline?.upstream?.length > 0);
}, },
isStageView() {
return this.viewType === STAGE_VIEW;
},
metricsConfig() { metricsConfig() {
return { return {
path: this.configPaths.metricsPath, path: this.configPaths.metricsPath,
collectMetrics: true, collectMetrics: true,
}; };
}, },
shouldHideLinks() {
return this.isStageView;
},
// The show downstream check prevents showing redundant linked columns // The show downstream check prevents showing redundant linked columns
showDownstreamPipelines() { showDownstreamPipelines() {
return ( return (
@ -101,6 +116,26 @@ export default {
this.getMeasurements(); this.getMeasurements();
}, },
methods: { methods: {
generateColumnsFromLayersList() {
return this.pipelineLayers.map((layers, idx) => {
/*
look up the groups in each layer,
then add each set of layer groups to a stage-like object
*/
const groups = layers.map((id) => {
const { stageIdx, groupIdx } = this.pipeline.stagesLookup[id];
return this.pipeline.stages?.[stageIdx]?.groups?.[groupIdx];
});
return {
name: '',
id: `layer-${idx}`,
status: { action: null },
groups: groups.filter(Boolean),
};
});
},
getMeasurements() { getMeasurements() {
this.measurements = { this.measurements = {
width: this.$refs[this.containerId].scrollWidth, width: this.$refs[this.containerId].scrollWidth,
@ -147,29 +182,31 @@ export default {
:linked-pipelines="upstreamPipelines" :linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')" :column-title="__('Upstream')"
:type="$options.pipelineTypeConstants.UPSTREAM" :type="$options.pipelineTypeConstants.UPSTREAM"
:view-type="viewType"
@error="onError" @error="onError"
/> />
</template> </template>
<template #main> <template #main>
<div :id="containerId" :ref="containerId"> <div :id="containerId" :ref="containerId">
<links-layer <links-layer
:pipeline-data="graph" :pipeline-data="layout"
:pipeline-id="pipeline.id" :pipeline-id="pipeline.id"
:container-id="containerId" :container-id="containerId"
:container-measurements="measurements" :container-measurements="measurements"
:highlighted-job="hoveredJobName" :highlighted-job="hoveredJobName"
:metrics-config="metricsConfig" :metrics-config="metricsConfig"
:never-show-links="true" :never-show-links="shouldHideLinks"
:view-type="viewType"
default-link-color="gl-stroke-transparent" default-link-color="gl-stroke-transparent"
@error="onError" @error="onError"
@highlightedJobsChange="updateHighlightedJobs" @highlightedJobsChange="updateHighlightedJobs"
> >
<stage-column-component <stage-column-component
v-for="stage in graph" v-for="column in layout"
:key="stage.name" :key="column.id || column.name"
:title="stage.name" :title="column.name"
:groups="stage.groups" :groups="column.groups"
:action="stage.status.action" :action="column.status.action"
:highlighted-jobs="highlightedJobs" :highlighted-jobs="highlightedJobs"
:job-hovered="hoveredJobName" :job-hovered="hoveredJobName"
:pipeline-expanded="pipelineExpanded" :pipeline-expanded="pipelineExpanded"
@ -189,6 +226,7 @@ export default {
:linked-pipelines="downstreamPipelines" :linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')" :column-title="__('Downstream')"
:type="$options.pipelineTypeConstants.DOWNSTREAM" :type="$options.pipelineTypeConstants.DOWNSTREAM"
:view-type="viewType"
@downstreamHovered="setJob" @downstreamHovered="setJob"
@pipelineExpandToggle="togglePipelineExpanded" @pipelineExpandToggle="togglePipelineExpanded"
@scrollContainer="slidePipelineContainer" @scrollContainer="slidePipelineContainer"

View file

@ -5,7 +5,8 @@ import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants'; import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import { reportToSentry } from '../../utils'; import { reportToSentry } from '../../utils';
import { IID_FAILURE, STAGE_VIEW } from './constants'; import { listByLayers } from '../parsing_utils';
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW } from './constants';
import PipelineGraph from './graph_component.vue'; import PipelineGraph from './graph_component.vue';
import GraphViewSelector from './graph_view_selector.vue'; import GraphViewSelector from './graph_view_selector.vue';
import { import {
@ -43,6 +44,7 @@ export default {
alertType: null, alertType: null,
currentViewType: STAGE_VIEW, currentViewType: STAGE_VIEW,
pipeline: null, pipeline: null,
pipelineLayers: null,
showAlert: false, showAlert: false,
}; };
}, },
@ -155,6 +157,13 @@ export default {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`); reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
}, },
methods: { methods: {
getPipelineLayers() {
if (this.currentViewType === LAYER_VIEW && !this.pipelineLayers) {
this.pipelineLayers = listByLayers(this.pipeline);
}
return this.pipelineLayers;
},
hideAlert() { hideAlert() {
this.showAlert = false; this.showAlert = false;
this.alertType = null; this.alertType = null;
@ -192,6 +201,8 @@ export default {
v-if="pipeline" v-if="pipeline"
:config-paths="configPaths" :config-paths="configPaths"
:pipeline="pipeline" :pipeline="pipeline"
:pipeline-layers="getPipelineLayers()"
:view-type="currentViewType"
@error="reportFailure" @error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph" @refreshPipelineGraph="refreshPipelineGraph"
/> />

View file

@ -55,16 +55,16 @@ export default {
</script> </script>
<template> <template>
<div class="gl-display-flex gl-justify-content-end gl-align-items-center gl-my-4"> <div class="gl-display-flex gl-align-items-center gl-my-4">
<span>{{ $options.i18n.labelText }}</span> <span>{{ $options.i18n.labelText }}</span>
<gl-dropdown class="gl-ml-4" :right="true"> <gl-dropdown class="gl-ml-4">
<template #button-content> <template #button-content>
<gl-sprintf :message="currentDropdownText"> <gl-sprintf :message="currentDropdownText">
<template #code="{ content }"> <template #code="{ content }">
<code> {{ content }} </code> <code> {{ content }} </code>
</template> </template>
</gl-sprintf> </gl-sprintf>
<gl-icon class="gl-px-2" name="angle-down" :size="18" /> <gl-icon class="gl-px-2" name="angle-down" :size="16" />
</template> </template>
<gl-dropdown-item <gl-dropdown-item
v-for="view in $options.views" v-for="view in $options.views"

View file

@ -2,7 +2,8 @@
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { LOAD_FAILURE } from '../../constants'; import { LOAD_FAILURE } from '../../constants';
import { reportToSentry } from '../../utils'; import { reportToSentry } from '../../utils';
import { ONE_COL_WIDTH, UPSTREAM } from './constants'; import { listByLayers } from '../parsing_utils';
import { ONE_COL_WIDTH, UPSTREAM, LAYER_VIEW } from './constants';
import LinkedPipeline from './linked_pipeline.vue'; import LinkedPipeline from './linked_pipeline.vue';
import { import {
getQueryHeaders, getQueryHeaders,
@ -35,11 +36,16 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
viewType: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
currentPipeline: null, currentPipeline: null,
loadingPipelineId: null, loadingPipelineId: null,
pipelineLayers: {},
pipelineExpanded: false, pipelineExpanded: false,
}; };
}, },
@ -123,6 +129,13 @@ export default {
toggleQueryPollingByVisibility(this.$apollo.queries.currentPipeline); toggleQueryPollingByVisibility(this.$apollo.queries.currentPipeline);
}, },
getPipelineLayers(id) {
if (this.viewType === LAYER_VIEW && !this.pipelineLayers[id]) {
this.pipelineLayers[id] = listByLayers(this.currentPipeline);
}
return this.pipelineLayers[id];
},
isExpanded(id) { isExpanded(id) {
return Boolean(this.currentPipeline?.id && id === this.currentPipeline.id); return Boolean(this.currentPipeline?.id && id === this.currentPipeline.id);
}, },
@ -203,7 +216,9 @@ export default {
class="d-inline-block gl-mt-n2" class="d-inline-block gl-mt-n2"
:config-paths="configPaths" :config-paths="configPaths"
:pipeline="currentPipeline" :pipeline="currentPipeline"
:pipeline-layers="getPipelineLayers(pipeline.id)"
:is-linked-pipeline="true" :is-linked-pipeline="true"
:view-type="viewType"
/> />
</div> </div>
</li> </li>

View file

@ -1,6 +1,6 @@
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { unwrapStagesWithNeeds } from '../unwrapping_utils'; import { unwrapStagesWithNeedsAndLookup } from '../unwrapping_utils';
const addMulti = (mainPipelineProjectPath, linkedPipeline) => { const addMulti = (mainPipelineProjectPath, linkedPipeline) => {
return { return {
@ -86,12 +86,13 @@ const unwrapPipelineData = (mainPipelineProjectPath, data) => {
stages: { nodes: stages }, stages: { nodes: stages },
} = pipeline; } = pipeline;
const nodes = unwrapStagesWithNeeds(stages); const { stages: updatedStages, lookup } = unwrapStagesWithNeedsAndLookup(stages);
return { return {
...pipeline, ...pipeline,
id: getIdFromGraphQLId(pipeline.id), id: getIdFromGraphQLId(pipeline.id),
stages: nodes, stages: updatedStages,
stagesLookup: lookup,
upstream: upstream upstream: upstream
? [upstream].map(addMulti.bind(null, mainPipelineProjectPath)).map(transformId) ? [upstream].map(addMulti.bind(null, mainPipelineProjectPath)).map(transformId)
: [], : [],

View file

@ -11,6 +11,7 @@ import {
import { performanceMarkAndMeasure } from '~/performance/utils'; import { performanceMarkAndMeasure } from '~/performance/utils';
import { DRAW_FAILURE } from '../../constants'; import { DRAW_FAILURE } from '../../constants';
import { createJobsHash, generateJobNeedsDict, reportToSentry } from '../../utils'; import { createJobsHash, generateJobNeedsDict, reportToSentry } from '../../utils';
import { STAGE_VIEW } from '../graph/constants';
import { parseData } from '../parsing_utils'; import { parseData } from '../parsing_utils';
import { reportPerformance } from './api'; import { reportPerformance } from './api';
import { generateLinksData } from './drawing_utils'; import { generateLinksData } from './drawing_utils';
@ -54,11 +55,17 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
viewType: {
type: String,
required: false,
default: STAGE_VIEW,
},
}, },
data() { data() {
return { return {
links: [], links: [],
needsObject: null, needsObject: null,
parsedData: {},
}; };
}, },
computed: { computed: {
@ -108,6 +115,15 @@ export default {
highlightedJobs(jobs) { highlightedJobs(jobs) {
this.$emit('highlightedJobsChange', jobs); this.$emit('highlightedJobsChange', jobs);
}, },
viewType() {
/*
We need to wait a tick so that the layout reflows
before the links refresh.
*/
this.$nextTick(() => {
this.refreshLinks();
});
},
}, },
errorCaptured(err, _vm, info) { errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`); reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
@ -166,14 +182,17 @@ export default {
this.beginPerfMeasure(); this.beginPerfMeasure();
try { try {
const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups); const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs); this.parsedData = parseData(arrayOfJobs);
this.links = generateLinksData(parsedData, this.containerId, `-${this.pipelineId}`); this.refreshLinks();
} catch (err) { } catch (err) {
this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false }); this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false });
reportToSentry(this.$options.name, err); reportToSentry(this.$options.name, err);
} }
this.finishPerfMeasureAndSend(); this.finishPerfMeasureAndSend();
}, },
refreshLinks() {
this.links = generateLinksData(this.parsedData, this.containerId, `-${this.pipelineId}`);
},
getLinkClasses(link) { getLinkClasses(link) {
return [ return [
this.isLinkHighlighted(link.ref) ? 'gl-stroke-blue-400' : this.defaultLinkColor, this.isLinkHighlighted(link.ref) ? 'gl-stroke-blue-400' : this.defaultLinkColor,

View file

@ -1,4 +1,5 @@
import { uniqWith, isEqual } from 'lodash'; import { uniqWith, isEqual } from 'lodash';
import { createSankey } from './dag/drawing_utils';
/* /*
The following functions are the main engine in transforming the data as The following functions are the main engine in transforming the data as
@ -144,3 +145,28 @@ export const getMaxNodes = (nodes) => {
export const removeOrphanNodes = (sankeyfiedNodes) => { export const removeOrphanNodes = (sankeyfiedNodes) => {
return sankeyfiedNodes.filter((node) => node.sourceLinks.length || node.targetLinks.length); return sankeyfiedNodes.filter((node) => node.sourceLinks.length || node.targetLinks.length);
}; };
/*
This utility accepts unwrapped pipeline data in the format returned from
our standard pipeline GraphQL query and returns a list of names by layer
for the layer view. It can be combined with the stageLookup on the pipeline
to generate columns by layer.
*/
export const listByLayers = ({ stages }) => {
const arrayOfJobs = stages.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs);
const dataWithLayers = createSankey()(parsedData);
return dataWithLayers.nodes.reduce((acc, { layer, name }) => {
/* sort groups by layer */
if (!acc[layer]) {
acc[layer] = [];
}
acc[layer].push(name);
return acc;
}, []);
};

View file

@ -1,11 +1,11 @@
import { reportToSentry } from '../utils'; import { reportToSentry } from '../utils';
const unwrapGroups = (stages) => { const unwrapGroups = (stages) => {
return stages.map((stage) => { return stages.map((stage, idx) => {
const { const {
groups: { nodes: groups }, groups: { nodes: groups },
} = stage; } = stage;
return { ...stage, groups }; return { node: { ...stage, groups }, lookup: { stageIdx: idx } };
}); });
}; };
@ -23,20 +23,34 @@ const unwrapJobWithNeeds = (denodedJobArray) => {
return unwrapNodesWithName(denodedJobArray, 'needs'); return unwrapNodesWithName(denodedJobArray, 'needs');
}; };
const unwrapStagesWithNeeds = (denodedStages) => { const unwrapStagesWithNeedsAndLookup = (denodedStages) => {
const unwrappedNestedGroups = unwrapGroups(denodedStages); const unwrappedNestedGroups = unwrapGroups(denodedStages);
const nodes = unwrappedNestedGroups.map((node) => { const lookupMap = {};
const nodes = unwrappedNestedGroups.map(({ node, lookup }) => {
const { groups } = node; const { groups } = node;
const groupsWithJobs = groups.map((group) => { const groupsWithJobs = groups.map((group, idx) => {
const jobs = unwrapJobWithNeeds(group.jobs.nodes); const jobs = unwrapJobWithNeeds(group.jobs.nodes);
lookupMap[group.name] = { ...lookup, groupIdx: idx };
return { ...group, jobs }; return { ...group, jobs };
}); });
return { ...node, groups: groupsWithJobs }; return { ...node, groups: groupsWithJobs };
}); });
return nodes; return { stages: nodes, lookup: lookupMap };
}; };
export { unwrapGroups, unwrapNodesWithName, unwrapJobWithNeeds, unwrapStagesWithNeeds }; const unwrapStagesWithNeeds = (denodedStages) => {
return unwrapStagesWithNeedsAndLookup(denodedStages).stages;
};
export {
unwrapGroups,
unwrapJobWithNeeds,
unwrapNodesWithName,
unwrapStagesWithNeeds,
unwrapStagesWithNeedsAndLookup,
};

View file

@ -25,32 +25,3 @@
background-color: $red-500; background-color: $red-500;
} }
} }
.runner {
.btn {
padding: 1px 6px;
}
h4 {
font-weight: $gl-font-weight-normal;
}
}
.admin-runner-btn-group-cell {
min-width: 150px;
.btn-sm {
padding: 4px 9px;
}
.btn-default {
color: $gl-text-color-secondary;
}
}
@include media-breakpoint-down(md) {
.runners-content {
width: 100%;
overflow: auto;
}
}

View file

@ -116,7 +116,7 @@ class JiraService < IssueTrackerService
end end
def description def description
s_('JiraService|Jira issue tracker') s_('JiraService|Track issues in Jira')
end end
def self.to_param def self.to_param
@ -130,19 +130,20 @@ class JiraService < IssueTrackerService
name: 'url', name: 'url',
title: s_('JiraService|Web URL'), title: s_('JiraService|Web URL'),
placeholder: 'https://jira.example.com', placeholder: 'https://jira.example.com',
help: s_('JiraService|Base URL of the Jira instance.'),
required: true required: true
}, },
{ {
type: 'text', type: 'text',
name: 'api_url', name: 'api_url',
title: s_('JiraService|Jira API URL'), title: s_('JiraService|Jira API URL'),
placeholder: s_('JiraService|If different from Web URL') help: s_('JiraService|If different from Web URL.')
}, },
{ {
type: 'text', type: 'text',
name: 'username', name: 'username',
title: s_('JiraService|Username or Email'), title: s_('JiraService|Username or Email'),
placeholder: s_('JiraService|Use a username for server version and an email for cloud version'), help: s_('JiraService|Use a username for server version and an email for cloud version.'),
required: true required: true
}, },
{ {
@ -150,8 +151,8 @@ class JiraService < IssueTrackerService
name: 'password', name: 'password',
title: s_('JiraService|Password or API token'), title: s_('JiraService|Password or API token'),
non_empty_password_title: s_('JiraService|Enter new password or API token'), non_empty_password_title: s_('JiraService|Enter new password or API token'),
non_empty_password_help: s_('JiraService|Leave blank to use your current password or API token'), non_empty_password_help: s_('JiraService|Leave blank to use your current password or API token.'),
placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), help: s_('JiraService|Use a password for server version and an API token for cloud version.'),
required: true required: true
} }
] ]

View file

@ -6,7 +6,7 @@
.form-check .form-check
= f.check_box :authorized_keys_enabled, class: 'form-check-input' = f.check_box :authorized_keys_enabled, class: 'form-check-input'
= f.label :authorized_keys_enabled, class: 'form-check-label' do = f.label :authorized_keys_enabled, class: 'form-check-label' do
Write to "authorized_keys" file = _('Write to "authorized_keys" file')
.form-text.text-muted .form-text.text-muted
By default, we write to the "authorized_keys" file to support Git By default, we write to the "authorized_keys" file to support Git
over SSH without additional configuration. GitLab can be optimized over SSH without additional configuration. GitLab can be optimized
@ -31,4 +31,4 @@
.form-text.text-muted .form-text.text-muted
= _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.') = _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.')
= f.submit 'Save changes', class: "gl-button btn btn-confirm" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -118,7 +118,7 @@
= _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
- if @runners.any? - if @runners.any?
.runners-content.content-list .content-list{ data: { testid: 'runners-table' } }
.table-holder .table-holder
.gl-responsive-table-row.table-row-header{ role: 'row' } .gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-10{ role: 'rowheader' }= _('Type/State') .table-section.section-10{ role: 'rowheader' }= _('Type/State')

View file

@ -82,7 +82,7 @@
- if @group_runners.any? - if @group_runners.any?
.runners-content.content-list .content-list{ data: { testid: 'runners-table' } }
.table-holder .table-holder
.gl-responsive-table-row.table-row-header{ role: 'row' } .gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-10{ role: 'rowheader' }= _('Type/State') .table-section.section-10{ role: 'rowheader' }= _('Type/State')

View file

@ -1,3 +1,3 @@
.file-content.code .file-content.code
.nothing-here-block .nothing-here-block
Empty file = _("Empty file")

View file

@ -1,5 +1,5 @@
%li.runner{ id: dom_id(runner) } %li.runner{ id: dom_id(runner) }
%h4 %h4.gl-font-weight-normal
= runner_status_icon(runner) = runner_status_icon(runner)
- if @project_runners.include?(runner) - if @project_runners.include?(runner)
@ -9,9 +9,9 @@
%span.has-tooltip{ title: _('Locked to current projects') } %span.has-tooltip{ title: _('Locked to current projects') }
= sprite_icon('lock') = sprite_icon('lock')
%small.edit-runner = link_to edit_project_runner_path(@project, runner), class: 'btn gl-button btn-sm btn-icon', data: { testid: 'edit-runner-link' } do
= link_to edit_project_runner_path(@project, runner), class: 'btn gl-button btn-edit' do = sprite_icon('pencil')
= sprite_icon('pencil', css_class: 'gl-my-2')
- else - else
%span.commit-sha %span.commit-sha
= runner.short_sha = runner.short_sha

View file

@ -0,0 +1,5 @@
---
title: Externalize strings in _performance.html.haml
merge_request: 58016
author: nuwe1
type: other

View file

@ -0,0 +1,5 @@
---
title: Externalizes strings in viewers/_empty.html.haml
merge_request: 58451
author: nuwe1
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/checks
merge_request: 58248
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/graphql
merge_request: 58261
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Update Jira plugin UI copy
merge_request: 57793
author: Russell Dickenson rdickenson@gitlab.com
type: other

View file

@ -0,0 +1,6 @@
---
title: Fill in all placeholder values in the apply suggestion commit message placeholder
text
merge_request: 58136
author:
type: other

View file

@ -57,7 +57,7 @@ functions available. Consider these best practices when creating ChatOps jobs:
in the project, the job runs. The job itself can use existing in the project, the job runs. The job itself can use existing
[CI/CD variables](../variables/README.md#predefined-cicd-variables) like [CI/CD variables](../variables/README.md#predefined-cicd-variables) like
`GITLAB_USER_ID` to perform additional rights validation, but `GITLAB_USER_ID` to perform additional rights validation, but
these variables can be [overridden](../variables/README.md#priority-of-cicd-variables). these variables can be [overridden](../variables/README.md#cicd-variable-precedence).
### Controlling the ChatOps reply ### Controlling the ChatOps reply

View file

@ -690,7 +690,7 @@ with `review/` would have that variable available.
Some GitLab features can behave differently for each environment. Some GitLab features can behave differently for each environment.
For example, you can For example, you can
[create a secret variable to be injected only into a production environment](../variables/README.md#limit-the-environment-scopes-of-cicd-variables). [create a project CI/CD variable to be injected only into a production environment](../variables/README.md#limit-the-environment-scope-of-a-cicd-variable).
In most cases, these features use the _environment specs_ mechanism, which offers In most cases, these features use the _environment specs_ mechanism, which offers
an efficient way to implement scoping in each environment group. an efficient way to implement scoping in each environment group.

View file

@ -220,7 +220,7 @@ the ones defined in the upstream project take precedence.
#### With variable inheritance #### With variable inheritance
You can pass variables to a downstream pipeline with [`dotenv` variable inheritance](variables/README.md#inherit-cicd-variables) and [cross project artifact downloads](yaml/README.md#cross-project-artifact-downloads-with-needs). You can pass variables to a downstream pipeline with [`dotenv` variable inheritance](variables/README.md#pass-an-environment-variable-to-another-job) and [cross project artifact downloads](yaml/README.md#cross-project-artifact-downloads-with-needs).
In the upstream pipeline: In the upstream pipeline:

View file

@ -255,7 +255,7 @@ curl --request POST \
"https://gitlab.example.com/api/v4/projects/9/trigger/pipeline" "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
``` ```
Trigger variables have the [highest priority](../variables/README.md#priority-of-cicd-variables) Trigger variables have the [highest priority](../variables/README.md#cicd-variable-precedence)
of all types of variables. of all types of variables.
## Using cron to trigger nightly pipelines ## Using cron to trigger nightly pipelines

View file

@ -66,6 +66,11 @@ or have them [prefilled in manual pipelines](../pipelines/index.md#prefill-varia
There are two types of variables: [`File` or `Variable`](#cicd-variable-types). There are two types of variables: [`File` or `Variable`](#cicd-variable-types).
Variable names are limited by the [shell the runner uses](https://docs.gitlab.com/runner/shells/index.html)
to execute scripts. Each shell has its own set of reserved variable names.
Make sure each variable is defined for the [scope you want to use it in](where_variables_can_be_used.md).
### Create a custom CI/CD variable in the `.gitlab-ci.yml` file ### Create a custom CI/CD variable in the `.gitlab-ci.yml` file
To create a custom variable in the [`.gitlab-ci.yml`](../yaml/README.md#variables) file, To create a custom variable in the [`.gitlab-ci.yml`](../yaml/README.md#variables) file,
@ -467,16 +472,23 @@ export GITLAB_USER_ID="42"
... ...
``` ```
## Inherit CI/CD variables ## Pass an environment variable to another job
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22638) in GitLab 13.0 behind a disabled [feature flag](../../administration/feature_flags.md): `ci_dependency_variables`. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/22638) in GitLab 13.0.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/217834) in GitLab 13.1. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/217834) in GitLab 13.1.
You can inherit CI/CD variables from dependent jobs. You can pass environment variables from one job to another job in a later stage.
These variables cannot be used as CI/CD variables to configure a pipeline, but
they can be used in job scripts.
This feature makes use of the [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv) report feature. 1. In the job script, save the variable as a `.env` file.
1. Save the `.env` file as an [`artifacts:reports:dotenv`](../pipelines/job_artifacts.md#artifactsreportsdotenv)
artifact.
1. Set a job in a later stage to receive the artifact by using the [`dependencies`](../yaml/README.md#dependencies)
or the [`needs`](../yaml/README.md#artifact-downloads-with-needs) keywords.
1. The later job can then [use the variable in scripts](#use-cicd-variables-in-job-scripts).
Example with [`dependencies`](../yaml/README.md#dependencies) keyword. For example, with the [`dependencies`](../yaml/README.md#dependencies) keyword:
```yaml ```yaml
build: build:
@ -490,12 +502,12 @@ build:
deploy: deploy:
stage: deploy stage: deploy
script: script:
- echo $BUILD_VERSION # => hello - echo $BUILD_VERSION # Output is: 'hello'
dependencies: dependencies:
- build - build
``` ```
Example with the [`needs`](../yaml/README.md#artifact-downloads-with-needs) keyword: For example, with the [`needs`](../yaml/README.md#artifact-downloads-with-needs) keyword:
```yaml ```yaml
build: build:
@ -509,131 +521,113 @@ build:
deploy: deploy:
stage: deploy stage: deploy
script: script:
- echo $BUILD_VERSION # => hello - echo $BUILD_VERSION # Output is: 'hello'
needs: needs:
- job: build - job: build
artifacts: true artifacts: true
``` ```
## Priority of CI/CD variables ## CI/CD variable precedence
Variables of different types can take precedence over other You can use CI/CD variables with the same name in different places, but the values
variables, depending on where they are defined. can overwrite each other. The type of variable and where they are defined determines
which variables take precedence.
The order of precedence for variables is (from highest to lowest): The order of precedence for variables is (from highest to lowest):
1. [Trigger variables](../triggers/README.md#making-use-of-trigger-variables), [scheduled pipeline variables](../pipelines/schedules.md#using-variables), 1. [Trigger variables](../triggers/README.md#making-use-of-trigger-variables),
and [manual pipeline run variables](#override-a-variable-by-manually-running-a-pipeline). [scheduled pipeline variables](../pipelines/schedules.md#using-variables),
1. Project-level [variables](#custom-cicd-variables) or [protected variables](#protect-a-cicd-variable). and [manual pipeline run variables](#override-a-variable-when-running-a-pipeline-manually).
1. Group-level [variables](#group-cicd-variables) or [protected variables](#protect-a-cicd-variable). 1. Project [variables](#custom-cicd-variables).
1. Instance-level [variables](#instance-cicd-variables) or [protected variables](#protect-a-cicd-variable). 1. Group [variables](#group-cicd-variables).
1. [Inherited CI/CD variables](#inherit-cicd-variables). 1. Instance [variables](#instance-cicd-variables).
1. YAML-defined [job-level variables](../yaml/README.md#variables). 1. [Inherited variables](#pass-an-environment-variable-to-another-job).
1. YAML-defined [global variables](../yaml/README.md#variables). 1. Variables defined in jobs in the `.gitlab-ci.yml` file.
1. Variables defined outside of jobs (globally) in the `.gitlab-ci.yml` file.
1. [Deployment variables](#deployment-variables). 1. [Deployment variables](#deployment-variables).
1. [Predefined CI/CD variables](predefined_variables.md). 1. [Predefined variables](predefined_variables.md).
For example, if you define: In the following example, when the script in `job1` executes, the value of `API_TOKEN` is `secure`.
Variables defined in jobs have a higher precedence than variables defined globally.
- `API_TOKEN=secure` as a project variable. ```yaml
- `API_TOKEN=yaml` in your `.gitlab-ci.yml`. variables:
API_TOKEN: "default"
`API_TOKEN` takes the value `secure` as the project job1:
variables take precedence over those defined in `.gitlab-ci.yml`. variables:
API_TOKEN: "secure"
script:
- echo "The variable value is $API_TOKEN"
```
## Unsupported variables ## Override a defined CI/CD variable
Variable names are limited by the underlying shell used to execute scripts (see [available shells](https://docs.gitlab.com/runner/shells/index.html). You can override the value of a variable when you:
Each shell has its own unique set of reserved variable names.
Keep in mind the [scope of CI/CD variables](where_variables_can_be_used.md) to ensure a variable is defined in the scope in which you wish to use it.
## Where variables can be used 1. [Run a pipeline manually](#override-a-variable-when-running-a-pipeline-manually) in the UI.
1. Create a pipeline by using [the API](../../api/pipelines.md#create-a-new-pipeline).
1. Run a job manually in the UI.
1. Use [push options](../../user/project/push_options.md#push-options-for-gitlab-cicd).
1. Trigger a pipeline by using [the API](../triggers/README.md#making-use-of-trigger-variables).
1. Pass variables to a [downstream pipeline](../multi_project_pipelines.md#passing-cicd-variables-to-a-downstream-pipeline).
[This section](where_variables_can_be_used.md) describes where and how the different types of variables can be used. The pipeline variables declared in these events take [priority over other variables](#cicd-variable-precedence).
## Advanced use ### Override a variable when running a pipeline manually
### Limit the environment scopes of CI/CD variables You can override the value of a CI/CD variable when you
[run a pipeline manually](../pipelines/index.md#run-a-pipeline-manually).
1. Go to your project's **CI/CD > Pipelines** and select **Run pipeline**.
1. Choose the branch you want to run the pipeline for.
1. Input the variable and its value in the UI.
### Restrict who can override variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/295234) in GitLab 13.8.
You can grant permission to override variables to [maintainers](../../user/permissions.md#project-features) only. When other users try to run a pipeline
with overridden variables, they receive the `Insufficient permissions to set pipeline variables`
error message.
If you [store your CI/CD configurations in a different repository](../../ci/pipelines/settings.md#custom-cicd-configuration-path),
use this setting for control over the environment the pipeline runs in.
You can enable this feature by using [the projects API](../../api/projects.md#edit-project)
to enable the `restrict_user_defined_variables` setting. The setting is `disabled` by default.
## Limit the environment scope of a CI/CD variable
You can limit the environment scope of a variable by You can limit the environment scope of a variable by
[defining which environments](../environments/index.md) it can be available for. [defining which environments](../environments/index.md) it can be available for.
To learn more about scoping environments, see [Scoping environments with specs](../environments/index.md#scoping-environments-with-specs). To learn more about scoping environments, see [Scoping environments with specs](../environments/index.md#scoping-environments-with-specs).
### Deployment variables ## Deployment variables
[Integrations](../../user/project/integrations/overview.md) that are Integrations that are responsible for deployment configuration can define their own
responsible for deployment configuration may define their own variables that variables that are set in the build environment. These variables are only defined
are set in the build environment. These variables are only defined for for [deployment jobs](../environments/index.md).
[deployment jobs](../environments/index.md). Please consult the documentation of
the integrations that you are using to learn which variables they define.
An example integration that defines deployment variables is the For example, the [Kubernetes integration](../../user/project/clusters/index.md#deployment-variables)
[Kubernetes integration](../../user/project/clusters/index.md#deployment-variables). defines deployment variables that you can use with the integration.
### Auto DevOps environment variables The [documentation for each integration](../../user/project/integrations/overview.md)
explains if the integration has any deployment variables available.
## Auto DevOps environment variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/49056) in GitLab 11.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/49056) in GitLab 11.7.
You can configure [Auto DevOps](../../topics/autodevops/index.md) to You can configure [Auto DevOps](../../topics/autodevops/index.md) to pass CI/CD variables
pass CI/CD variables to the running application by prefixing the key of the to a running application.
variable with `K8S_SECRET_`.
These [prefixed variables](../../topics/autodevops/customize.md#application-secret-variables) are To make a CI/CD variable available as an environment variable in the running application's container,
then available as environment variables on the running application [prefix the variable key](../../topics/autodevops/customize.md#application-secret-variables)
container. with `K8S_SECRET_`.
WARNING: CI/CD variables with multi-line values are not supported.
Variables with multi-line values are not supported due to
limitations with the Auto DevOps scripting environment.
### When you can override variables
You can override the value of a variable when:
1. [Manually running](#override-a-variable-by-manually-running-a-pipeline) pipelines in the UI.
1. Manually creating pipelines [via API](../../api/pipelines.md#create-a-new-pipeline).
1. Manually playing a job via the UI.
1. Using [push options](../../user/project/push_options.md#push-options-for-gitlab-cicd).
1. Manually triggering pipelines with [the API](../triggers/README.md#making-use-of-trigger-variables).
1. Passing variables to a [downstream pipeline](../multi_project_pipelines.md#passing-cicd-variables-to-a-downstream-pipeline).
These pipeline variables declared in these events take [priority over other variables](#priority-of-cicd-variables).
#### Restrict who can override variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/295234) in GitLab 13.8.
To allow only users with Maintainer role to set these variables, you can use
[the API](../../api/projects.md#edit-project) to enable the project setting `restrict_user_defined_variables`.
When a user without Maintainer role tries to run a pipeline with overridden
variables, an `Insufficient permissions to set pipeline variables` error occurs.
The setting is `disabled` by default.
If you [store your CI/CD configurations in a different repository](../../ci/pipelines/settings.md#custom-cicd-configuration-path),
use this setting for strict control over all aspects of the environment
the pipeline runs in.
#### Override a variable by manually running a pipeline
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44059) in GitLab 10.8.
You can override the value of a current variable by
[running a pipeline manually](../pipelines/index.md#run-a-pipeline-manually).
For instance, suppose you added a custom variable named `$TEST`
and you want to override it in a manual pipeline.
Go to your project's **CI/CD > Pipelines** and select **Run pipeline**.
Choose the branch you want to run the pipeline for, then add a variable and its value in the UI:
![Override variable value](img/override_variable_manual_pipeline.png)
The runner overrides the value previously set and uses the custom
value for this specific pipeline.
![Manually overridden variable output](img/override_value_via_manual_pipeline_output.png)
## CI/CD variable expressions ## CI/CD variable expressions
@ -828,44 +822,22 @@ testvariable:
> Introduced in GitLab Runner 1.7. > Introduced in GitLab Runner 1.7.
WARNING: WARNING:
Enabling debug tracing can have severe security implications. The Debug logging can be a serious security risk. The output contains the content of
output **will** contain the content of all your variables and any other all variables and other secrets available to the job. The output is uploaded to the
secrets! The output **will** be uploaded to the GitLab server and made visible GitLab server and visible in job logs.
in job logs!
By default, the runner hides most of the details of what it is doing when You can use debug logging to help troubleshoot problems with pipeline configuration
processing a job. This behavior keeps job logs short, and prevents secrets or job scripts. Debug logging exposes job execution details that are usually hidden
from being leaked into the log unless your script writes them to the screen. by the runner and makes job logs more verbose. It also exposes all variables and secrets
available to the job.
If a job isn't working as expected, this can make the problem difficult to Before you enable debug logging, make sure only [team members](../../user/permissions.md#project-features)
investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. can view job logs. You should also [delete job logs](../jobs/index.md#view-jobs-in-a-pipeline)
Available on GitLab Runner v1.7+, this feature enables the shell's execution log. This results in a verbose job log listing all commands that were run, variables that were set, and so on. with debug output before you make logs public again.
Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should
also [erase](../jobs/index.md#view-jobs-in-a-pipeline) all generated job logs
before making them visible again.
### Restricted access to debug logging
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213159) in GitLab 13.7.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292661) in GitLab 13.8.
With restricted access to debug logging, only users with
[developer or higher permissions](../../user/permissions.md#project-members-permissions)
can view job logs when debug logging is enabled with a variable in:
- The [`.gitlab-ci.yml` file](#create-a-custom-cicd-variable-in-the-gitlab-ciyml-file).
- The CI/CD variables set in the GitLab UI.
WARNING:
If you add `CI_DEBUG_TRACE` as a local variable to your runners, debug logs are visible
to all users with access to job logs. The permission levels are not checked by Runner,
so you should make use of the variable in GitLab only.
### Enable Debug logging ### Enable Debug logging
To enable debug logs (traces), set the `CI_DEBUG_TRACE` variable to `true`: To enable debug logging (tracing), set the `CI_DEBUG_TRACE` variable to `true`:
```yaml ```yaml
job_name: job_name:
@ -873,11 +845,10 @@ job_name:
CI_DEBUG_TRACE: "true" CI_DEBUG_TRACE: "true"
``` ```
Example truncated output with `CI_DEBUG_TRACE` set to `true`: Example output (truncated):
```shell ```shell
... ...
export CI_SERVER_TLS_CA_FILE="/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE" export CI_SERVER_TLS_CA_FILE="/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE"
if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
echo $'\''\x1b[32;1mFetching changes...\x1b[0;m'\'' echo $'\''\x1b[32;1mFetching changes...\x1b[0;m'\''
@ -982,10 +953,26 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_COMMIT_REF_NAME=master ++ CI_COMMIT_REF_NAME=master
++ export CI_COMMIT_REF_SLUG=master ++ export CI_COMMIT_REF_SLUG=master
++ CI_COMMIT_REF_SLUG=master ++ CI_COMMIT_REF_SLUG=master
... ...
``` ```
### Restrict access to debug logging
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213159) in GitLab 13.7.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292661) in GitLab 13.8.
You can restrict access to debug logging. When restricted, only users with
[developer or higher permissions](../../user/permissions.md#project-members-permissions)
can view job logs when debug logging is enabled with a variable in:
- The [`.gitlab-ci.yml` file](#create-a-custom-cicd-variable-in-the-gitlab-ciyml-file).
- The CI/CD variables set in the GitLab UI.
WARNING:
If you add `CI_DEBUG_TRACE` as a local variable to runners, debug logs generate and are visible
to all users with access to job logs. The permission levels are not checked by the runner,
so you should only use the variable in GitLab itself.
## Video walkthrough of a working example ## Video walkthrough of a working example
The [Managing the Complex Configuration Data Management Monster Using GitLab](https://www.youtube.com/watch?v=v4ZOJ96hAck) The [Managing the Complex Configuration Data Management Monster Using GitLab](https://www.youtube.com/watch?v=v4ZOJ96hAck)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View file

@ -4636,7 +4636,7 @@ meaning it applies to all jobs. If you define a variable in a job, it's availabl
to that job only. to that job only.
If a variable of the same name is defined globally and for a specific job, the If a variable of the same name is defined globally and for a specific job, the
[job-specific variable overrides the global variable](../variables/README.md#priority-of-cicd-variables). [job-specific variable overrides the global variable](../variables/README.md#cicd-variable-precedence).
All YAML-defined variables are also set to any linked All YAML-defined variables are also set to any linked
[Docker service containers](../services/index.md). [Docker service containers](../services/index.md).

View file

@ -408,8 +408,19 @@ When ready to merge:
circling back with the author about that. Otherwise, if the MR only has a few commits, we'll circling back with the author about that. Otherwise, if the MR only has a few commits, we'll
be respecting the author's setting by not squashing them. be respecting the author's setting by not squashing them.
- **Start a new merge request pipeline with the `Run Pipeline` button in the merge WARNING:
request's "Pipelines" tab, and enable "Merge When Pipeline Succeeds" (MWPS).** Note that: **If the merge request is from a fork, review all changes thoroughly for malicious code before
starting a [Pipeline for Merged Results](../ci/merge_request_pipelines/index.md#run-pipelines-in-the-parent-project-for-merge-requests-from-a-forked-project).**
Pay particular attention to new dependencies and dependency updates, such as Ruby gems and Node packages.
While changes to files like `Gemfile.lock` or `yarn.lock` might appear trivial, they could lead to the
fetching of malicious packages.
If the MR source branch is more than 100 commits behind the target branch, ask the author to rebase it.
Review links and images, especially in documentation MRs.
When in doubt, ask someone from `@gitlab-com/gl-security/appsec` to review the merge request **before starting any merge request pipeline**.
- Start a new merge request pipeline with the `Run Pipeline` button in the merge
request's "Pipelines" tab, and enable "Merge When Pipeline Succeeds" (MWPS).
Note that:
- If **[master is broken](https://about.gitlab.com/handbook/engineering/workflow/#broken-master), - If **[master is broken](https://about.gitlab.com/handbook/engineering/workflow/#broken-master),
do not merge the merge request** except for do not merge the merge request** except for
[very specific cases](https://about.gitlab.com/handbook/engineering/workflow/#criteria-for-merging-during-broken-master). [very specific cases](https://about.gitlab.com/handbook/engineering/workflow/#criteria-for-merging-during-broken-master).
@ -417,10 +428,6 @@ When ready to merge:
- If the **latest [Pipeline for Merged Results](../ci/merge_request_pipelines/pipelines_for_merged_results/#pipelines-for-merged-results)** finished less than 2 hours ago, you - If the **latest [Pipeline for Merged Results](../ci/merge_request_pipelines/pipelines_for_merged_results/#pipelines-for-merged-results)** finished less than 2 hours ago, you
might merge without starting a new pipeline as the merge request is close might merge without starting a new pipeline as the merge request is close
enough to `master`. enough to `master`.
- If the **merge request is from a fork**, we can use [Pipelines for Merged Results from a forked project](../ci/merge_request_pipelines/index.md#run-pipelines-in-the-parent-project-for-merge-requests-from-a-forked-project) with caution.
Before triggering the pipeline, review all changes for **malicious code**.
If you cannot trigger the pipeline, review the status of the fork relative to `master`.
If it's more than 100 commits behind, ask the author to rebase it before merging.
- When you set the MR to "Merge When Pipeline Succeeds", you should take over - When you set the MR to "Merge When Pipeline Succeeds", you should take over
subsequent revisions for anything that would be spotted after that. subsequent revisions for anything that would be spotted after that.

View file

@ -171,7 +171,7 @@ list of options.
## Custom Helm chart per environment ## Custom Helm chart per environment
You can specify the use of a custom Helm chart per environment by scoping the CI/CD variable You can specify the use of a custom Helm chart per environment by scoping the CI/CD variable
to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limit-the-environment-scopes-of-cicd-variables). to the desired environment. See [Limit environment scope of CI/CD variables](../../ci/variables/README.md#limit-the-environment-scope-of-a-cicd-variable).
## Customizing `.gitlab-ci.yml` ## Customizing `.gitlab-ci.yml`

View file

@ -231,7 +231,7 @@ any of the following places:
**Continuous Integration and Delivery** section **Continuous Integration and Delivery** section
The base domain variable `KUBE_INGRESS_BASE_DOMAIN` follows the same order of precedence The base domain variable `KUBE_INGRESS_BASE_DOMAIN` follows the same order of precedence
as other environment [variables](../../ci/variables/README.md#priority-of-cicd-variables). as other environment [variables](../../ci/variables/README.md#cicd-variable-precedence).
If the CI/CD variable is not set and the cluster setting is left blank, the instance-wide **Auto DevOps domain** If the CI/CD variable is not set and the cluster setting is left blank, the instance-wide **Auto DevOps domain**
setting is used if set. setting is used if set.
@ -274,7 +274,7 @@ used by Auto DevOps currently defines 3 environment names:
Those environments are tied to jobs using [Auto Deploy](stages.md#auto-deploy), so Those environments are tied to jobs using [Auto Deploy](stages.md#auto-deploy), so
except for the environment scope, they must have a different deployment domain. except for the environment scope, they must have a different deployment domain.
You must define a separate `KUBE_INGRESS_BASE_DOMAIN` variable for each of the above You must define a separate `KUBE_INGRESS_BASE_DOMAIN` variable for each of the above
[based on the environment](../../ci/variables/README.md#limit-the-environment-scopes-of-cicd-variables). [based on the environment](../../ci/variables/README.md#limit-the-environment-scope-of-a-cicd-variable).
The following table is an example of how to configure the three different clusters: The following table is an example of how to configure the three different clusters:

View file

@ -107,7 +107,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
When adding more than one Kubernetes cluster to your project, you need to differentiate When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with them with an environment scope. The environment scope associates clusters with
[environments](../../../ci/environments/index.md) similar to how the [environments](../../../ci/environments/index.md) similar to how the
[environment-specific variables](../../../ci/variables/README.md#limit-the-environment-scopes-of-cicd-variables) [environment-specific CI/CD variables](../../../ci/variables/README.md#limit-the-environment-scope-of-a-cicd-variable)
work. work.
While evaluating which environment matches the environment scope of a While evaluating which environment matches the environment scope of a

View file

@ -86,7 +86,7 @@ differentiates the new cluster from the rest.
When adding more than one Kubernetes cluster to your project, you need to differentiate When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments/index.md) similar to how the them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments/index.md) similar to how the
[environment-specific CI/CD variables](../../../ci/variables/README.md#limit-the-environment-scopes-of-cicd-variables) work. [environment-specific CI/CD variables](../../../ci/variables/README.md#limit-the-environment-scope-of-a-cicd-variable) work.
The default environment scope is `*`, which means all jobs, regardless of their The default environment scope is `*`, which means all jobs, regardless of their
environment, use that cluster. Each scope can be used only by a single cluster environment, use that cluster. Each scope can be used only by a single cluster

View file

@ -16961,7 +16961,7 @@ msgstr ""
msgid "Integrations|Use the GitLab Slack application" msgid "Integrations|Use the GitLab Slack application"
msgstr "" msgstr ""
msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created." msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) is created."
msgstr "" msgstr ""
msgid "Integrations|You can now close this window and return to the GitLab for Jira application." msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
@ -17678,6 +17678,9 @@ msgstr ""
msgid "JiraService|Automatically transitions Jira issues to the \"Done\" category. %{linkStart}Learn more%{linkEnd}" msgid "JiraService|Automatically transitions Jira issues to the \"Done\" category. %{linkStart}Learn more%{linkEnd}"
msgstr "" msgstr ""
msgid "JiraService|Base URL of the Jira instance."
msgstr ""
msgid "JiraService|Define the type of Jira issue to create from a vulnerability." msgid "JiraService|Define the type of Jira issue to create from a vulnerability."
msgstr "" msgstr ""
@ -17708,10 +17711,13 @@ msgstr ""
msgid "JiraService|For example, 12, 24" msgid "JiraService|For example, 12, 24"
msgstr "" msgstr ""
msgid "JiraService|For example, AB"
msgstr ""
msgid "JiraService|GitLab for Jira Configuration" msgid "JiraService|GitLab for Jira Configuration"
msgstr "" msgstr ""
msgid "JiraService|If different from Web URL" msgid "JiraService|If different from Web URL."
msgstr "" msgstr ""
msgid "JiraService|Issue List" msgid "JiraService|Issue List"
@ -17732,16 +17738,13 @@ msgstr ""
msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request." msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr "" msgstr ""
msgid "JiraService|Jira issue tracker"
msgstr ""
msgid "JiraService|Jira issue type" msgid "JiraService|Jira issue type"
msgstr "" msgstr ""
msgid "JiraService|Jira project key" msgid "JiraService|Jira project key"
msgstr "" msgstr ""
msgid "JiraService|Leave blank to use your current password or API token" msgid "JiraService|Leave blank to use your current password or API token."
msgstr "" msgstr ""
msgid "JiraService|Move to Done" msgid "JiraService|Move to Done"
@ -17777,13 +17780,16 @@ msgstr ""
msgid "JiraService|This issue is synchronized with Jira" msgid "JiraService|This issue is synchronized with Jira"
msgstr "" msgstr ""
msgid "JiraService|Track issues in Jira"
msgstr ""
msgid "JiraService|Transition Jira issues to their final state:" msgid "JiraService|Transition Jira issues to their final state:"
msgstr "" msgstr ""
msgid "JiraService|Use a password for server version and an API token for cloud version" msgid "JiraService|Use a password for server version and an API token for cloud version."
msgstr "" msgstr ""
msgid "JiraService|Use a username for server version and an email for cloud version" msgid "JiraService|Use a username for server version and an email for cloud version."
msgstr "" msgstr ""
msgid "JiraService|Use custom transitions" msgid "JiraService|Use custom transitions"
@ -17798,7 +17804,7 @@ msgstr ""
msgid "JiraService|View Jira issues in GitLab" msgid "JiraService|View Jira issues in GitLab"
msgstr "" msgstr ""
msgid "JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below." msgid "JiraService|Warning: All GitLab users that have access to this GitLab project are able to view all issues from the Jira project specified below."
msgstr "" msgstr ""
msgid "JiraService|Web URL" msgid "JiraService|Web URL"
@ -17807,9 +17813,6 @@ msgstr ""
msgid "JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only." msgid "JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only."
msgstr "" msgstr ""
msgid "JiraService|e.g. AB"
msgstr ""
msgid "JiraService|transition ids can have only numbers which can be split with , or ;" msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
msgstr "" msgstr ""
@ -18975,9 +18978,6 @@ msgstr ""
msgid "Manage projects." msgid "Manage projects."
msgstr "" msgstr ""
msgid "Manage storage usage"
msgstr ""
msgid "Manage two-factor authentication" msgid "Manage two-factor authentication"
msgstr "" msgstr ""
@ -20535,17 +20535,15 @@ msgstr ""
msgid "NamespaceStorageSize|You have reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})" msgid "NamespaceStorageSize|You have reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})"
msgstr "" msgstr ""
msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on %{locked_project_count} project. To unlock it, please purchase additional storage" msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on one or more projects."
msgid_plural "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on %{locked_project_count} projects. To unlock them, please purchase additional storage"
msgstr[0] ""
msgstr[1] ""
msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To learn more about reducing storage capacity please visit our docs."
msgstr "" msgstr ""
msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines." msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines."
msgstr "" msgstr ""
msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines. To learn more about reducing storage capacity please visit our docs."
msgstr ""
msgid "Namespaces" msgid "Namespaces"
msgstr "" msgstr ""
@ -34201,6 +34199,9 @@ msgstr ""
msgid "View the performance dashboard at" msgid "View the performance dashboard at"
msgstr "" msgstr ""
msgid "View usage details"
msgstr ""
msgid "View users statistics" msgid "View users statistics"
msgstr "" msgstr ""
@ -35115,6 +35116,9 @@ msgstr ""
msgid "Write milestone description..." msgid "Write milestone description..."
msgstr "" msgstr ""
msgid "Write to \"authorized_keys\" file"
msgstr ""
msgid "Write your release notes or drag your files here…" msgid "Write your release notes or drag your files here…"
msgstr "" msgstr ""

View file

@ -51,7 +51,7 @@
"@gitlab/favicon-overlay": "2.0.0", "@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.188.0", "@gitlab/svgs": "1.188.0",
"@gitlab/tributejs": "1.0.0", "@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "29.1.0", "@gitlab/ui": "29.2.1",
"@gitlab/visual-review-tools": "1.6.1", "@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4", "@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4", "@rails/ujs": "^6.0.3-4",

View file

@ -201,21 +201,21 @@ RSpec.describe "Admin Runners" do
visit admin_runners_path visit admin_runners_path
within '.runners-content .gl-responsive-table-row:nth-child(2)' do within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
expect(page).to have_content 'runner-2' expect(page).to have_content 'runner-2'
end end
within '.runners-content .gl-responsive-table-row:nth-child(3)' do within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
expect(page).to have_content 'runner-1' expect(page).to have_content 'runner-1'
end end
sorting_by 'Last Contact' sorting_by 'Last Contact'
within '.runners-content .gl-responsive-table-row:nth-child(2)' do within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
expect(page).to have_content 'runner-1' expect(page).to have_content 'runner-1'
end end
within '.runners-content .gl-responsive-table-row:nth-child(3)' do within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
expect(page).to have_content 'runner-2' expect(page).to have_content 'runner-2'
end end
end end

View file

@ -79,7 +79,7 @@ RSpec.describe 'Runners' do
visit project_runners_path(project) visit project_runners_path(project)
within '.activated-specific-runners' do within '.activated-specific-runners' do
first('.edit-runner > a').click first('[data-testid="edit-runner-link"]').click
end end
expect(page.find_field('runner[access_level]')).not_to be_checked expect(page.find_field('runner[access_level]')).not_to be_checked
@ -99,7 +99,7 @@ RSpec.describe 'Runners' do
visit project_runners_path(project) visit project_runners_path(project)
within '.activated-specific-runners' do within '.activated-specific-runners' do
first('.edit-runner > a').click first('[data-testid="edit-runner-link"]').click
end end
expect(page.find_field('runner[run_untagged]')).to be_checked expect(page.find_field('runner[run_untagged]')).to be_checked

View file

@ -1,32 +1,65 @@
import { mount, createLocalVue } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { escape } from 'lodash'; import { escape } from 'lodash';
import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import DiffsModule from '~/diffs/store/modules';
import NoteActions from '~/notes/components/note_actions.vue'; import NoteActions from '~/notes/components/note_actions.vue';
import NoteBody from '~/notes/components/note_body.vue'; import NoteBody from '~/notes/components/note_body.vue';
import NoteHeader from '~/notes/components/note_header.vue'; import NoteHeader from '~/notes/components/note_header.vue';
import issueNote from '~/notes/components/noteable_note.vue'; import issueNote from '~/notes/components/noteable_note.vue';
import createStore from '~/notes/stores'; import NotesModule from '~/notes/stores/modules';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data'; import { noteableDataMock, notesDataMock, note } from '../mock_data';
Vue.use(Vuex);
const singleLineNotePosition = {
line_range: {
start: {
line_code: 'abc_1_1',
type: null,
old_line: '1',
new_line: '1',
},
end: {
line_code: 'abc_1_1',
type: null,
old_line: '1',
new_line: '1',
},
},
};
describe('issue_note', () => { describe('issue_note', () => {
let store; let store;
let wrapper; let wrapper;
const findMultilineComment = () => wrapper.find('[data-testid="multiline-comment"]'); const findMultilineComment = () => wrapper.find('[data-testid="multiline-comment"]');
const createWrapper = (props = {}) => { const createWrapper = (props = {}, storeUpdater = (s) => s) => {
store = createStore(); store = new Vuex.Store(
storeUpdater({
modules: {
notes: NotesModule(),
diffs: DiffsModule(),
},
}),
);
store.dispatch('setNoteableData', noteableDataMock); store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock); store.dispatch('setNotesData', notesDataMock);
const localVue = createLocalVue(); wrapper = mount(issueNote, {
wrapper = mount(localVue.extend(issueNote), {
store, store,
propsData: { propsData: {
note, note,
...props, ...props,
}, },
localVue,
stubs: [ stubs: [
'note-header', 'note-header',
'user-avatar-link', 'user-avatar-link',
@ -216,10 +249,14 @@ describe('issue_note', () => {
const noteBodyComponent = wrapper.findComponent(NoteBody); const noteBodyComponent = wrapper.findComponent(NoteBody);
store.hotUpdate({ store.hotUpdate({
modules: {
notes: {
actions: { actions: {
updateNote() {}, updateNote() {},
setSelectedCommentPositionHover() {}, setSelectedCommentPositionHover() {},
}, },
},
},
}); });
noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {}); noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {});
@ -238,9 +275,13 @@ describe('issue_note', () => {
it('restores content of updated note', async () => { it('restores content of updated note', async () => {
const updatedText = 'updated note text'; const updatedText = 'updated note text';
store.hotUpdate({ store.hotUpdate({
modules: {
notes: {
actions: { actions: {
updateNote() {}, updateNote() {},
}, },
},
},
}); });
const noteBody = wrapper.findComponent(NoteBody); const noteBody = wrapper.findComponent(NoteBody);
noteBody.vm.resetAutoSave = () => {}; noteBody.vm.resetAutoSave = () => {};
@ -267,10 +308,14 @@ describe('issue_note', () => {
const updateActions = () => { const updateActions = () => {
store.hotUpdate({ store.hotUpdate({
modules: {
notes: {
actions: { actions: {
updateNote, updateNote,
setSelectedCommentPositionHover() {}, setSelectedCommentPositionHover() {},
}, },
},
},
}); });
}; };
@ -299,4 +344,62 @@ describe('issue_note', () => {
expect(updateNote.mock.calls[0][1].note.note.position).toBe(expectation); expect(updateNote.mock.calls[0][1].note.note.position).toBe(expectation);
}); });
}); });
describe('diffFile', () => {
it.each`
scenario | files | noteDef
${'the note has no position'} | ${undefined} | ${note}
${'the Diffs store has no data'} | ${[]} | ${{ ...note, position: singleLineNotePosition }}
`(
'returns `null` when $scenario and no diff file is provided as a prop',
({ noteDef, diffs }) => {
const storeUpdater = (rawStore) => {
const updatedStore = { ...rawStore };
if (diffs) {
updatedStore.modules.diffs.state.diffFiles = diffs;
}
return updatedStore;
};
createWrapper({ note: noteDef, discussionFile: null }, storeUpdater);
expect(wrapper.vm.diffFile).toBe(null);
},
);
it("returns the correct diff file from the Diffs store if it's available", () => {
createWrapper(
{
note: { ...note, position: singleLineNotePosition },
},
(rawStore) => {
const updatedStore = { ...rawStore };
updatedStore.modules.diffs.state.diffFiles = [
{ file_hash: 'abc', testId: 'diffFileTest' },
];
return updatedStore;
},
);
expect(wrapper.vm.diffFile.testId).toBe('diffFileTest');
});
it('returns the provided diff file if the more robust getters fail', () => {
createWrapper(
{
note: { ...note, position: singleLineNotePosition },
discussionFile: { testId: 'diffFileTest' },
},
(rawStore) => {
const updatedStore = { ...rawStore };
updatedStore.modules.diffs.state.diffFiles = [];
return updatedStore;
},
);
expect(wrapper.vm.diffFile.testId).toBe('diffFileTest');
});
});
}); });

View file

@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import { GRAPHQL } from '~/pipelines/components/graph/constants'; import { GRAPHQL, STAGE_VIEW } from '~/pipelines/components/graph/constants';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import JobItem from '~/pipelines/components/graph/job_item.vue'; import JobItem from '~/pipelines/components/graph/job_item.vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
@ -20,6 +20,7 @@ describe('graph component', () => {
const defaultProps = { const defaultProps = {
pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'), pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'),
viewType: STAGE_VIEW,
configPaths: { configPaths: {
metricsPath: '', metricsPath: '',
graphqlResourceEtag: 'this/is/a/path', graphqlResourceEtag: 'this/is/a/path',

View file

@ -1,13 +1,15 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { IID_FAILURE } from '~/pipelines/components/graph/constants'; import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue'; import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue'; import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue';
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
import { mockPipelineResponse } from './mock_data'; import { mockPipelineResponse } from './mock_data';
const defaultProvide = { const defaultProvide = {
@ -24,6 +26,9 @@ describe('Pipeline graph wrapper', () => {
const getAlert = () => wrapper.find(GlAlert); const getAlert = () => wrapper.find(GlAlert);
const getLoadingIcon = () => wrapper.find(GlLoadingIcon); const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
const getGraph = () => wrapper.find(PipelineGraph); const getGraph = () => wrapper.find(PipelineGraph);
const getStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
const getAllStageColumnGroupsInColumn = () =>
wrapper.find(StageColumnComponent).findAll('[data-testid="stage-column-group"]');
const getViewSelector = () => wrapper.find(GraphViewSelector); const getViewSelector = () => wrapper.find(GraphViewSelector);
const createComponent = ({ const createComponent = ({
@ -48,12 +53,13 @@ describe('Pipeline graph wrapper', () => {
const createComponentWithApollo = ({ const createComponentWithApollo = ({
getPipelineDetailsHandler = jest.fn().mockResolvedValue(mockPipelineResponse), getPipelineDetailsHandler = jest.fn().mockResolvedValue(mockPipelineResponse),
mountFn = shallowMount,
provide = {}, provide = {},
} = {}) => { } = {}) => {
const requestHandlers = [[getPipelineDetails, getPipelineDetailsHandler]]; const requestHandlers = [[getPipelineDetails, getPipelineDetailsHandler]];
const apolloProvider = createMockApollo(requestHandlers); const apolloProvider = createMockApollo(requestHandlers);
createComponent({ apolloProvider, provide }); createComponent({ apolloProvider, provide, mountFn });
}; };
afterEach(() => { afterEach(() => {
@ -223,13 +229,16 @@ describe('Pipeline graph wrapper', () => {
}); });
describe('when feature flag is on', () => { describe('when feature flag is on', () => {
let layersFn;
beforeEach(async () => { beforeEach(async () => {
layersFn = jest.spyOn(parsingUtils, 'listByLayers');
createComponentWithApollo({ createComponentWithApollo({
provide: { provide: {
glFeatures: { glFeatures: {
pipelineGraphLayersView: true, pipelineGraphLayersView: true,
}, },
}, },
mountFn: mount,
}); });
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
@ -239,6 +248,26 @@ describe('Pipeline graph wrapper', () => {
it('appears', () => { it('appears', () => {
expect(getViewSelector().exists()).toBe(true); expect(getViewSelector().exists()).toBe(true);
}); });
it('switches between views', async () => {
const groupsInFirstColumn =
mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length;
expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn);
expect(getStageColumnTitle().text()).toBe('Build');
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1);
expect(getStageColumnTitle().text()).toBe('');
});
it('calls listByLayers only once no matter how many times view is switched', async () => {
expect(layersFn).not.toHaveBeenCalled();
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
expect(layersFn).toHaveBeenCalledTimes(1);
await getViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
await getViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
expect(layersFn).toHaveBeenCalledTimes(1);
});
}); });
}); });
}); });

View file

@ -2,10 +2,17 @@ import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { DOWNSTREAM, GRAPHQL, UPSTREAM } from '~/pipelines/components/graph/constants'; import {
DOWNSTREAM,
GRAPHQL,
UPSTREAM,
LAYER_VIEW,
STAGE_VIEW,
} from '~/pipelines/components/graph/constants';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue'; import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
import { LOAD_FAILURE } from '~/pipelines/constants'; import { LOAD_FAILURE } from '~/pipelines/constants';
import { import {
mockPipelineResponse, mockPipelineResponse,
@ -20,6 +27,7 @@ describe('Linked Pipelines Column', () => {
columnTitle: 'Downstream', columnTitle: 'Downstream',
linkedPipelines: processedPipeline.downstream, linkedPipelines: processedPipeline.downstream,
type: DOWNSTREAM, type: DOWNSTREAM,
viewType: STAGE_VIEW,
configPaths: { configPaths: {
metricsPath: '', metricsPath: '',
graphqlResourceEtag: 'this/is/a/path', graphqlResourceEtag: 'this/is/a/path',
@ -67,7 +75,7 @@ describe('Linked Pipelines Column', () => {
describe('it renders correctly', () => { describe('it renders correctly', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponentWithApollo();
}); });
it('renders the pipeline title', () => { it('renders the pipeline title', () => {
@ -91,6 +99,27 @@ describe('Linked Pipelines Column', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
}; };
describe('layer type rendering', () => {
let layersFn;
beforeEach(() => {
layersFn = jest.spyOn(parsingUtils, 'listByLayers');
createComponentWithApollo({ mountFn: mount });
});
it('calls listByLayers only once no matter how many times view is switched', async () => {
expect(layersFn).not.toHaveBeenCalled();
await clickExpandButtonAndAwaitTimers();
await wrapper.setProps({ viewType: LAYER_VIEW });
await wrapper.vm.$nextTick();
expect(layersFn).toHaveBeenCalledTimes(1);
await wrapper.setProps({ viewType: STAGE_VIEW });
await wrapper.setProps({ viewType: LAYER_VIEW });
await wrapper.setProps({ viewType: STAGE_VIEW });
expect(layersFn).toHaveBeenCalledTimes(1);
});
});
describe('downstream', () => { describe('downstream', () => {
describe('when successful', () => { describe('when successful', () => {
beforeEach(() => { beforeEach(() => {

View file

@ -434,21 +434,7 @@ export const mockPipelineResponse = {
}, },
needs: { needs: {
__typename: 'CiBuildNeedConnection', __typename: 'CiBuildNeedConnection',
nodes: [ nodes: [],
{
__typename: 'CiBuildNeed',
name: 'build_c',
},
{
__typename: 'CiBuildNeed',
name: 'build_b',
},
{
__typename: 'CiBuildNeed',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
],
}, },
}, },
], ],

View file

@ -96,11 +96,11 @@ const completeMock = [
describe('Shared pipeline unwrapping utils', () => { describe('Shared pipeline unwrapping utils', () => {
describe('unwrapGroups', () => { describe('unwrapGroups', () => {
it('takes stages without nodes and returns the unwrapped groups', () => { it('takes stages without nodes and returns the unwrapped groups', () => {
expect(unwrapGroups(stagesAndGroups)[0].groups).toEqual(groupsArray); expect(unwrapGroups(stagesAndGroups)[0].node.groups).toEqual(groupsArray);
}); });
it('keeps other stage properties intact', () => { it('keeps other stage properties intact', () => {
expect(unwrapGroups(stagesAndGroups)[0]).toMatchObject(basicStageInfo); expect(unwrapGroups(stagesAndGroups)[0].node).toMatchObject(basicStageInfo);
}); });
}); });

View file

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) } let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:protocol) { 'http' } let(:protocol) { 'http' }
let(:git_user) { user } let(:git_user) { user }
let(:repository) { project.repository } let(:repository) { project.repository }

View file

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) } let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
let(:repository) { project.repository } let(:repository) { project.repository }
let(:protocol) { 'http' } let(:protocol) { 'http' }
let(:git_user) { user } let(:git_user) { user }
@ -101,6 +102,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with project snippet' do context 'with project snippet' do
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) } let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
let(:repository) { snippet.repository } let(:repository) { snippet.repository }
it_behaves_like 'errors per protocol' do it_behaves_like 'errors per protocol' do
@ -111,6 +113,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with personal snippet' do context 'with personal snippet' do
let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) } let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) }
let(:repository) { snippet.repository } let(:repository) { snippet.repository }
it 'returns nil' do it 'returns nil' do

View file

@ -6,6 +6,7 @@ require 'test_prof/recipes/rspec/let_it_be'
RSpec.describe ::Gitlab::Graphql::BatchKey do RSpec.describe ::Gitlab::Graphql::BatchKey do
let_it_be(:rect) { Struct.new(:len, :width) } let_it_be(:rect) { Struct.new(:len, :width) }
let_it_be(:circle) { Struct.new(:radius) } let_it_be(:circle) { Struct.new(:radius) }
let(:lookahead) { nil } let(:lookahead) { nil }
let(:object) { rect.new(2, 3) } let(:object) { rect.new(2, 3) }

View file

@ -6,6 +6,7 @@ RSpec.describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository } let(:repository) { project.repository }
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) } let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) }
let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) } let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) }

View file

@ -57,6 +57,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
describe 'basic verification that references work' do describe 'basic verification that references work' do
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") } let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") }

View file

@ -337,6 +337,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
describe '#nodes' do describe '#nodes' do
let_it_be(:all_nodes) { create_list(:project, 5) } let_it_be(:all_nodes) { create_list(:project, 5) }
let(:paged_nodes) { subject.nodes } let(:paged_nodes) { subject.nodes }
it_behaves_like 'connection with paged nodes' do it_behaves_like 'connection with paged nodes' do

View file

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::Keyset::LastItems do RSpec.describe Gitlab::Graphql::Pagination::Keyset::LastItems do
let_it_be(:merge_request) { create(:merge_request) } let_it_be(:merge_request) { create(:merge_request) }
let(:scope) { MergeRequest.order_merged_at_asc } let(:scope) { MergeRequest.order_merged_at_asc }
subject { described_class.take_items(*args) } subject { described_class.take_items(*args) }

View file

@ -23,10 +23,10 @@ RSpec.describe ServiceFieldEntity do
type: 'text', type: 'text',
name: 'username', name: 'username',
title: 'Username or Email', title: 'Username or Email',
placeholder: 'Use a username for server version and an email for cloud version', placeholder: nil,
help: 'Use a username for server version and an email for cloud version.',
required: true, required: true,
choices: nil, choices: nil,
help: nil,
value: 'jira_username' value: 'jira_username'
} }
@ -42,10 +42,10 @@ RSpec.describe ServiceFieldEntity do
type: 'password', type: 'password',
name: 'password', name: 'password',
title: 'Enter new password or API token', title: 'Enter new password or API token',
placeholder: 'Use a password for server version and an API token for cloud version', placeholder: nil,
help: 'Leave blank to use your current password or API token.',
required: true, required: true,
choices: nil, choices: nil,
help: 'Leave blank to use your current password or API token',
value: 'true' value: 'true'
} }

View file

@ -907,10 +907,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@29.1.0": "@gitlab/ui@29.2.1":
version "29.1.0" version "29.2.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.1.0.tgz#0db1c12ca1ee9882613a73ddc964a81db2697c2f" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.2.1.tgz#e6377131f3a22eebe07903198ec874ca5d26884c"
integrity sha512-jmsh67gmucdLxP7MBYt+SkLCXa62nYZy9/iRLZk4B+QdyEOzbpqj0ydAmPjMjFNfOV18KHKC6Lvpw4kUDE/GRA== integrity sha512-vLzCMQsppGLpzWRaNxXpx86y6JjSOEnQXQxtLudnSJktWl0uHfglED2xe7vJQJo+qmzQf6T5m3gbVupj/pG4+A==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"