Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-18 18:10:41 +00:00
parent 15714832c8
commit fca2dd4d55
121 changed files with 3022 additions and 1663 deletions

View File

@ -1,11 +1,11 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/dist/charts';
import * as Sentry from '@sentry/browser';
import produce from 'immer';
import { sortBy } from 'lodash';
import { formatDateAsMonth } from '~/lib/utils/datetime_utility';
import { s__, __ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import latestGroupsQuery from '../graphql/queries/groups.query.graphql';
import latestProjectsQuery from '../graphql/queries/projects.query.graphql';

View File

@ -1,9 +1,9 @@
<script>
import * as Sentry from '@sentry/browser';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { s__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import usageTrendsCountQuery from '../graphql/queries/usage_trends_count.query.graphql';
const defaultPrecision = 0;

View File

@ -1,13 +1,13 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/dist/charts';
import * as Sentry from '@sentry/browser';
import { some, every } from 'lodash';
import {
differenceInMonths,
formatDateAsMonth,
getDayDifference,
} from '~/lib/utils/datetime_utility';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import { TODAY, START_DATE } from '../constants';
import { getAverageByMonth, getEarliestDate, generateDataKeys } from '../utils';

View File

@ -1,11 +1,11 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import * as Sentry from '@sentry/browser';
import produce from 'immer';
import { sortBy } from 'lodash';
import { formatDateAsMonth } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import usersQuery from '../graphql/queries/users.query.graphql';
import { getAverageByMonth } from '../utils';

View File

@ -1,9 +1,9 @@
import * as Sentry from '@sentry/browser';
import { deprecatedCreateFlash as flash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import { MAX_REQUESTS } from '../constants';
import * as types from './mutation_types';

View File

@ -283,7 +283,7 @@ export function addContextLines(options) {
* Trims the first char of the `richText` property when it's either a space or a diff symbol.
* @param {Object} line
* @returns {Object}
* @deprecated
* @deprecated Use `line.rich_text = line.rich_text ? line.rich_text.replace(/^[+ -]/, '') : undefined;` instead!. For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/299329
*/
export function trimFirstCharOfLineContent(line = {}) {
// eslint-disable-next-line no-param-reassign

View File

@ -16,6 +16,9 @@ export const EDITOR_READY_EVENT = 'editor-ready';
export const EDITOR_TYPE_CODE = 'vs.editor.ICodeEditor';
export const EDITOR_TYPE_DIFF = 'vs.editor.IDiffEditor';
export const EDITOR_CODE_INSTANCE_FN = 'createInstance';
export const EDITOR_DIFF_INSTANCE_FN = 'createDiffInstance';
//
// EXTENSIONS' CONSTANTS
//

View File

@ -0,0 +1,164 @@
import { debounce } from 'lodash';
import { KeyCode, KeyMod, Range } from 'monaco-editor';
import { EDITOR_TYPE_DIFF } from '~/editor/constants';
import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base';
import Disposable from '~/ide/lib/common/disposable';
import { editorOptions } from '~/ide/lib/editor_options';
import keymap from '~/ide/lib/keymap.json';
const isDiffEditorType = (instance) => {
return instance.getEditorType() === EDITOR_TYPE_DIFF;
};
export const UPDATE_DIMENSIONS_DELAY = 200;
export class EditorWebIdeExtension extends EditorLiteExtension {
constructor({ instance, modelManager, ...options } = {}) {
super({
instance,
...options,
modelManager,
disposable: new Disposable(),
debouncedUpdate: debounce(() => {
instance.updateDimensions();
}, UPDATE_DIMENSIONS_DELAY),
});
window.addEventListener('resize', instance.debouncedUpdate, false);
instance.onDidDispose(() => {
window.removeEventListener('resize', instance.debouncedUpdate);
// catch any potential errors with disposing the error
// this is mainly for tests caused by elements not existing
try {
instance.disposable.dispose();
} catch (e) {
if (process.env.NODE_ENV !== 'test') {
// eslint-disable-next-line no-console
console.error(e);
}
}
});
EditorWebIdeExtension.addActions(instance);
}
static addActions(instance) {
const { store } = instance;
const getKeyCode = (key) => {
const monacoKeyMod = key.indexOf('KEY_') === 0;
return monacoKeyMod ? KeyCode[key] : KeyMod[key];
};
keymap.forEach((command) => {
const { bindings, id, label, action } = command;
const keybindings = bindings.map((binding) => {
const keys = binding.split('+');
// eslint-disable-next-line no-bitwise
return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]);
});
instance.addAction({
id,
label,
keybindings,
run() {
store.dispatch(action.name, action.params);
return null;
},
});
});
}
createModel(file, head = null) {
return this.modelManager.addModel(file, head);
}
attachModel(model) {
if (isDiffEditorType(this)) {
this.setModel({
original: model.getOriginalModel(),
modified: model.getModel(),
});
return;
}
this.setModel(model.getModel());
this.updateOptions(
editorOptions.reduce((acc, obj) => {
Object.keys(obj).forEach((key) => {
Object.assign(acc, {
[key]: obj[key](model),
});
});
return acc;
}, {}),
);
}
attachMergeRequestModel(model) {
this.setModel({
original: model.getBaseModel(),
modified: model.getModel(),
});
}
updateDimensions() {
this.layout();
this.updateDiffView();
}
setPos({ lineNumber, column }) {
this.revealPositionInCenter({
lineNumber,
column,
});
this.setPosition({
lineNumber,
column,
});
}
onPositionChange(cb) {
if (!this.onDidChangeCursorPosition) {
return;
}
this.disposable.add(this.onDidChangeCursorPosition((e) => cb(this, e)));
}
updateDiffView() {
if (!isDiffEditorType(this)) {
return;
}
this.updateOptions({
renderSideBySide: EditorWebIdeExtension.renderSideBySide(this.getDomNode()),
});
}
replaceSelectedText(text) {
let selection = this.getSelection();
const range = new Range(
selection.startLineNumber,
selection.startColumn,
selection.endLineNumber,
selection.endColumn,
);
this.executeEdits('', [{ range, text }]);
selection = this.getSelection();
this.setPosition({ lineNumber: selection.endLineNumber, column: selection.endColumn });
}
static renderSideBySide(domElement) {
return domElement.offsetWidth >= 700;
}
}

View File

@ -1,5 +1,5 @@
import * as Sentry from '@sentry/browser';
import { escape } from 'lodash';
import * as Sentry from '~/sentry/wrapper';
import { spriteIcon } from './lib/utils/common_utils';
const FLASH_TYPES = {

View File

@ -49,7 +49,9 @@ export default {
</script>
<template>
<div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
<div
class="d-flex align-items-center ide-file-templates qa-file-templates-bar gl-relative gl-z-index-1"
>
<strong class="gl-mr-3"> {{ __('File templates') }} </strong>
<dropdown
:data="templateTypes"

View File

@ -1,6 +1,15 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import {
EDITOR_TYPE_DIFF,
EDITOR_CODE_INSTANCE_FN,
EDITOR_DIFF_INSTANCE_FN,
} from '~/editor/constants';
import EditorLite from '~/editor/editor_lite';
import { EditorWebIdeExtension } from '~/editor/extensions/editor_lite_webide_ext';
import { deprecatedCreateFlash as flash } from '~/flash';
import ModelManager from '~/ide/lib/common/model_manager';
import { defaultDiffEditorOptions, defaultEditorOptions } from '~/ide/lib/editor_options';
import { __ } from '~/locale';
import {
WEBIDE_MARK_FILE_CLICKED,
@ -20,7 +29,6 @@ import {
FILE_VIEW_MODE_PREVIEW,
} from '../constants';
import eventHub from '../eventhub';
import Editor from '../lib/editor';
import { getRulesWithTraversal } from '../lib/editorconfig/parser';
import mapRulesToMonaco from '../lib/editorconfig/rules_mapper';
import { getFileEditorOrDefault } from '../stores/modules/editor/utils';
@ -46,6 +54,9 @@ export default {
content: '',
images: {},
rules: {},
globalEditor: null,
modelManager: new ModelManager(),
isEditorLoading: true,
};
},
computed: {
@ -132,6 +143,7 @@ export default {
// Compare key to allow for files opened in review mode to be cached differently
if (oldVal.key !== this.file.key) {
this.isEditorLoading = true;
this.initEditor();
if (this.currentActivityView !== leftSidebarViews.edit.name) {
@ -149,6 +161,7 @@ export default {
}
},
viewer() {
this.isEditorLoading = false;
if (!this.file.pending) {
this.createEditorInstance();
}
@ -181,11 +194,11 @@ export default {
},
},
beforeDestroy() {
this.editor.dispose();
this.globalEditor.dispose();
},
mounted() {
if (!this.editor) {
this.editor = Editor.create(this.$store, this.editorOptions);
if (!this.globalEditor) {
this.globalEditor = new EditorLite();
}
this.initEditor();
@ -211,8 +224,6 @@ export default {
return;
}
this.editor.clearEditor();
this.registerSchemaForFile();
Promise.all([this.fetchFileData(), this.fetchEditorconfigRules()])
@ -251,20 +262,45 @@ export default {
return;
}
this.editor.dispose();
this.$nextTick(() => {
if (this.viewer === viewerTypes.edit) {
this.editor.createInstance(this.$refs.editor);
} else {
this.editor.createDiffInstance(this.$refs.editor);
}
const isDiff = this.viewer !== viewerTypes.edit;
const shouldDisposeEditor = isDiff !== (this.editor?.getEditorType() === EDITOR_TYPE_DIFF);
if (this.editor && !shouldDisposeEditor) {
this.setupEditor();
});
} else {
if (this.editor && shouldDisposeEditor) {
this.editor.dispose();
}
const instanceOptions = isDiff ? defaultDiffEditorOptions : defaultEditorOptions;
const method = isDiff ? EDITOR_DIFF_INSTANCE_FN : EDITOR_CODE_INSTANCE_FN;
this.editor = this.globalEditor[method]({
el: this.$refs.editor,
blobPath: this.file.path,
blobGlobalId: this.file.key,
blobContent: this.content || this.file.content,
...instanceOptions,
...this.editorOptions,
});
this.editor.use(
new EditorWebIdeExtension({
instance: this.editor,
modelManager: this.modelManager,
store: this.$store,
file: this.file,
options: this.editorOptions,
}),
);
this.$nextTick(() => {
this.setupEditor();
});
}
},
setupEditor() {
if (!this.file || !this.editor.instance || this.file.loading) return;
if (!this.file || !this.editor || this.file.loading) return;
const head = this.getStagedFile(this.file.path);
@ -279,6 +315,8 @@ export default {
this.editor.attachModel(this.model);
}
this.isEditorLoading = false;
this.model.updateOptions(this.rules);
this.model.onChange((model) => {
@ -298,7 +336,7 @@ export default {
});
});
this.editor.setPosition({
this.editor.setPos({
lineNumber: this.fileEditor.editorRow,
column: this.fileEditor.editorColumn,
});
@ -308,6 +346,10 @@ export default {
fileLanguage: this.model.language,
});
this.$nextTick(() => {
this.editor.updateDimensions();
});
this.$emit('editorSetup');
if (performance.getEntriesByName(WEBIDE_MARK_FILE_CLICKED).length) {
eventHub.$emit(WEBIDE_MEASURE_FILE_AFTER_INTERACTION);
@ -344,7 +386,7 @@ export default {
});
},
onPaste(event) {
const editor = this.editor.instance;
const { editor } = this;
const reImage = /^image\/(png|jpg|jpeg|gif)$/;
const file = event.clipboardData.files[0];
@ -395,6 +437,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
data-testid="edit-tab"
@click.prevent="updateEditor({ viewMode: $options.FILE_VIEW_MODE_EDITOR })"
>
{{ __('Edit') }}
@ -404,6 +447,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
data-testid="preview-tab"
@click.prevent="updateEditor({ viewMode: $options.FILE_VIEW_MODE_PREVIEW })"
>{{ previewMode.previewTitle }}</a
>
@ -414,6 +458,7 @@ export default {
<div
v-show="showEditor"
ref="editor"
:key="`content-editor`"
:class="{
'is-readonly': isCommitModeActive,
'is-deleted': file.deleted,
@ -421,6 +466,8 @@ export default {
}"
class="multi-file-editor-holder"
data-qa-selector="editor_container"
data-testid="editor-container"
:data-editor-loading="isEditorLoading"
@focusout="triggerFilesChange"
></div>
<content-viewer

View File

@ -1,5 +1,5 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlTab } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { __, sprintf } from '~/locale';
@ -13,6 +13,7 @@ export default {
FileIcon,
GlIcon,
ChangedFileIcon,
GlTab,
},
props: {
tab: {
@ -71,29 +72,30 @@ export default {
</script>
<template>
<li
:class="{
active: tab.active,
disabled: tab.pending,
}"
<gl-tab
:active="tab.active"
:disabled="tab.pending"
:title="tab.name"
@click="clickFile(tab)"
@mouseover="mouseOverTab"
@mouseout="mouseOutTab"
>
<div :title="getUrlForPath(tab.path)" class="multi-file-tab">
<file-icon :file-name="tab.name" :size="16" />
{{ tab.name }}
<file-status-icon :file="tab" />
</div>
<button
:aria-label="closeLabel"
:disabled="tab.pending"
type="button"
class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab)"
>
<gl-icon v-if="!showChangedIcon" :size="12" name="close" />
<changed-file-icon v-else :file="tab" />
</button>
</li>
<template #title>
<div :title="getUrlForPath(tab.path)" class="multi-file-tab">
<file-icon :file-name="tab.name" :size="16" />
{{ tab.name }}
<file-status-icon :file="tab" />
</div>
<button
:aria-label="closeLabel"
:disabled="tab.pending"
type="button"
class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab)"
>
<gl-icon v-if="!showChangedIcon" :size="12" name="close" />
<changed-file-icon v-else :file="tab" />
</button>
</template>
</gl-tab>
</template>

View File

@ -1,10 +1,12 @@
<script>
import { GlTabs } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import RepoTab from './repo_tab.vue';
export default {
components: {
RepoTab,
GlTabs,
},
props: {
activeFile: {
@ -42,8 +44,8 @@ export default {
<template>
<div class="multi-file-tabs">
<ul ref="tabsScroller" class="list-unstyled gl-mb-0">
<gl-tabs>
<repo-tab v-for="tab in files" :key="tab.key" :tab="tab" />
</ul>
</gl-tabs>
</div>
</template>

View File

@ -1,5 +1,5 @@
import * as Sentry from '@sentry/browser';
import { sanitize } from '~/lib/dompurify';
import * as Sentry from '~/sentry/wrapper';
// We currently load + parse the data from the issue app and related merge request
let cachedParsedData;

View File

@ -1,7 +1,7 @@
import * as Sentry from '@sentry/browser';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import * as Sentry from '~/sentry/wrapper';
import { convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
import { ENVIRONMENT_AVAILABLE_STATE, OVERVIEW_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants';

View File

@ -1,4 +1,4 @@
import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; // eslint-disable-line import/no-deprecated
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import AjaxCache from '~/lib/utils/ajax_cache';
import { sprintf, __ } from '~/locale';
@ -34,7 +34,7 @@ export const hasQuickActions = (note) => createQuickActionsRegex().test(note);
export const stripQuickActions = (note) => note.replace(createQuickActionsRegex(), '').trim();
export const prepareDiffLines = (diffLines) =>
diffLines.map((line) => ({ ...trimFirstCharOfLineContent(line) }));
diffLines.map((line) => ({ ...trimFirstCharOfLineContent(line) })); // eslint-disable-line import/no-deprecated
export const gqClient = createGqClient(
{},

View File

@ -54,3 +54,24 @@ export const MR_DIFFS_MARK_DIFF_FILES_END = 'mr-diffs-mark-diff-files-end';
// Measures
export const MR_DIFFS_MEASURE_FILE_TREE_DONE = 'mr-diffs-measure-file-tree-done';
export const MR_DIFFS_MEASURE_DIFF_FILES_DONE = 'mr-diffs-measure-diff-files-done';
//
// Pipelines Detail namespace
//
// Marks
export const PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START =
'pipelines-detail-links-mark-calculate-start';
export const PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END =
'pipelines-detail-links-mark-calculate-end';
// Measures
export const PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION =
'Pipelines Detail Graph: Links Calculation';
// Metrics
// Note: These strings must match the backend
// (defined in: app/services/ci/prometheus_metrics/observe_histograms_service.rb)
export const PIPELINES_DETAIL_LINK_DURATION = 'pipeline_graph_link_calculation_duration_seconds';
export const PIPELINES_DETAIL_LINKS_TOTAL = 'pipeline_graph_links_total';
export const PIPELINES_DETAIL_LINKS_JOB_RATIO = 'pipeline_graph_link_per_job_ratio';

View File

@ -17,6 +17,7 @@ import {
GlLoadingIcon,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { uniqueId } from 'lodash';
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
@ -24,7 +25,6 @@ import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__, __, n__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import { VARIABLE_TYPE, FILE_TYPE, CONFIG_VARIABLES_TIMEOUT } from '../constants';
export default {

View File

@ -15,14 +15,19 @@ export default {
StageColumnComponent,
},
props: {
pipeline: {
type: Object,
required: true,
},
isLinkedPipeline: {
type: Boolean,
required: false,
default: false,
},
pipeline: {
type: Object,
required: true,
metricsPath: {
type: String,
required: false,
default: '',
},
type: {
type: String,
@ -66,6 +71,12 @@ export default {
hasUpstreamPipelines() {
return Boolean(this.pipeline?.upstream?.length > 0);
},
metricsConfig() {
return {
path: this.metricsPath,
collectMetrics: true,
};
},
// The show downstream check prevents showing redundant linked columns
showDownstreamPipelines() {
return (
@ -145,6 +156,7 @@ export default {
:container-id="containerId"
:container-measurements="measurements"
:highlighted-job="hoveredJobName"
:metrics-config="metricsConfig"
default-link-color="gl-stroke-transparent"
@error="onError"
@highlightedJobsChange="updateHighlightedJobs"

View File

@ -14,6 +14,9 @@ export default {
PipelineGraph,
},
inject: {
metricsPath: {
default: '',
},
pipelineIid: {
default: '',
},
@ -108,6 +111,7 @@ export default {
<gl-loading-icon v-if="showLoadingIcon" class="gl-mx-auto gl-my-4" size="lg" />
<pipeline-graph
v-if="pipeline"
:metrics-path="metricsPath"
:pipeline="pipeline"
@error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph"

View File

@ -1,6 +1,6 @@
import * as Sentry from '@sentry/browser';
import Visibility from 'visibilityjs';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import * as Sentry from '~/sentry/wrapper';
import { unwrapStagesWithNeeds } from '../unwrapping_utils';
const addMulti = (mainPipelineProjectPath, linkedPipeline) => {

View File

@ -0,0 +1,8 @@
import axios from '~/lib/utils/axios_utils';
import { reportToSentry } from '../graph/utils';
export const reportPerformance = (path, stats) => {
axios.post(path, stats).catch((err) => {
reportToSentry('links_inner_perf', `error: ${err}`);
});
};

View File

@ -1,9 +1,19 @@
<script>
import { isEmpty } from 'lodash';
import {
PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
PIPELINES_DETAIL_LINK_DURATION,
PIPELINES_DETAIL_LINKS_TOTAL,
PIPELINES_DETAIL_LINKS_JOB_RATIO,
} from '~/performance/constants';
import { performanceMarkAndMeasure } from '~/performance/utils';
import { DRAW_FAILURE } from '../../constants';
import { createJobsHash, generateJobNeedsDict } from '../../utils';
import { reportToSentry } from '../graph/utils';
import { parseData } from '../parsing_utils';
import { reportPerformance } from './api';
import { generateLinksData } from './drawing_utils';
export default {
@ -26,6 +36,15 @@ export default {
type: Array,
required: true,
},
totalGroups: {
type: Number,
required: true,
},
metricsConfig: {
type: Object,
required: false,
default: () => ({}),
},
defaultLinkColor: {
type: String,
required: false,
@ -44,6 +63,9 @@ export default {
};
},
computed: {
shouldCollectMetrics() {
return this.metricsConfig.collectMetrics && this.metricsConfig.path;
},
hasHighlightedJob() {
return Boolean(this.highlightedJob);
},
@ -97,10 +119,52 @@ export default {
}
},
methods: {
beginPerfMeasure() {
if (this.shouldCollectMetrics) {
performanceMarkAndMeasure({ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START });
}
},
finishPerfMeasureAndSend() {
if (this.shouldCollectMetrics) {
performanceMarkAndMeasure({
mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
measures: [
{
name: PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
start: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
},
],
});
}
window.requestAnimationFrame(() => {
const duration = window.performance.getEntriesByName(
PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
)[0]?.duration;
if (!duration) {
return;
}
const data = {
histograms: [
{ name: PIPELINES_DETAIL_LINK_DURATION, value: duration },
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: this.links.length },
{
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
value: this.links.length / this.totalGroups,
},
],
};
reportPerformance(this.metricsConfig.path, data);
});
},
isLinkHighlighted(linkRef) {
return this.highlightedLinks.includes(linkRef);
},
prepareLinkData() {
this.beginPerfMeasure();
try {
const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs);
@ -109,6 +173,7 @@ export default {
this.$emit('error', DRAW_FAILURE);
reportToSentry(this.$options.name, err);
}
this.finishPerfMeasureAndSend();
},
getLinkClasses(link) {
return [

View File

@ -70,6 +70,7 @@ export default {
v-if="showLinkedLayers"
:container-measurements="containerMeasurements"
:pipeline-data="pipelineData"
:total-groups="numGroups"
v-bind="$attrs"
v-on="$listeners"
>

View File

@ -93,8 +93,13 @@ export default async function initPipelineDetailsBundle() {
/* webpackChunkName: 'createPipelinesDetailApp' */ './pipeline_details_graph'
);
const { pipelineProjectPath, pipelineIid } = dataset;
createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, pipelineProjectPath, pipelineIid);
const { metricsPath, pipelineProjectPath, pipelineIid } = dataset;
createPipelinesDetailApp(
SELECTORS.PIPELINE_GRAPH,
pipelineProjectPath,
pipelineIid,
metricsPath,
);
} catch {
Flash(__('An error occurred while loading the pipeline.'));
}

View File

@ -16,7 +16,7 @@ const apolloProvider = new VueApollo({
),
});
const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) => {
const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid, metricsPath) => {
// eslint-disable-next-line no-new
new Vue({
el: selector,
@ -25,6 +25,7 @@ const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) =>
},
apolloProvider,
provide: {
metricsPath,
pipelineProjectPath,
pipelineIid,
dataMethod: GRAPHQL,

View File

@ -1,8 +1,8 @@
import * as Sentry from '@sentry/browser';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import * as types from './mutation_types';
export default {

View File

@ -1,6 +1,6 @@
import * as Sentry from '@sentry/browser';
import $ from 'jquery';
import { __ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
const IGNORE_ERRORS = [
// Random plugins/extensions

View File

@ -1,26 +0,0 @@
// Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
// export * from '@sentry/browser';
export function init(...args) {
return args;
}
export function setUser(...args) {
return args;
}
export function captureException(...args) {
return args;
}
export function captureMessage(...args) {
return args;
}
export function withScope(fn) {
fn({
setTag(...args) {
return args;
},
});
}

View File

@ -122,6 +122,8 @@ export default {
:value="subscribed"
class="hide-collapsed"
data-testid="subscription-toggle"
:label="__('Notifications')"
label-position="hidden"
@change="toggleSubscription"
/>
</div>

View File

@ -11,12 +11,12 @@ import {
GlButton,
GlSafeHtmlDirective,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import { fetchPolicies } from '~/lib/graphql';
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import Tracking from '~/tracking';
import initUserPopovers from '~/user_popovers';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';

View File

@ -1,7 +1,7 @@
<script>
import * as Sentry from '@sentry/browser';
import Vue from 'vue';
import Vuex from 'vuex';
import * as Sentry from '~/sentry/wrapper';
Vue.use(Vuex);

View File

@ -36,6 +36,11 @@ export default {
required: false,
default: () => [VALID_IMAGE_FILE_MIMETYPE.mimetype],
},
singleFileSelection: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -79,7 +84,7 @@ export default {
return;
}
this.$emit('change', files);
this.$emit('change', this.singleFileSelection ? files[0] : files);
},
ondragenter(e) {
this.dragCounter += 1;
@ -92,7 +97,7 @@ export default {
this.$refs.fileUpload.click();
},
onFileInputChange(e) {
this.$emit('change', e.target.files);
this.$emit('change', this.singleFileSelection ? e.target.files[0] : e.target.files);
},
},
};
@ -119,9 +124,15 @@ export default {
data-testid="dropzone-area"
>
<gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" />
<p class="gl-mb-0">
<p class="gl-mb-0" data-testid="upload-text">
<slot name="upload-text" :openFileUpload="openFileUpload">
<gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} files to attach')">
<gl-sprintf
:message="
singleFileSelection
? __('Drop or %{linkStart}upload%{linkEnd} file to attach')
: __('Drop or %{linkStart}upload%{linkEnd} files to attach')
"
>
<template #link="{ content }">
<gl-link @click.stop="openFileUpload">
{{ content }}
@ -139,7 +150,7 @@ export default {
name="upload_file"
:accept="validFileMimetypes"
class="hide"
multiple
:multiple="!singleFileSelection"
@change="onFileInputChange"
/>
</slot>

View File

@ -3,6 +3,11 @@
@include gl-display-flex;
@include gl-justify-content-center;
@include gl-align-items-center;
@include gl-z-index-0;
> * {
filter: blur(5px);
}
&::before {
content: '';

View File

@ -313,8 +313,9 @@ table {
resize: none;
padding: $gl-padding-8 $gl-padding-12;
line-height: 1;
border-color: $border-color;
border: 1px solid $border-color;
background-color: $white;
overflow: hidden;
@include media-breakpoint-down(xs) {
margin-bottom: $gl-padding-8;

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module SecurityAndCompliancePermissions
extend ActiveSupport::Concern
included do
before_action :ensure_security_and_compliance_enabled!
end
private
def ensure_security_and_compliance_enabled!
render_404 unless can?(current_user, :access_security_and_compliance, project)
end
end

View File

@ -3,6 +3,8 @@
module Projects
module Security
class ConfigurationController < Projects::ApplicationController
include SecurityAndCompliancePermissions
feature_category :static_application_security_testing
def show

View File

@ -395,6 +395,7 @@ class ProjectsController < Projects::ApplicationController
metrics_dashboard_access_level
analytics_access_level
operations_access_level
security_and_compliance_access_level
]
end

View File

@ -69,7 +69,7 @@ class RootController < Dashboard::ProjectsController
end
def customize_homepage
@customize_homepage = experiment_enabled?(:customize_homepage)
@customize_homepage = Feature.enabled?(:customize_homepage, default_enabled: :yaml)
end
end

View File

@ -3,7 +3,7 @@
module Types
class BoardType < BaseObject
graphql_name 'Board'
description 'Represents a project or group board'
description 'Represents a project or group issue board'
accepts ::Board
authorize :read_board

View File

@ -7,9 +7,9 @@ module Types
graphql_name 'SastUiComponentSize'
description 'Size of UI component in SAST configuration page'
value 'SMALL'
value 'MEDIUM'
value 'LARGE'
value 'SMALL', description: "The size of UI component in SAST configuration page is small."
value 'MEDIUM', description: "The size of UI component in SAST configuration page is medium."
value 'LARGE', description: "The size of UI component in SAST configuration page is large."
end
end
end

View File

@ -379,10 +379,15 @@ module ProjectsHelper
private
def can_read_security_configuration?(project, current_user)
::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @subject, default_enabled: :yaml) &&
show_security_and_compliance_config? &&
can?(current_user, :access_security_and_compliance, project) &&
can?(current_user, :read_security_configuration, project)
end
def show_security_and_compliance_config?
::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @subject, default_enabled: :yaml)
end
def get_project_security_nav_tabs(project, current_user)
if can_read_security_configuration?(project, current_user)
[:security_and_compliance, :security_configuration]
@ -646,7 +651,8 @@ module ProjectsHelper
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
operationsAccessLevel: feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
allowEditingCommitMessages: project.allow_editing_commit_messages?
allowEditingCommitMessages: project.allow_editing_commit_messages?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level
}
end
@ -668,10 +674,13 @@ module ProjectsHelper
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?,
pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control')
pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'),
securityAndComplianceAvailable: show_security_and_compliance_toggle?
}
end
alias_method :show_security_and_compliance_toggle?, :show_security_and_compliance_config?
def project_permissions_panel_data_json(project)
project_permissions_panel_data(project).to_json.html_safe
end

View File

@ -34,6 +34,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_boolean(:snippets_access_level, value)
end
def security_and_compliance_enabled=(value)
write_feature_attribute_boolean(:security_and_compliance_access_level, value)
end
def repository_access_level=(value)
write_feature_attribute_string(:repository_access_level, value)
end
@ -78,6 +82,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:operations_access_level, value)
end
def security_and_compliance_access_level=(value)
write_feature_attribute_string(:security_and_compliance_access_level, value)
end
private
def write_feature_attribute_boolean(field, value)

View File

@ -164,6 +164,10 @@ class Namespace < ApplicationRecord
name = host.delete_suffix(gitlab_host)
Namespace.where(parent_id: nil).by_path(name)
end
def top_most
where(parent_id: nil)
end
end
def package_settings

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
module Packages
module Nuget
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
def self.table_name_prefix
'packages_nuget_'
end

View File

@ -98,12 +98,12 @@ class Packages::Package < ApplicationRecord
end
scope :preload_composer, -> { preload(:composer_metadatum) }
scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
scope :has_version, -> { where.not(version: nil) }
scope :processed, -> do
where.not(package_type: :nuget).or(
where.not(name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME)
where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME)
)
end
scope :preload_files, -> { preload(:package_files) }

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Packages
module Rubygems
TEMPORARY_PACKAGE_NAME = 'Gem.Temporary.Package'
def self.table_name_prefix
'packages_rubygems_'
end
end
end

View File

@ -3,7 +3,6 @@
module Packages
module Rubygems
class Metadatum < ApplicationRecord
self.table_name = 'packages_rubygems_metadata'
self.primary_key = :package_id
belongs_to :package, -> { where(package_type: :rubygems) }, inverse_of: :rubygems_metadatum

View File

@ -392,7 +392,8 @@ class Project < ApplicationRecord
:merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level,
:operations_enabled?, :operations_access_level, to: :project_feature, allow_nil: true
:operations_enabled?, :operations_access_level, :security_and_compliance_access_level,
to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
:show_default_award_emojis?,
to: :project_setting, allow_nil: true

View File

@ -3,7 +3,8 @@
class ProjectFeature < ApplicationRecord
include Featurable
FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard analytics operations).freeze
FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard analytics operations security_and_compliance).freeze
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance]).freeze
set_available_features(FEATURES)
@ -37,16 +38,17 @@ class ProjectFeature < ApplicationRecord
validate :repository_children_level
validate :allowed_access_levels
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
default_value_for :forking_access_level, value: ENABLED, allows_nil: false
default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
default_value_for :analytics_access_level, value: ENABLED, allows_nil: false
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
default_value_for :forking_access_level, value: ENABLED, allows_nil: false
default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
default_value_for :analytics_access_level, value: ENABLED, allows_nil: false
default_value_for :metrics_dashboard_access_level, value: PRIVATE, allows_nil: false
default_value_for :operations_access_level, value: ENABLED, allows_nil: false
default_value_for :operations_access_level, value: ENABLED, allows_nil: false
default_value_for :security_and_compliance_access_level, value: PRIVATE, allows_nil: false
default_value_for(:pages_access_level, allows_nil: false) do |feature|
if ::Gitlab::Pages.access_control_is_forced?

View File

@ -156,6 +156,7 @@ class ProjectPolicy < BasePolicy
metrics_dashboard
analytics
operations
security_and_compliance
]
features.each do |f|
@ -640,6 +641,10 @@ class ProjectPolicy < BasePolicy
enable :set_pipeline_variables
end
rule { ~security_and_compliance_disabled & can?(:developer_access) }.policy do
enable :access_security_and_compliance
end
private
def user_is_user?

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Packages
class CreateTemporaryPackageService < ::Packages::CreatePackageService
PACKAGE_VERSION = '0.0.0'
def execute(package_type, name: 'Temporary.Package')
create_package!(package_type,
name: name,
version: "#{PACKAGE_VERSION}-#{uuid}",
status: 'processing'
)
end
private
def uuid
SecureRandom.uuid
end
end
end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
module Packages
module Nuget
class CreatePackageService < ::Packages::CreatePackageService
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
PACKAGE_VERSION = '0.0.0'
def execute
create_package!(:nuget,
name: TEMPORARY_PACKAGE_NAME,
version: "#{PACKAGE_VERSION}-#{uuid}"
)
end
private
def uuid
SecureRandom.uuid
end
end
end
end

View File

@ -68,7 +68,8 @@ module Packages
def update_linked_package
@package_file.package.update!(
name: package_name,
version: package_version
version: package_version,
status: :default
)
::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies)

View File

@ -6,60 +6,67 @@
"^.*$": {
"allOf": [
{ "$ref": "#/definitions/named_field" },
{ "$ref": "#/definitions/type_list" }
{ "$ref": "#/definitions/detail_type" }
]
}
},
"definitions": {
"type_list": {
"detail_type": {
"oneOf": [
{ "$ref": "#/definitions/named_list" },
{ "$ref": "#/definitions/list" },
{ "$ref": "#/definitions/table" },
{ "$ref": "#/definitions/text" },
{ "$ref": "#/definitions/url" },
{ "$ref": "#/definitions/code" },
{ "$ref": "#/definitions/int" },
{ "$ref": "#/definitions/value" },
{ "$ref": "#/definitions/diff" },
{ "$ref": "#/definitions/markdown" },
{ "$ref": "#/definitions/commit" },
{ "$ref": "#/definitions/file_location" },
{ "$ref": "#/definitions/module_location" }
]
},
"lang_text": {
"type": "object",
"required": [ "value", "lang" ],
"properties": {
"lang": { "type": "string" },
"value": { "type": "string" }
}
},
"lang_text_list": {
"type": "array",
"items": { "$ref": "#/definitions/lang_text" }
"text_value": {
"type": "string"
},
"named_field": {
"type": "object",
"required": [ "name" ],
"required": [
"name"
],
"properties": {
"name": { "$ref": "#/definitions/lang_text_list" },
"description": { "$ref": "#/definitions/lang_text_list" }
"name": {
"$ref": "#/definitions/text_value",
"minLength": 1
},
"description": {
"$ref": "#/definitions/text_value"
}
}
},
"named_list": {
"type": "object",
"description": "An object with named and typed fields",
"required": [ "type", "items" ],
"required": [
"type",
"items"
],
"properties": {
"type": { "const": "named-list" },
"type": {
"const": "named-list"
},
"items": {
"type": "object",
"patternProperties": {
"^.*$": {
"allOf": [
{ "$ref": "#/definitions/named_field" },
{ "$ref": "#/definitions/type_list" }
{
"$ref": "#/definitions/named_field"
},
{
"$ref": "#/definitions/detail_type"
}
]
}
}
@ -69,38 +76,45 @@
"list": {
"type": "object",
"description": "A list of typed fields",
"required": [ "type", "items" ],
"required": [
"type",
"items"
],
"properties": {
"type": { "const": "list" },
"type": {
"const": "list"
},
"items": {
"type": "array",
"items": { "$ref": "#/definitions/type_list" }
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
},
"table": {
"type": "object",
"description": "A table of typed fields",
"required": [],
"required": [
"type",
"rows"
],
"properties": {
"type": { "const": "table" },
"items": {
"type": "object",
"properties": {
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/type_list"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/type_list"
}
}
"type": {
"const": "table"
},
"header": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/detail_type"
}
}
}
@ -109,73 +123,171 @@
"text": {
"type": "object",
"description": "Raw text",
"required": [ "type", "value" ],
"required": [
"type",
"value"
],
"properties": {
"type": { "const": "text" },
"value": { "$ref": "#/definitions/lang_text_list" }
"type": {
"const": "text"
},
"value": {
"$ref": "#/definitions/text_value"
}
}
},
"url": {
"type": "object",
"description": "A single URL",
"required": [ "type", "href" ],
"required": [
"type",
"href"
],
"properties": {
"type": { "const": "url" },
"text": { "$ref": "#/definitions/lang_text_list" },
"href": { "type": "string" }
"type": {
"const": "url"
},
"text": {
"$ref": "#/definitions/text_value"
},
"href": {
"type": "string",
"minLength": 1,
"examples": ["http://mysite.com"]
}
}
},
"code": {
"type": "object",
"description": "A codeblock",
"required": [ "type", "value" ],
"required": [
"type",
"value"
],
"properties": {
"type": { "const": "code" },
"value": { "type": "string" },
"lang": { "type": "string" }
"type": {
"const": "code"
},
"value": {
"type": "string"
},
"lang": {
"type": "string",
"description": "A programming language"
}
}
},
"int": {
"value": {
"type": "object",
"description": "An integer",
"required": [ "type", "value" ],
"description": "A field that can store a range of types of value",
"required": ["type", "value"],
"properties": {
"type": { "const": "int" },
"value": { "type": "integer" },
"format": {
"type": "string",
"enum": [ "default", "hex" ]
"type": { "const": "value" },
"value": {
"type": ["number", "string", "boolean"]
}
}
},
"diff": {
"type": "object",
"description": "A diff",
"required": [
"type",
"before",
"after"
],
"properties": {
"type": {
"const": "diff"
},
"before": {
"type": "string"
},
"after": {
"type": "string"
}
}
},
"markdown": {
"type": "object",
"description": "GitLab flavoured markdown, see https://docs.gitlab.com/ee/user/markdown.html",
"required": [
"type",
"value"
],
"properties": {
"type": {
"const": "markdown"
},
"value": {
"$ref": "#/definitions/text_value",
"examples": ["Here is markdown `inline code` #1 [test](gitlab.com)\n\n![GitLab Logo](https://about.gitlab.com/images/press/logo/preview/gitlab-logo-white-preview.png)"]
}
}
},
"commit": {
"type": "object",
"description": "A specific commit within the project",
"required": [ "type", "value" ],
"description": "A commit/tag/branch within the GitLab project",
"required": [
"type",
"value"
],
"properties": {
"type": { "const": "commit" },
"value": { "type": "string", "description": "The commit SHA" }
"type": {
"const": "commit"
},
"value": {
"type": "string",
"description": "The commit SHA",
"minLength": 1
}
}
},
"file_location": {
"type": "object",
"description": "A location within a file in the project",
"required": [ "type", "file_name", "line_start" ],
"required": [
"type",
"file_name",
"line_start"
],
"properties": {
"type": { "const": "file-location" },
"file_name": { "type": "string" },
"line_start": { "type": "integer" },
"line_end": { "type": "integer" }
"type": {
"const": "file-location"
},
"file_name": {
"type": "string",
"minLength": 1
},
"line_start": {
"type": "integer"
},
"line_end": {
"type": "integer"
}
}
},
"module_location": {
"type": "object",
"description": "A location within a binary module of the form module+relative_offset",
"required": [ "type", "module_name", "offset" ],
"required": [
"type",
"module_name",
"offset"
],
"properties": {
"type": { "const": "module-location" },
"module_name": { "type": "string" },
"offset": { "type": "integer" }
"type": {
"const": "module-location"
},
"module_name": {
"type": "string",
"minLength": 1,
"examples": ["compiled_binary"]
},
"offset": {
"type": "integer",
"examples": [100]
}
}
}
}

View File

@ -54,8 +54,7 @@
= Gon::Base.render_data(nonce: content_security_policy_nonce)
= javascript_include_tag locale_path unless I18n.locale == :en
-# Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
-# = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
= webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
= yield :page_specific_javascripts

View File

@ -26,4 +26,4 @@
= render "projects/pipelines/with_tabs", pipeline: @pipeline, pipeline_has_errors: pipeline_has_errors
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid } }
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json), metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid } }

View File

@ -0,0 +1,5 @@
---
title: Introduce WebIDE as an extension for Editor Lite
merge_request: 51527
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Enable customize homepage banner by default
merge_request: 54357
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add GlToggle label in sidebar subscription toggle
merge_request: 54548
author: Yogi (@yo)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix the npm instance level API to exclude subgroups
merge_request: 54554
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: convert to GlTabs in app/assets/javascripts/ide/components/repo_tabs.vue
merge_request: 42162
author: Brandon Everett
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix style issue with "reply" placeholder textarea in firefox
merge_request: 54592
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Restore Sentry functionaly to the frontend
merge_request: 54441
author:
type: changed

View File

@ -0,0 +1,8 @@
---
name: customize_homepage
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54357
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299895
milestone: '13.10'
type: development
group: group::expansion
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: customize_homepage_experiment_percentage
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39348
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/187
milestone: '13.4'
type: experiment
group: group::expansion
default_enabled: false

View File

@ -101,7 +101,7 @@ function generateEntries() {
const manualEntries = {
default: defaultEntries,
// sentry: './sentry/index.js', Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179
sentry: './sentry/index.js',
performance_bar: './performance_bar/index.js',
chrome_84_icon_fix: './lib/chrome_84_icon_fix.js',
jira_connect_app: './jira_connect/index.js',

View File

@ -103,8 +103,10 @@ class Gitlab::Seeder::Packages
name = "MyNugetApp.Package#{i}"
version = "4.2.#{i}"
pkg = ::Packages::Nuget::CreatePackageService.new(project, project.creator, {}).execute
# when using ::Packages::Nuget::CreatePackageService, packages have a fixed name and a fixed version.
pkg = ::Packages::CreateTemporaryPackageService.new(
project, project.creator, {}
).execute(:nuget, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME)
# when using ::Packages::CreateTemporaryPackageService, packages have a fixed name and a fixed version.
pkg.update!(name: name, version: version)
filename = 'package.nupkg'

View File

@ -5,160 +5,21 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, howto
---
# PlantUML & GitLab **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8537) in GitLab 8.16.
# PlantUML and GitLab **(FREE)**
When [PlantUML](https://plantuml.com) integration is enabled and configured in
GitLab you can create diagrams in AsciiDoc and Markdown documents
created in snippets, wikis, and repositories.
GitLab, you can create diagrams in snippets, wikis, and repositories. To set up
the integration, you must:
## PlantUML Server
1. [Configure your PlantUML server](#configure-your-plantuml-server).
1. [Configure local PlantUML access](#configure-local-plantuml-access).
1. [Configure PlantUML security](#configure-plantuml-security).
1. [Enable the integration](#enable-plantuml-integration).
Before you can enable PlantUML in GitLab; set up your own PlantUML
server to generate the diagrams.
### Docker
With Docker, you can just run a container like this:
```shell
docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat
```
The **PlantUML URL** is the hostname of the server running the container.
When running GitLab in Docker, it must have access to the PlantUML container.
You can achieve that by using [Docker Compose](https://docs.docker.com/compose/).
A basic `docker-compose.yml` file could contain:
```yaml
version: "3"
services:
gitlab:
image: 'gitlab/gitlab-ee:12.2.5-ee.0'
environment:
GITLAB_OMNIBUS_CONFIG: |
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n proxy_cache off; \n proxy_pass http://plantuml:8080/; \n}\n"
plantuml:
image: 'plantuml/plantuml-server:tomcat'
container_name: plantuml
```
In this scenario, PlantUML is accessible to GitLab at the URL
`http://plantuml:8080/`.
### Debian/Ubuntu
You can also install and configure a PlantUML server in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
```shell
sudo apt-get install graphviz openjdk-8-jdk git-core maven
git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
mvn package
```
The above sequence of commands generates a `.war` file you can deploy with Tomcat:
```shell
sudo apt-get install tomcat8
sudo cp target/plantuml.war /var/lib/tomcat8/webapps/plantuml.war
sudo chown tomcat8:tomcat8 /var/lib/tomcat8/webapps/plantuml.war
sudo service tomcat8 restart
```
After the Tomcat service restarts, the PlantUML service is ready and
listening for requests on port 8080:
```plaintext
http://localhost:8080/plantuml
```
To change these defaults, edit the `/etc/tomcat8/server.xml` file.
NOTE:
The default URL is different when using this approach. The Docker-based image
makes the service available at the root URL, with no relative path. Adjust
the configuration below accordingly.
### Making local PlantUML accessible using custom GitLab setup
The PlantUML server runs locally on your server, so it is not accessible
externally by default. As such, it is necessary to catch external PlantUML
calls and redirect them to the local server.
The idea is to redirect each call to `https://gitlab.example.com/-/plantuml/`
to the local PlantUML server `http://plantuml:8080/` or `http://localhost:8080/plantuml/`, depending on your setup.
To enable the redirection, add the following line in `/etc/gitlab/gitlab.rb`:
```ruby
# Docker deployment
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n proxy_cache off; \n proxy_pass http://plantuml:8080/; \n}\n"
# Built from source
nginx['custom_gitlab_server_config'] = "location /-/plantuml { \n rewrite ^/-/(plantuml.*) /$1 break;\n proxy_cache off; \n proxy_pass http://localhost:8080/plantuml; \n}\n"
```
To activate the changes, run the following command:
```shell
sudo gitlab-ctl reconfigure
```
Note that the redirection through GitLab must be configured
when running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl.html)
due to PlantUML's use of the insecure HTTP protocol. Newer browsers such
as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408)
do not load insecure HTTP resources on a page served over HTTPS.
### Security
PlantUML has features that allow fetching network resources.
```plaintext
@startuml
start
' ...
!include http://localhost/
stop;
@enduml
```
**If you self-host the PlantUML server, network controls should be put in place to isolate it.**
## GitLab
You need to enable PlantUML integration from Settings under Admin Area. To do
that, sign in with an Administrator account, and then do following:
1. In GitLab, go to **Admin Area > Settings > General**.
1. Expand the **PlantUML** section.
1. Select the **Enable PlantUML** check box.
1. Set the PlantUML instance as `https://gitlab.example.com/-/plantuml/`.
NOTE:
If you are using a PlantUML server running v1.2020.9 and
above (for example, [plantuml.com](https://plantuml.com)), set the `PLANTUML_ENCODING`
environment variable to enable the `deflate` compression. On Omnibus GitLab,
this can be set in `/etc/gitlab.rb`:
```ruby
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
```
From GitLab 13.1 and later, PlantUML integration now
[requires a header prefix in the URL](https://github.com/plantuml/plantuml/issues/117#issuecomment-6235450160)
to distinguish different encoding types.
## Creating Diagrams
With PlantUML integration enabled and configured, we can start adding diagrams to
our AsciiDoc snippets, wikis, and repositories using delimited blocks:
After completing the integration, PlantUML converts `plantuml`
blocks to an HTML image tag, with the source pointing to the PlantUML instance. The PlantUML
diagram delimiters `@startuml`/`@enduml` aren't required, as these are replaced
by the `plantuml` block:
- **Markdown**
@ -189,13 +50,12 @@ our AsciiDoc snippets, wikis, and repositories using delimited blocks:
Alice -> Bob: hi
```
You can also use the `uml::` directive for compatibility with
Although you can use the `uml::` directive for compatibility with
[`sphinxcontrib-plantuml`](https://pypi.org/project/sphinxcontrib-plantuml/),
but GitLab only supports the `caption` option.
GitLab supports only the `caption` option.
The above blocks are converted to an HTML image tag with source pointing to the
PlantUML instance. If the PlantUML server is correctly configured, this should
render a nice diagram instead of the block:
If the PlantUML server is correctly configured, these examples should render a
diagram instead of the code block:
```plantuml
Bob -> Alice : hello
@ -204,23 +64,166 @@ Alice -> Bob : hi
Inside the block you can add any of the diagrams PlantUML supports, such as:
- [Sequence](https://plantuml.com/sequence-diagram)
- [Use Case](https://plantuml.com/use-case-diagram)
- [Class](https://plantuml.com/class-diagram)
- [Activity](https://plantuml.com/activity-diagram-legacy)
- [Class](https://plantuml.com/class-diagram)
- [Component](https://plantuml.com/component-diagram)
- [State](https://plantuml.com/state-diagram),
- [Object](https://plantuml.com/object-diagram)
- [Sequence](https://plantuml.com/sequence-diagram)
- [State](https://plantuml.com/state-diagram)
- [Use Case](https://plantuml.com/use-case-diagram)
You do not need to use the PlantUML
diagram delimiters `@startuml`/`@enduml`, as these are replaced by the AsciiDoc `plantuml` block.
You can add parameters to block definitions:
Some parameters can be added to the AsciiDoc block definition:
- `format`: Can be either `png` or `svg`. Note that `svg` is not supported by
all browsers so use with care. The default is `png`.
- `format`: Can be either `png` (default) or `svg`. Use `svg` with care, as it's
not supported by all browsers, and isn't supported by Markdown.
- `id`: A CSS ID added to the diagram HTML tag.
- `width`: Width attribute added to the image tag.
- `height`: Height attribute added to the image tag.
Markdown does not support any parameters and always uses PNG format.
Markdown does not support any parameters, and always uses PNG format.
## Configure your PlantUML server
Before you can enable PlantUML in GitLab, set up your own PlantUML
server to generate the diagrams:
- [In Docker](#docker).
- [In Debian/Ubuntu](#debianubuntu).
### Docker
To run a PlantUML container in Docker, run this command:
```shell
docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat
```
The **PlantUML URL** is the hostname of the server running the container.
When running GitLab in Docker, it must have access to the PlantUML container.
To achieve that, use [Docker Compose](https://docs.docker.com/compose/).
In this basic `docker-compose.yml` file, PlantUML is accessible to GitLab at the URL
`http://plantuml:8080/`:
```yaml
version: "3"
services:
gitlab:
image: 'gitlab/gitlab-ee:12.2.5-ee.0'
environment:
GITLAB_OMNIBUS_CONFIG: |
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n proxy_cache off; \n proxy_pass http://plantuml:8080/; \n}\n"
plantuml:
image: 'plantuml/plantuml-server:tomcat'
container_name: plantuml
```
### Debian/Ubuntu
You can install and configure a PlantUML server in Debian/Ubuntu distributions
using Tomcat:
1. Run these commands to create a `plantuml.war` file from the source code:
```shell
sudo apt-get install graphviz openjdk-8-jdk git-core maven
git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
mvn package
```
1. Deploy the `.war` file from the previous step with these commands:
```shell
sudo apt-get install tomcat8
sudo cp target/plantuml.war /var/lib/tomcat8/webapps/plantuml.war
sudo chown tomcat8:tomcat8 /var/lib/tomcat8/webapps/plantuml.war
sudo service tomcat8 restart
```
The Tomcat service should restart. After the restart is complete, the
PlantUML service is ready and listening for requests on port 8080:
`http://localhost:8080/plantuml`
To change these defaults, edit the `/etc/tomcat8/server.xml` file.
NOTE:
The default URL is different when using this approach. The Docker-based image
makes the service available at the root URL, with no relative path. Adjust
the configuration below accordingly.
## Configure local PlantUML access
The PlantUML server runs locally on your server, so it can't be accessed
externally by default. Your server must catch external PlantUML
calls to `https://gitlab.example.com/-/plantuml/` and redirect them to the
local PlantUML server. Depending on your setup, the URL is either of the
following:
- `http://plantuml:8080/`
- `http://localhost:8080/plantuml/`
If you're running [GitLab with TLS](https://docs.gitlab.com/omnibus/settings/ssl.html)
you must configure this redirection, because PlantUML uses the insecure HTTP protocol.
Newer browsers such as [Google Chrome 86+](https://www.chromestatus.com/feature/4926989725073408)
don't load insecure HTTP resources on pages served over HTTPS.
To enable this redirection:
1. Add the following line in `/etc/gitlab/gitlab.rb`, depending on your setup method:
```ruby
# Docker deployment
nginx['custom_gitlab_server_config'] = "location /-/plantuml/ { \n proxy_cache off; \n proxy_pass http://plantuml:8080/; \n}\n"
# Built from source
nginx['custom_gitlab_server_config'] = "location /-/plantuml { \n rewrite ^/-/(plantuml.*) /$1 break;\n proxy_cache off; \n proxy_pass http://localhost:8080/plantuml; \n}\n"
```
1. To activate the changes, run the following command:
```shell
sudo gitlab-ctl reconfigure
```
### Configure PlantUML security
PlantUML has features that allow fetching network resources. If you self-host the
PlantUML server, put network controls in place to isolate it.
```plaintext
@startuml
start
' ...
!include http://localhost/
stop;
@enduml
```
## Enable PlantUML integration
After configuring your local PlantUML server, you're ready to enable the PlantUML integration:
1. Sign in to GitLab as an [Administrator](../../user/permissions.md) user.
1. In the top menu, click **{admin}** **Admin Area**.
1. In the left sidebar, go to **Settings > General** and expand the **PlantUML** section.
1. Select the **Enable PlantUML** check box.
1. Set the PlantUML instance as `https://gitlab.example.com/-/plantuml/`,
and click **Save changes**.
Depending on your PlantUML and GitLab version numbers, you may also need to take
these steps:
- For PlantUML servers running v1.2020.9 and above, such as [plantuml.com](https://plantuml.com),
you must set the `PLANTUML_ENCODING` environment variable to enable the `deflate`
compression. In Omnibus GitLab, you can set this value in `/etc/gitlab.rb` with
this command:
```ruby
gitlab_rails['env'] = { 'PLANTUML_ENCODING' => 'deflate' }
```
- For GitLab versions 13.1 and later, PlantUML integration now
[requires a header prefix in the URL](https://github.com/plantuml/plantuml/issues/117#issuecomment-6235450160)
to distinguish different encoding types.

View File

@ -1603,7 +1603,7 @@ enum BlobViewersType {
}
"""
Represents a project or group board
Represents a project or group issue board
"""
type Board {
"""
@ -9490,7 +9490,7 @@ type EpicBoard {
hideClosedList: Boolean
"""
Global ID of the board.
Global ID of the epic board.
"""
id: BoardsEpicBoardID!
@ -9525,9 +9525,19 @@ type EpicBoard {
): EpicListConnection
"""
Name of the board.
Name of the epic board.
"""
name: String
"""
Web path of the epic board.
"""
webPath: String!
"""
Web URL of the epic board.
"""
webUrl: String!
}
"""
@ -23855,8 +23865,19 @@ type SastCiConfigurationOptionsEntityEdge {
Size of UI component in SAST configuration page
"""
enum SastUiComponentSize {
"""
The size of UI component in SAST configuration page is large.
"""
LARGE
"""
The size of UI component in SAST configuration page is medium.
"""
MEDIUM
"""
The size of UI component in SAST configuration page is small.
"""
SMALL
}
@ -28452,7 +28473,7 @@ type VulnerabilityDetailBase {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28462,7 +28483,7 @@ type VulnerabilityDetailBase {
"""
Name of the field.
"""
name: String!
name: String
}
"""
@ -28472,7 +28493,7 @@ type VulnerabilityDetailBoolean {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28482,7 +28503,7 @@ type VulnerabilityDetailBoolean {
"""
Name of the field.
"""
name: String!
name: String
"""
Value of the field.
@ -28497,7 +28518,7 @@ type VulnerabilityDetailCode {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28512,7 +28533,7 @@ type VulnerabilityDetailCode {
"""
Name of the field.
"""
name: String!
name: String
"""
Source code.
@ -28527,7 +28548,7 @@ type VulnerabilityDetailCommit {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28537,7 +28558,7 @@ type VulnerabilityDetailCommit {
"""
Name of the field.
"""
name: String!
name: String
"""
The commit SHA value.
@ -28562,7 +28583,7 @@ type VulnerabilityDetailDiff {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28572,7 +28593,7 @@ type VulnerabilityDetailDiff {
"""
Name of the field.
"""
name: String!
name: String
}
"""
@ -28582,7 +28603,7 @@ type VulnerabilityDetailFileLocation {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28607,7 +28628,7 @@ type VulnerabilityDetailFileLocation {
"""
Name of the field.
"""
name: String!
name: String
}
"""
@ -28617,7 +28638,7 @@ type VulnerabilityDetailInt {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28627,7 +28648,7 @@ type VulnerabilityDetailInt {
"""
Name of the field.
"""
name: String!
name: String
"""
Value of the field.
@ -28642,7 +28663,7 @@ type VulnerabilityDetailList {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28657,7 +28678,7 @@ type VulnerabilityDetailList {
"""
Name of the field.
"""
name: String!
name: String
}
"""
@ -28667,7 +28688,7 @@ type VulnerabilityDetailMarkdown {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28677,7 +28698,7 @@ type VulnerabilityDetailMarkdown {
"""
Name of the field.
"""
name: String!
name: String
"""
Value of the Markdown field.
@ -28692,7 +28713,7 @@ type VulnerabilityDetailModuleLocation {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28707,7 +28728,7 @@ type VulnerabilityDetailModuleLocation {
"""
Name of the field.
"""
name: String!
name: String
"""
Offset of the module location.
@ -28722,7 +28743,7 @@ type VulnerabilityDetailTable {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28737,7 +28758,7 @@ type VulnerabilityDetailTable {
"""
Name of the field.
"""
name: String!
name: String
"""
Table rows.
@ -28752,7 +28773,7 @@ type VulnerabilityDetailText {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28762,7 +28783,7 @@ type VulnerabilityDetailText {
"""
Name of the field.
"""
name: String!
name: String
"""
Value of the text field.
@ -28777,7 +28798,7 @@ type VulnerabilityDetailUrl {
"""
Description of the field.
"""
description: String!
description: String
"""
Name of the field.
@ -28792,7 +28813,7 @@ type VulnerabilityDetailUrl {
"""
Name of the field.
"""
name: String!
name: String
"""
Text of the URL.

View File

@ -4260,7 +4260,7 @@
{
"kind": "OBJECT",
"name": "Board",
"description": "Represents a project or group board",
"description": "Represents a project or group issue board",
"fields": [
{
"name": "assignee",
@ -26126,7 +26126,7 @@
},
{
"name": "id",
"description": "Global ID of the board.",
"description": "Global ID of the epic board.",
"args": [
],
@ -26207,7 +26207,7 @@
},
{
"name": "name",
"description": "Name of the board.",
"description": "Name of the epic board.",
"args": [
],
@ -26218,6 +26218,42 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "webPath",
"description": "Web path of the epic board.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "webUrl",
"description": "Web URL of the epic board.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -68666,19 +68702,19 @@
"enumValues": [
{
"name": "SMALL",
"description": null,
"description": "The size of UI component in SAST configuration page is small.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "MEDIUM",
"description": null,
"description": "The size of UI component in SAST configuration page is medium.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "LARGE",
"description": null,
"description": "The size of UI component in SAST configuration page is large.",
"isDeprecated": false,
"deprecationReason": null
}
@ -81695,13 +81731,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81727,13 +81759,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81758,13 +81786,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81790,13 +81814,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81839,13 +81859,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81885,13 +81901,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81903,13 +81915,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81934,13 +81942,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81966,13 +81970,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -81984,13 +81984,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82015,13 +82011,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82033,13 +82025,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82051,13 +82039,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82083,13 +82067,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82114,13 +82094,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82146,13 +82122,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82200,13 +82172,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82231,13 +82199,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82263,13 +82227,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82312,13 +82272,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82370,13 +82326,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82401,13 +82353,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82433,13 +82381,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82451,13 +82395,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82482,13 +82422,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
@ -82514,13 +82450,9 @@
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null

View File

@ -438,7 +438,7 @@ Autogenerated return type of AwardEmojiToggle.
### Board
Represents a project or group board.
Represents a project or group issue board.
| Field | Type | Description |
| ----- | ---- | ----------- |
@ -1699,9 +1699,11 @@ Represents an epic board.
| ----- | ---- | ----------- |
| `hideBacklogList` | Boolean | Whether or not backlog list is hidden. |
| `hideClosedList` | Boolean | Whether or not closed list is hidden. |
| `id` | BoardsEpicBoardID! | Global ID of the board. |
| `id` | BoardsEpicBoardID! | Global ID of the epic board. |
| `lists` | EpicListConnection | Epic board lists. |
| `name` | String | Name of the board. |
| `name` | String | Name of the epic board. |
| `webPath` | String! | Web path of the epic board. |
| `webUrl` | String! | Web URL of the epic board. |
### EpicBoardCreatePayload
@ -4333,9 +4335,9 @@ Represents the vulnerability details base.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
### VulnerabilityDetailBoolean
@ -4343,9 +4345,9 @@ Represents the vulnerability details boolean value.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | Boolean! | Value of the field. |
### VulnerabilityDetailCode
@ -4354,10 +4356,10 @@ Represents the vulnerability details code field.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `lang` | String | Language of the code. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | String! | Source code. |
### VulnerabilityDetailCommit
@ -4366,9 +4368,9 @@ Represents the vulnerability details commit field.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | String! | The commit SHA value. |
### VulnerabilityDetailDiff
@ -4379,9 +4381,9 @@ Represents the vulnerability details diff field.
| ----- | ---- | ----------- |
| `after` | String! | Value of the field after the change. |
| `before` | String! | Value of the field before the change. |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
### VulnerabilityDetailFileLocation
@ -4389,12 +4391,12 @@ Represents the vulnerability details location within a file in the project.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `fileName` | String! | File name. |
| `lineEnd` | Int! | End line number of the file location. |
| `lineStart` | Int! | Start line number of the file location. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
### VulnerabilityDetailInt
@ -4402,9 +4404,9 @@ Represents the vulnerability details integer value.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | Int! | Value of the field. |
### VulnerabilityDetailList
@ -4413,10 +4415,10 @@ Represents the vulnerability details list value.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `items` | VulnerabilityDetail! => Array | List of details. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
### VulnerabilityDetailMarkdown
@ -4424,9 +4426,9 @@ Represents the vulnerability details Markdown field.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | String! | Value of the Markdown field. |
### VulnerabilityDetailModuleLocation
@ -4435,10 +4437,10 @@ Represents the vulnerability details location within a file in the project.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `moduleName` | String! | Module name. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `offset` | Int! | Offset of the module location. |
### VulnerabilityDetailTable
@ -4447,10 +4449,10 @@ Represents the vulnerability details table value.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `headers` | VulnerabilityDetail! => Array | Table headers. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `rows` | VulnerabilityDetail! => Array | Table rows. |
### VulnerabilityDetailText
@ -4459,9 +4461,9 @@ Represents the vulnerability details text field.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `value` | String! | Value of the text field. |
### VulnerabilityDetailUrl
@ -4470,10 +4472,10 @@ Represents the vulnerability details URL field.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `description` | String! | Description of the field. |
| `description` | String | Description of the field. |
| `fieldName` | String | Name of the field. |
| `href` | String! | Href of the URL. |
| `name` | String! | Name of the field. |
| `name` | String | Name of the field. |
| `text` | String | Text of the URL. |
### VulnerabilityDismissPayload
@ -5470,9 +5472,9 @@ Size of UI component in SAST configuration page.
| Value | Description |
| ----- | ----------- |
| `LARGE` | |
| `MEDIUM` | |
| `SMALL` | |
| `LARGE` | The size of UI component in SAST configuration page is large. |
| `MEDIUM` | The size of UI component in SAST configuration page is medium. |
| `SMALL` | The size of UI component in SAST configuration page is small. |
### SecurityReportTypeEnum

View File

@ -1061,7 +1061,7 @@ POST /projects
| `avatar` | mixed | **{dotted-circle}** No | Image file for avatar of the project. |
| `build_coverage_regex` | string | **{dotted-circle}** No | Test coverage parsing. |
| `build_git_strategy` | string | **{dotted-circle}** No | The Git strategy. Defaults to `fetch`. |
| `build_timeout` | integer | **{dotted-circle}** No | The maximum amount of time in minutes that a job is able run (in seconds). |
| `build_timeout` | integer | **{dotted-circle}** No | The maximum amount of time, in seconds, that a job can run. |
| `builds_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. |
| `ci_config_path` | string | **{dotted-circle}** No | The path to CI configuration file. |
| `container_expiration_policy_attributes` | hash | **{dotted-circle}** No | Update the image cleanup policy for this project. Accepts: `cadence` (string), `keep_n` (integer), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean). |

View File

@ -242,6 +242,27 @@ class BatchedMigrationName < Elastic::Migration
end
```
### Multi-version compatibility
These Elasticsearch migrations, like any other GitLab changes, need to support the case where
[multiple versions of the application are running at the same time](multi_version_compatibility.md).
Depending on the order of deployment, it's possible that the migration
has started or finished and there's still a server running the application code from before the
migration. We need to take this into consideration until we can [ensure all Elasticsearch migrations
start after the deployment has finished](https://gitlab.com/gitlab-org/gitlab/-/issues/321619).
### Reverting a migration
Because Elasticsearch does not support transactions, we always need to design our
migrations to accommodate a situation where the application
code is reverted after the migration has started or after it is finished.
For this reason we generally defer destructive actions (for example, deletions after
some data is moved) to a later merge request after the migrations have
completed successfully. To be safe, for self-managed customers we should also
defer it to another release if there is risk of important data loss.
## Performance Monitoring
### Prometheus

View File

@ -98,6 +98,57 @@ When declaring multiple globals, always use one `/* global [name] */` line per v
/* global jQuery */
```
### Deprecating functions with `import/no-deprecated`
Our `@gitlab/eslint-plugin` Node module contains the [`eslint-plugin-import`](https://gitlab.com/gitlab-org/frontend/eslint-plugin) package.
We can use the [`import/no-deprecated`](https://github.com/benmosher/eslint-plugin-import/blob/HEAD/docs/rules/no-deprecated.md) rule to deprecate functions using a JSDoc block with a `@deprecated` tag:
```javascript
/**
* Convert search query into an object
*
* @param {String} query from "document.location.search"
* @param {Object} options
* @param {Boolean} options.gatherArrays - gather array values into an Array
* @returns {Object}
*
* ex: "?one=1&two=2" into {one: 1, two: 2}
* @deprecated Please use `queryToObject` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/283982 for more information
*/
export function queryToObject(query, options = {}) {
...
}
```
It is strongly encouraged that you:
- Put in an **alternative path for developers** looking to use this function.
- **Provide a link to the issue** that tracks the migration process.
NOTE:
Uses are detected if you import the deprecated function into another file. They are not detected when the function is used in the same file.
Running `$ yarn eslint` after this will give us the list of deprecated usages:
```shell
$ yarn eslint
./app/assets/javascripts/issuable_form.js
9:10 error Deprecated: Please use `queryToObject` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/283982 for more information import/no-deprecated
33:23 error Deprecated: Please use `queryToObject` instead. See https://gitlab.com/gitlab-org/gitlab/-/issues/283982 for more information import/no-deprecated
...
```
Grep for disabled cases of this rule to generate a working list to create issues from, so you can track the effort of removing deprecated uses:
```shell
$ grep "eslint-disable.*import/no-deprecated" -r .
./app/assets/javascripts/issuable_form.js:import { queryToObject, objectToQuery } from './lib/utils/url_utility'; // eslint-disable-line import/no-deprecate
./app/assets/javascripts/issuable_form.js: // eslint-disable-next-line import/no-deprecated
```
## Formatting with Prettier
> Support for `.graphql` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227280) in GitLab 13.2.

View File

@ -35,7 +35,7 @@ Access the default page for admin area settings by navigating to **Admin Area >
| ------ | ----------- |
| [Elasticsearch](../../../integration/elasticsearch.md#enabling-advanced-search) | Elasticsearch integration. Elasticsearch AWS IAM. |
| [Kroki](../../../administration/integration/kroki.md#enable-kroki-in-gitlab) | Allow rendering of diagrams in AsciiDoc and Markdown documents using [kroki.io](https://kroki.io). |
| [PlantUML](../../../administration/integration/plantuml.md#gitlab) | Allow rendering of PlantUML diagrams in AsciiDoc and Markdown documents. |
| [PlantUML](../../../administration/integration/plantuml.md) | Allow rendering of PlantUML diagrams in documents. |
| [Slack application](../../../user/project/integrations/gitlab_slack_application.md#configuration) **(FREE SAAS)** | Slack integration allows you to interact with GitLab via slash commands in a chat window. This option is only available on GitLab.com, though it may be [available for self-managed instances in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/28164). |
| [Third party offers](third_party_offers.md) | Control the display of third party offers. |
| [Snowplow](../../../development/snowplow.md) | Configure the Snowplow integration. |

View File

@ -52,7 +52,8 @@ module API
namespace_path = namespace_path_from_package_name
next unless namespace_path
namespace = namespace_from_path(namespace_path)
namespace = Namespace.top_most
.by_path(namespace_path)
next unless namespace
finder = ::Packages::Npm::PackageFinder.new(params[:package_name], namespace: namespace)
@ -70,13 +71,6 @@ module API
package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first
end
def namespace_from_path(path)
group = Group.by_path(path)
return group if group
Namespace.for_user.by_path(path)
end
end
end
end

View File

@ -62,8 +62,9 @@ module API
file_name: PACKAGE_FILENAME
)
package = ::Packages::Nuget::CreatePackageService.new(project_or_group, current_user, declared_params.merge(build: current_authenticated_job))
.execute
package = ::Packages::CreateTemporaryPackageService.new(
project_or_group, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:nuget, name: ::Packages::Nuget::TEMPORARY_PACKAGE_NAME)
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute

View File

@ -12,7 +12,7 @@ module API
# The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
# Updating the version should require a GitLab API version change.
MARSHAL_VERSION = '4.8'
PACKAGE_FILENAME = 'package.gem'
FILE_NAME_REQUIREMENTS = {
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
@ -73,16 +73,45 @@ module API
detail 'This feature was introduced in GitLab 13.9'
end
post 'gems/authorize' do
# To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263
not_found!
authorize_workhorse!(
subject: user_project,
has_length: false,
maximum_size: user_project.actual_limits.rubygems_max_file_size
)
end
desc 'Upload a gem' do
detail 'This feature was introduced in GitLab 13.9'
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end
post 'gems' do
# To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263
not_found!
authorize_upload!(user_project)
bad_request!('File is too large') if user_project.actual_limits.exceeded?(:rubygems_max_file_size, params[:file].size)
track_package_event('push_package', :rubygems)
ActiveRecord::Base.transaction do
package = ::Packages::CreateTemporaryPackageService.new(
user_project, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:rubygems, name: ::Packages::Rubygems::TEMPORARY_PACKAGE_NAME)
file_params = {
file: params[:file],
file_name: PACKAGE_FILENAME
}
::Packages::CreatePackageFileService.new(
package, file_params.merge(build: current_authenticated_job)
).execute
end
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: user_project.id })
forbidden!
end
desc 'Fetch a list of dependencies' do

View File

@ -172,6 +172,7 @@ apifuzzer_fuzz_dnd:
-e FUZZAPI_HAR \
-e FUZZAPI_OPENAPI \
-e FUZZAPI_POSTMAN_COLLECTION \
-e FUZZAPI_POSTMAN_COLLECTION_VARIABLES \
-e FUZZAPI_TARGET_URL \
-e FUZZAPI_OVERRIDES_FILE \
-e FUZZAPI_OVERRIDES_ENV \
@ -214,6 +215,7 @@ apifuzzer_fuzz_dnd:
-e FUZZAPI_HAR \
-e FUZZAPI_OPENAPI \
-e FUZZAPI_POSTMAN_COLLECTION \
-e FUZZAPI_POSTMAN_COLLECTION_VARIABLES \
-e FUZZAPI_TARGET_URL \
-e FUZZAPI_OVERRIDES_FILE \
-e FUZZAPI_OVERRIDES_ENV \

View File

@ -58,10 +58,6 @@ module Gitlab
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp',
use_backwards_compatible_subject_index: true
},
customize_homepage: {
tracking_category: 'Growth::Expansion::Experiment::CustomizeHomepage',
use_backwards_compatible_subject_index: true
},
group_only_trials: {
tracking_category: 'Growth::Conversion::Experiment::GroupOnlyTrials',
use_backwards_compatible_subject_index: true

View File

@ -41,6 +41,9 @@
- i_package_pypi_delete_package
- i_package_pypi_pull_package
- i_package_pypi_push_package
- i_package_rubygems_delete_package
- i_package_rubygems_pull_package
- i_package_rubygems_push_package
- i_package_tag_delete_package
- i_package_tag_pull_package
- i_package_tag_push_package

View File

@ -99,6 +99,16 @@
aggregation: weekly
redis_slot: package
feature_flag: collect_package_events_redis
- name: i_package_rubygems_deploy_token
category: deploy_token_packages
aggregation: weekly
redis_slot: package
feature_flag: collect_package_events_redis
- name: i_package_rubygems_user
category: user_packages
aggregation: weekly
redis_slot: package
feature_flag: collect_package_events_redis
- name: i_package_tag_deploy_token
category: deploy_token_packages
aggregation: weekly

View File

@ -10772,6 +10772,9 @@ msgstr ""
msgid "Drop or %{linkStart}upload%{linkEnd} designs to attach"
msgstr ""
msgid "Drop or %{linkStart}upload%{linkEnd} file to attach"
msgstr ""
msgid "Drop or %{linkStart}upload%{linkEnd} files to attach"
msgstr ""

View File

@ -58,6 +58,7 @@
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
"@sentry/browser": "^5.22.3",
"@sourcegraph/code-host-integration": "0.0.52",
"@toast-ui/editor": "^2.5.1",
"@toast-ui/vue-editor": "^2.5.1",
@ -164,7 +165,7 @@
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.10.1",
"@gitlab/eslint-plugin": "8.0.0",
"@gitlab/eslint-plugin": "8.1.0",
"@gitlab/stylelint-config": "^2.2.0",
"@testing-library/dom": "^7.16.2",
"@vue/test-utils": "1.1.2",
@ -177,17 +178,17 @@
"commander": "^2.18.0",
"custom-jquery-matchers": "^2.1.0",
"docdash": "^1.0.2",
"eslint": "7.19.0",
"eslint": "7.20.0",
"eslint-import-resolver-jest": "3.0.0",
"eslint-import-resolver-webpack": "0.13.0",
"eslint-plugin-jasmine": "4.1.2",
"eslint-plugin-no-jquery": "2.5.0",
"gettext-extractor": "^3.5.3",
"gettext-extractor-vue": "^5.0.0",
"glob": "^7.1.6",
"istanbul-lib-coverage": "^3.0.0",
"istanbul-lib-report": "^3.0.0",
"istanbul-reports": "^3.0.0",
"glob": "^7.1.6",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",

View File

@ -54,7 +54,7 @@ module QA
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
image: mcr.microsoft.com/dotnet/core/sdk:3.1
image: mcr.microsoft.com/dotnet/sdk:5.0
stages:
- deploy
@ -102,7 +102,7 @@ module QA
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
@ -115,7 +115,7 @@ module QA
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
image: mcr.microsoft.com/dotnet/core/sdk:3.1
image: mcr.microsoft.com/dotnet/sdk:5.0
stages:
- install

View File

@ -123,11 +123,7 @@ RSpec.describe RootController do
expect(response).to render_template 'dashboard/projects/index'
end
context 'when experiment is enabled' do
before do
stub_experiment_for_subject(customize_homepage: true)
end
context 'when customize_homepage is enabled' do
it 'renders the default dashboard' do
get :index
@ -135,9 +131,9 @@ RSpec.describe RootController do
end
end
context 'when experiment not enabled' do
context 'when customize_homepage is not enabled' do
before do
stub_experiment(customize_homepage: false)
stub_feature_flags(customize_homepage: false)
end
it 'renders the default dashboard' do

View File

@ -12,7 +12,7 @@ RSpec.describe 'Sentry' do
expect(has_requested_sentry).to eq(false)
end
xit 'loads sentry if sentry is enabled' do
it 'loads sentry if sentry is enabled' do
stub_sentry_settings
visit new_user_session_path

View File

@ -122,7 +122,7 @@ RSpec.describe Packages::GroupPackagesFinder do
end
context 'when there are processing packages' do
let_it_be(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
let_it_be(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([package1, package2]) }
end

View File

@ -14,7 +14,7 @@ RSpec.describe ::Packages::PackageFinder do
it { is_expected.to eq(maven_package) }
context 'processing packages' do
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
let(:package_id) { nuget_package.id }
it 'are not returned' do

View File

@ -76,7 +76,7 @@ RSpec.describe ::Packages::PackagesFinder do
end
context 'with processing packages' do
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([conan_package, maven_package]) }
end

View File

@ -4,12 +4,12 @@ import {
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlTable,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Clusters from '~/clusters_list/components/clusters.vue';
import ClusterStore from '~/clusters_list/store';
import axios from '~/lib/utils/axios_utils';
import * as Sentry from '~/sentry/wrapper';
import { apiData } from '../mock_data';
describe('Clusters', () => {

View File

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/browser';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import waitForPromises from 'helpers/wait_for_promises';
@ -7,7 +8,6 @@ import * as types from '~/clusters_list/store/mutation_types';
import { deprecatedCreateFlash as flashError } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
import * as Sentry from '~/sentry/wrapper';
import { apiData } from '../mock_data';
jest.mock('~/flash.js');

View File

@ -275,24 +275,28 @@ describe('DiffsStoreUtils', () => {
describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => {
// eslint-disable-next-line import/no-deprecated
expect(utils.trimFirstCharOfLineContent({ rich_text: ' diff' })).toEqual({
rich_text: 'diff',
});
});
it('trims the line when it starts with a +', () => {
// eslint-disable-next-line import/no-deprecated
expect(utils.trimFirstCharOfLineContent({ rich_text: '+diff' })).toEqual({
rich_text: 'diff',
});
});
it('trims the line when it starts with a -', () => {
// eslint-disable-next-line import/no-deprecated
expect(utils.trimFirstCharOfLineContent({ rich_text: '-diff' })).toEqual({
rich_text: 'diff',
});
});
it('does not trims the line when it starts with a letter', () => {
// eslint-disable-next-line import/no-deprecated
expect(utils.trimFirstCharOfLineContent({ rich_text: 'diff' })).toEqual({
rich_text: 'diff',
});
@ -303,12 +307,14 @@ describe('DiffsStoreUtils', () => {
rich_text: ' diff',
};
// eslint-disable-next-line import/no-deprecated
utils.trimFirstCharOfLineContent(lineObj);
expect(lineObj).toEqual({ rich_text: ' diff' });
});
it('handles a undefined or null parameter', () => {
// eslint-disable-next-line import/no-deprecated
expect(utils.trimFirstCharOfLineContent()).toEqual({});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
import { GlTab } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { stubComponent } from 'helpers/stub_component';
import RepoTab from '~/ide/components/repo_tab.vue';
import { createRouter } from '~/ide/ide_router';
import { createStore } from '~/ide/stores';
@ -8,16 +10,25 @@ import { file } from '../helpers';
const localVue = createLocalVue();
localVue.use(Vuex);
const GlTabStub = stubComponent(GlTab, {
template: '<li><slot name="title" /></li>',
});
describe('RepoTab', () => {
let wrapper;
let store;
let router;
const findTab = () => wrapper.find(GlTabStub);
function createComponent(propsData) {
wrapper = mount(RepoTab, {
localVue,
store,
propsData,
stubs: {
GlTab: GlTabStub,
},
});
}
@ -55,7 +66,7 @@ describe('RepoTab', () => {
jest.spyOn(wrapper.vm, 'openPendingTab').mockImplementation(() => {});
await wrapper.trigger('click');
await findTab().vm.$emit('click');
expect(wrapper.vm.openPendingTab).not.toHaveBeenCalled();
});
@ -67,7 +78,7 @@ describe('RepoTab', () => {
jest.spyOn(wrapper.vm, 'clickFile').mockImplementation(() => {});
wrapper.trigger('click');
findTab().vm.$emit('click');
expect(wrapper.vm.clickFile).toHaveBeenCalledWith(wrapper.vm.tab);
});
@ -91,11 +102,11 @@ describe('RepoTab', () => {
tab,
});
await wrapper.trigger('mouseover');
await findTab().vm.$emit('mouseover');
expect(wrapper.find('.file-modified').exists()).toBe(false);
await wrapper.trigger('mouseout');
await findTab().vm.$emit('mouseout');
expect(wrapper.find('.file-modified').exists()).toBe(true);
});

View File

@ -1,5 +1,15 @@
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import {
PIPELINES_DETAIL_LINK_DURATION,
PIPELINES_DETAIL_LINKS_TOTAL,
PIPELINES_DETAIL_LINKS_JOB_RATIO,
} from '~/performance/constants';
import * as perfUtils from '~/performance/utils';
import * as sentryUtils from '~/pipelines/components/graph/utils';
import * as Api from '~/pipelines/components/graph_shared/api';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import { createJobsHash } from '~/pipelines/utils';
import {
@ -18,7 +28,9 @@ describe('Links Inner component', () => {
containerMeasurements: { width: 1019, height: 445 },
pipelineId: 1,
pipelineData: [],
totalGroups: 10,
};
let wrapper;
const createComponent = (props) => {
@ -194,4 +206,141 @@ describe('Links Inner component', () => {
expect(firstLink.classes(hoverColorClass)).toBe(true);
});
});
describe('performance metrics', () => {
let markAndMeasure;
let reportToSentry;
let reportPerformance;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
reportPerformance = jest.spyOn(Api, 'reportPerformance');
});
afterEach(() => {
mock.restore();
});
describe('with no metrics config object', () => {
beforeEach(() => {
setFixtures(pipelineData);
createComponent({
pipelineData: pipelineData.stages,
});
});
it('is not called', () => {
expect(markAndMeasure).not.toHaveBeenCalled();
expect(reportToSentry).not.toHaveBeenCalled();
expect(reportPerformance).not.toHaveBeenCalled();
});
});
describe('with metrics config set to false', () => {
beforeEach(() => {
setFixtures(pipelineData);
createComponent({
pipelineData: pipelineData.stages,
metricsConfig: {
collectMetrics: false,
metricsPath: '/path/to/metrics',
},
});
});
it('is not called', () => {
expect(markAndMeasure).not.toHaveBeenCalled();
expect(reportToSentry).not.toHaveBeenCalled();
expect(reportPerformance).not.toHaveBeenCalled();
});
});
describe('with no metrics path', () => {
beforeEach(() => {
setFixtures(pipelineData);
createComponent({
pipelineData: pipelineData.stages,
metricsConfig: {
collectMetrics: true,
metricsPath: '',
},
});
});
it('is not called', () => {
expect(markAndMeasure).not.toHaveBeenCalled();
expect(reportToSentry).not.toHaveBeenCalled();
expect(reportPerformance).not.toHaveBeenCalled();
});
});
describe('with metrics path and collect set to true', () => {
const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
const duration = 0.0478;
const numLinks = 1;
const metricsData = {
histograms: [
{ name: PIPELINES_DETAIL_LINK_DURATION, value: duration },
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
{
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
value: numLinks / defaultProps.totalGroups,
},
],
};
describe('when no duration is obtained', () => {
beforeEach(() => {
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
return [];
});
setFixtures(pipelineData);
createComponent({
pipelineData: pipelineData.stages,
metricsConfig: {
collectMetrics: true,
path: metricsPath,
},
});
});
it('attempts to collect metrics', () => {
expect(markAndMeasure).toHaveBeenCalled();
expect(reportPerformance).not.toHaveBeenCalled();
expect(reportToSentry).not.toHaveBeenCalled();
});
});
describe('with duration and no error', () => {
beforeEach(() => {
jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
return [{ duration }];
});
setFixtures(pipelineData);
createComponent({
pipelineData: pipelineData.stages,
metricsConfig: {
collectMetrics: true,
path: metricsPath,
},
});
});
it('it calls reportPerformance with expected arguments', () => {
expect(markAndMeasure).toHaveBeenCalled();
expect(reportPerformance).toHaveBeenCalled();
expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
expect(reportToSentry).not.toHaveBeenCalled();
});
});
});
});
});

View File

@ -1,5 +1,5 @@
import * as Sentry from '@sentry/browser';
import SentryConfig from '~/sentry/sentry_config';
import * as Sentry from '~/sentry/wrapper';
describe('SentryConfig', () => {
describe('IGNORE_ERRORS', () => {

View File

@ -84,6 +84,15 @@ describe('Subscriptions', () => {
spy.mockRestore();
});
it('has visually hidden label', () => {
wrapper = mountComponent();
expect(findToggleButton().props()).toMatchObject({
label: 'Notifications',
labelPosition: 'hidden',
});
});
describe('given project emails are disabled', () => {
const subscribeDisabledDescription = 'Notifications have been disabled';

View File

@ -19,6 +19,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
<p
class="gl-mb-0"
data-testid="upload-text"
>
<span>
Test %{linkStart}description%{linkEnd} message.
@ -98,10 +99,15 @@ exports[`Upload dropzone component when dragging renders correct template when d
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>
@ -178,10 +184,15 @@ exports[`Upload dropzone component when dragging renders correct template when d
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>
@ -258,10 +269,15 @@ exports[`Upload dropzone component when dragging renders correct template when d
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>
@ -337,10 +353,15 @@ exports[`Upload dropzone component when dragging renders correct template when d
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>
@ -416,10 +437,15 @@ exports[`Upload dropzone component when dragging renders correct template when d
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>
@ -495,10 +521,15 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
<p
class="gl-mb-0"
data-testid="upload-text"
>
<gl-sprintf-stub
message="Drop or %{linkStart}upload%{linkEnd} files to attach"
/>
Drop or
<gl-link-stub>
upload
</gl-link-stub>
files to attach
</p>
</div>
</button>

Some files were not shown because too many files have changed in this diff Show More