Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-10 12:07:24 +00:00
parent 20eb71551a
commit 6bbf79d852
129 changed files with 1358 additions and 708 deletions

View File

@ -35,7 +35,7 @@ review-docs-cleanup:
.docs-markdown-lint-image:
# When updating the image version here, update it in /scripts/lint-doc.sh too.
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/technical-writing/docs-gitlab-com/lint-markdown:alpine-3.21-vale-3.9.3-markdownlint2-0.17.1-lychee-0.18.0
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/technical-writing/docs-gitlab-com/lint-markdown:alpine-3.21-vale-3.11.2-markdownlint2-0.17.2-lychee-0.18.1
docs-lint markdown:
extends:

View File

@ -5,6 +5,8 @@
StylesPath = doc/.vale
MinAlertLevel = suggestion
IgnoredScopes = code, text.frontmatter.redirect_to
[*.md]
BasedOnStyles = gitlab_base, gitlab_docs

View File

@ -631,7 +631,7 @@
{"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"},
{"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"},
{"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"},
{"name":"ruby-lsp","version":"0.23.10","platform":"ruby","checksum":"71dfb08ff3bdc66f92c18e49f7ce3fe772b25804bcd08a4369f70bcad1534d6c"},
{"name":"ruby-lsp","version":"0.23.13","platform":"ruby","checksum":"a1875a9905a79a41c63d8df52bd016f238d635b64c8f0aac3639336bcf659f48"},
{"name":"ruby-lsp-rails","version":"0.3.31","platform":"ruby","checksum":"670aed466e54b5632e4907b8dedb91d8b144917c42513e013d656af175bf8c76"},
{"name":"ruby-lsp-rspec","version":"0.1.22","platform":"ruby","checksum":"e982edf5cd6ec1530c3f5fa7e423624ad00532ebeff7fc94e02c7516a9b759c0"},
{"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"},
@ -754,7 +754,7 @@
{"name":"typhoeus","version":"1.4.1","platform":"ruby","checksum":"1c17db8364bd45ab302dc61e460173c3e69835896be88a3df07c206d5c55ef7c"},
{"name":"tzinfo","version":"2.0.6","platform":"ruby","checksum":"8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b"},
{"name":"uber","version":"0.1.0","platform":"ruby","checksum":"5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc"},
{"name":"undercover","version":"0.6.3","platform":"ruby","checksum":"a74c4246bc3ed0a506681f9cc41e2cf353c12f1544bb2b7798807e81f2cb65fa"},
{"name":"undercover","version":"0.6.4","platform":"ruby","checksum":"3c34fcf129b52a4993065c52612a65e5e05e77f0cac3f4f8f388114fb129ec1a"},
{"name":"unf","version":"0.1.4","platform":"java","checksum":"49a5972ec0b3d091d3b0b2e00113f2f342b9b212f0db855eb30a629637f6d302"},
{"name":"unf","version":"0.1.4","platform":"ruby","checksum":"4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e"},
{"name":"unf_ext","version":"0.0.8.2","platform":"ruby","checksum":"90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa"},

View File

@ -1698,7 +1698,7 @@ GEM
ruby-fogbugz (0.3.0)
crack (~> 0.4)
multipart-post (~> 2.0)
ruby-lsp (0.23.10)
ruby-lsp (0.23.13)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
@ -1910,11 +1910,12 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
undercover (0.6.3)
undercover (0.6.4)
base64
bigdecimal
imagen (>= 0.2.0)
rainbow (>= 2.1, < 4.0)
rugged (>= 0.27, < 1.8)
rugged (>= 0.27, < 1.10)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)

View File

@ -641,7 +641,7 @@
{"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"},
{"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"},
{"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"},
{"name":"ruby-lsp","version":"0.23.10","platform":"ruby","checksum":"71dfb08ff3bdc66f92c18e49f7ce3fe772b25804bcd08a4369f70bcad1534d6c"},
{"name":"ruby-lsp","version":"0.23.13","platform":"ruby","checksum":"a1875a9905a79a41c63d8df52bd016f238d635b64c8f0aac3639336bcf659f48"},
{"name":"ruby-lsp-rails","version":"0.3.31","platform":"ruby","checksum":"670aed466e54b5632e4907b8dedb91d8b144917c42513e013d656af175bf8c76"},
{"name":"ruby-lsp-rspec","version":"0.1.22","platform":"ruby","checksum":"e982edf5cd6ec1530c3f5fa7e423624ad00532ebeff7fc94e02c7516a9b759c0"},
{"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"},
@ -767,7 +767,7 @@
{"name":"typhoeus","version":"1.4.1","platform":"ruby","checksum":"1c17db8364bd45ab302dc61e460173c3e69835896be88a3df07c206d5c55ef7c"},
{"name":"tzinfo","version":"2.0.6","platform":"ruby","checksum":"8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b"},
{"name":"uber","version":"0.1.0","platform":"ruby","checksum":"5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc"},
{"name":"undercover","version":"0.6.3","platform":"ruby","checksum":"a74c4246bc3ed0a506681f9cc41e2cf353c12f1544bb2b7798807e81f2cb65fa"},
{"name":"undercover","version":"0.6.4","platform":"ruby","checksum":"3c34fcf129b52a4993065c52612a65e5e05e77f0cac3f4f8f388114fb129ec1a"},
{"name":"unf","version":"0.1.4","platform":"java","checksum":"49a5972ec0b3d091d3b0b2e00113f2f342b9b212f0db855eb30a629637f6d302"},
{"name":"unf","version":"0.1.4","platform":"ruby","checksum":"4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e"},
{"name":"unf_ext","version":"0.0.8.2","platform":"ruby","checksum":"90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa"},

View File

@ -1730,7 +1730,7 @@ GEM
ruby-fogbugz (0.3.0)
crack (~> 0.4)
multipart-post (~> 2.0)
ruby-lsp (0.23.10)
ruby-lsp (0.23.13)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
@ -1944,11 +1944,12 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
uber (0.1.0)
undercover (0.6.3)
undercover (0.6.4)
base64
bigdecimal
imagen (>= 0.2.0)
rainbow (>= 2.1, < 4.0)
rugged (>= 0.27, < 1.8)
rugged (>= 0.27, < 1.10)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)

View File

@ -14,6 +14,7 @@ import {
import { __, s__, sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { convertEnvironmentScope } from '~/ci/common/private/ci_environments_dropdown';
import {
DEFAULT_EXCEEDS_VARIABLE_LIMIT_TEXT,
@ -69,6 +70,7 @@ export default {
GlTable,
GlModal,
CrudComponent,
ClipboardButton,
},
directives: {
GlModalDirective,
@ -273,15 +275,12 @@ export default {
class="gl-inline-block gl-max-w-full gl-break-anywhere"
>{{ item.key }}</span
>
<gl-button
v-gl-tooltip
category="tertiary"
icon="copy-to-clipboard"
class="-gl-my-2 gl-ml-2"
size="small"
<clipboard-button
:text="item.key"
:title="__('Copy key')"
:data-clipboard-text="item.key"
:aria-label="__('Copy to clipboard')"
category="tertiary"
size="small"
class="-gl-my-2 gl-ml-2"
/>
</div>
<div v-if="item.description" class="gl-mt-2 gl-text-sm gl-text-subtle">
@ -304,7 +303,7 @@ export default {
v-if="!item.hidden"
class="-gl-mr-3 gl-flex gl-items-start gl-justify-end md:gl-justify-start"
>
<span v-if="areValuesHidden" data-testid="hiddenValue">*****</span>
<span v-if="areValuesHidden" data-testid="hiddenValue"></span>
<span
v-else
:id="`ci-variable-value-${item.id}`"
@ -312,15 +311,12 @@ export default {
data-testid="revealedValue"
>{{ item.value }}</span
>
<gl-button
v-gl-tooltip
category="tertiary"
icon="copy-to-clipboard"
class="-gl-my-2 gl-ml-2"
size="small"
<clipboard-button
:text="item.value"
:title="__('Copy value')"
:data-clipboard-text="item.value"
:aria-label="__('Copy to clipboard')"
category="tertiary"
size="small"
class="-gl-my-2 gl-ml-2"
/>
</div>
</template>
@ -331,15 +327,12 @@ export default {
class="gl-inline-block gl-max-w-full gl-break-anywhere"
>{{ convertEnvironmentScopeValue(item.environmentScope) }}</span
>
<gl-button
v-gl-tooltip
category="tertiary"
icon="copy-to-clipboard"
class="-gl-my-2 gl-ml-2"
size="small"
<clipboard-button
:text="convertEnvironmentScopeValue(item.environmentScope)"
:title="__('Copy environment')"
:data-clipboard-text="convertEnvironmentScopeValue(item.environmentScope)"
:aria-label="__('Copy to clipboard')"
category="tertiary"
size="small"
class="-gl-my-2 gl-ml-2"
/>
</div>
</template>

View File

@ -309,18 +309,22 @@ export default {
>
</div>
</div>
<gl-button
v-if="showAction"
v-gl-tooltip
:title="action.ariaLabel"
:loading="isActionLoading"
:icon="action.icon"
class="gl-h-7 gl-w-7 !gl-rounded-full"
:aria-label="action.ariaLabel"
@click="action.method"
@mouseover="setActionTooltip(true)"
@mouseout="setActionTooltip(false)"
/>
<div>
<gl-button
v-if="showAction"
v-gl-tooltip
:title="action.ariaLabel"
:loading="isActionLoading"
size="small"
class="!gl-rounded-full !gl-p-0"
:aria-label="action.ariaLabel"
@click="action.method"
@mouseover="setActionTooltip(true)"
@mouseout="setActionTooltip(false)"
>
<gl-icon :name="action.icon" size="12" />
</gl-button>
</div>
</div>
<span
v-if="hasSourceJob"

View File

@ -10,7 +10,7 @@ import RunnerGoogleCloudOption from '~/ci/runner/components/runner_google_cloud_
import runnerForRegistrationQuery from '../../graphql/register/runner_for_registration.query.graphql';
import {
STATUS_ONLINE,
CREATION_STATE_FINISHED,
EXECUTORS_HELP_URL,
SERVICE_COMMANDS_HELP_URL,
RUNNER_REGISTRATION_POLLING_INTERVAL_MS,
@ -92,7 +92,7 @@ export default {
captureException({ error, component: this.$options.name });
},
pollInterval() {
if (this.isRunnerOnline) {
if (this.isRunnerRegistered) {
// stop polling
return 0;
}
@ -141,8 +141,8 @@ export default {
runCommand() {
return runCommand({ platform: this.platform });
},
isRunnerOnline() {
return this.runner?.status === STATUS_ONLINE;
isRunnerRegistered() {
return this.runner?.creationState === CREATION_STATE_FINISHED;
},
showGoogleCloudRegistration() {
return this.platform === GOOGLE_CLOUD_PLATFORM;
@ -152,7 +152,7 @@ export default {
},
},
watch: {
isRunnerOnline(newVal, oldVal) {
isRunnerRegistered(newVal, oldVal) {
if (!oldVal && newVal) {
this.$emit('runnerRegistered');
}
@ -172,7 +172,7 @@ export default {
this.isDrawerOpen = val;
},
onBeforeunload(event) {
if (this.isRunnerOnline) {
if (this.isRunnerRegistered) {
return undefined;
}
@ -310,7 +310,7 @@ export default {
<platforms-drawer :platform="platform" :open="isDrawerOpen" @close="onToggleDrawer(false)" />
</template>
<section v-if="isRunnerOnline" class="gl-mt-6">
<section v-if="isRunnerRegistered" class="gl-mt-6">
<h2 class="gl-heading-2">🎉 {{ s__("Runners|You've registered a new runner!") }}</h2>
<p>

View File

@ -150,6 +150,11 @@ export const GROUP_TYPE = 'GROUP_TYPE';
export const PROJECT_TYPE = 'PROJECT_TYPE';
export const RUNNER_TYPES = [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE];
// CiRunnerCreationState
export const CREATION_STATE_STARTED = 'STARTED';
export const CREATION_STATE_FINISHED = 'FINISHED';
// CiRunnerStatus
export const STATUS_ONLINE = 'ONLINE';

View File

@ -3,6 +3,6 @@ query getRunnerForRegistration($id: CiRunnerID!) {
id
description
ephemeralAuthenticationToken
status
creationState
}
}

View File

@ -787,6 +787,7 @@ export default {
<diffs-file-tree
v-if="renderFileTree"
class="gl-px-5"
:total-files-count="numTotalFiles"
@clickFile="goToFile({ path: $event.path })"
/>
<div class="col-12 col-md-auto diff-files-holder">

View File

@ -29,6 +29,11 @@ export default {
required: false,
default: false,
},
totalFilesCount: {
type: [Number, String],
default: undefined,
required: false,
},
},
data() {
const treeWidth =
@ -133,6 +138,7 @@ export default {
<tree-list
:hide-file-stats="hideFileStats"
:loaded-files="loadedFiles"
:total-files-count="totalFilesCount"
@clickFile="onFileClick"
/>
</div>

View File

@ -41,6 +41,11 @@ export default {
required: false,
default: null,
},
totalFilesCount: {
type: [Number, String],
default: undefined,
required: false,
},
},
data() {
return {
@ -52,7 +57,6 @@ export default {
'renderTreeList',
'currentDiffFileId',
'viewedDiffFileIds',
'realSize',
'fileTree',
'allBlobs',
'linkedFile',
@ -191,7 +195,9 @@ export default {
<div class="tree-list-holder flex-column gl-flex" data-testid="file-tree-container">
<div class="gl-mb-3 gl-flex gl-items-center">
<h5 class="gl-my-0 gl-inline-block">{{ __('Files') }}</h5>
<gl-badge class="gl-ml-2" data-testid="file-count">{{ realSize }}</gl-badge>
<gl-badge v-if="totalFilesCount != null" class="gl-ml-2" data-testid="file-count">{{
totalFilesCount
}}</gl-badge>
<gl-button-group class="gl-ml-auto">
<gl-button
v-gl-tooltip.hover

View File

@ -115,13 +115,13 @@ export default {
>
<template #form>
<group-select
class="gl-w-full"
:label="__('Group')"
:initial-selection="namespaceId"
:description="__('Choose the top-level group for your repository imports.')"
input-name="group"
input-id="group"
block
fluid-width
@input="handleGroupSelected"
@clear="handleGroupSelected"
/>

View File

@ -3,6 +3,7 @@ import { mapState } from 'pinia';
import DiffsFileTree from '~/diffs/components/diffs_file_tree.vue';
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
import { useFileBrowser } from '~/diffs/stores/file_browser';
import { useDiffsView } from '~/rapid_diffs/stores/diffs_view';
export default {
name: 'FileBrowser',
@ -10,6 +11,7 @@ export default {
DiffsFileTree,
},
computed: {
...mapState(useDiffsView, ['totalFilesCount']),
...mapState(useDiffsList, ['loadedFiles']),
...mapState(useFileBrowser, ['fileBrowserVisible']),
},
@ -26,6 +28,7 @@ export default {
v-if="fileBrowserVisible"
floating-resize
:loaded-files="loadedFiles"
:total-files-count="totalFilesCount"
@clickFile="clickFile"
/>
</template>

View File

@ -25,14 +25,11 @@ class RapidDiffsFacade {
document.querySelector('[data-diffs-list]'),
this.DiffFileImplementation,
);
const { reloadStreamUrl, metadataEndpoint, diffFilesEndpoint } =
const { reloadStreamUrl, diffsStatsEndpoint, diffFilesEndpoint } =
document.querySelector('[data-rapid-diffs]').dataset;
useDiffsView(pinia).metadataEndpoint = metadataEndpoint;
useDiffsView(pinia).diffsStatsEndpoint = diffsStatsEndpoint;
useDiffsView(pinia)
.loadMetadata()
.then(() => {
initHiddenFilesWarning();
})
.loadDiffsStats()
.catch((error) => {
createAlert({
message: __('Failed to load additional diffs information. Try reloading the page.'),
@ -46,6 +43,7 @@ class RapidDiffsFacade {
});
});
initViewSettings({ pinia, streamUrl: reloadStreamUrl });
initHiddenFilesWarning();
document.addEventListener(DIFF_FILE_MOUNTED, useDiffsList(pinia).addLoadedFile);
}

View File

@ -12,17 +12,17 @@ export async function initHiddenFilesWarning() {
el,
pinia,
computed: {
...mapState(useDiffsView, ['diffStats']),
...mapState(useDiffsView, ['overflow', 'totalFilesCount']),
},
render(h) {
if (!this.diffStats?.renderOverflowWarning) return null;
if (!this.overflow) return null;
return h(HiddenFilesWarning, {
props: {
total: this.diffStats?.realSize,
visible: this.diffStats?.size,
plainDiffPath: this.diffStats?.plainDiffPath,
emailPatchPath: this.diffStats?.emailPatchPath,
total: this.totalFilesCount,
visible: this.overflow?.visibleCount,
plainDiffPath: this.overflow?.diffPath,
emailPatchPath: this.overflow?.emailPath,
},
});
},

View File

@ -26,7 +26,7 @@ const initSettingsApp = (el, pinia) => {
'viewType',
'fileByFileMode',
'singleFileMode',
'diffStats',
'diffsStats',
]),
},
methods: {
@ -40,9 +40,9 @@ const initSettingsApp = (el, pinia) => {
diffViewType: this.viewType,
viewDiffsFileByFile: this.singleFileMode,
isLoading: this.isLoading,
addedLines: this.diffStats?.addedLines,
removedLines: this.diffStats?.removedLines,
diffsCount: this.diffStats?.diffsCount,
addedLines: this.diffsStats?.addedLines,
removedLines: this.diffsStats?.removedLines,
diffsCount: this.diffsStats?.diffsCount,
},
on: {
updateDiffViewType: this.updateViewType,

View File

@ -11,7 +11,6 @@ import { queueRedisHllEvents } from '~/diffs/utils/queue_events';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
import store from '~/mr_notes/stores';
export const useDiffsView = defineStore('diffsView', {
state() {
@ -21,28 +20,38 @@ export const useDiffsView = defineStore('diffsView', {
singleFileMode: false,
updateUserEndpoint: undefined,
streamUrl: undefined,
metadataEndpoint: undefined,
diffStats: null,
diffsStatsEndpoint: undefined,
diffsStats: null,
overflow: null,
};
},
actions: {
async loadMetadata() {
// TODO: refactor this to our own Pinia stores
store.state.diffs.endpointMetadata = this.metadataEndpoint;
store.state.diffs.diffViewType = this.viewType;
store.state.diffs.showWhitespace = this.showWhitespace;
await store.dispatch('diffs/fetchDiffFilesMeta');
this.diffStats = {
addedLines: store.state.diffs.addedLines,
removedLines: store.state.diffs.removedLines,
size: store.state.diffs.size,
realSize: store.state.diffs.realSize,
plainDiffPath: store.state.diffs.plainDiffPath,
emailPatchPath: store.state.diffs.emailPatchPath,
renderOverflowWarning: store.state.diffs.renderOverflowWarning,
// we will be using a number for that after refactoring
diffsCount: parseInt(store.state.diffs.realSize, 10),
async loadDiffsStats() {
const { data } = await axios.get(this.diffsStatsEndpoint);
const {
added_lines: addedLines,
removed_lines: removedLines,
diffs_count: diffsCount,
} = data.diffs_stats;
this.diffsStats = {
addedLines,
removedLines,
diffsCount,
};
if (data.overflow) {
const {
visible_count: visibleCount,
email_path: emailPath,
diff_path: diffPath,
} = data.overflow || {};
this.overflow = {
visibleCount,
emailPath,
diffPath,
};
} else {
this.overflow = null;
}
},
updateDiffView() {
if (this.singleFileMode) {
@ -68,7 +77,6 @@ export const useDiffsView = defineStore('diffsView', {
// we don't have to wait for the setting to be saved since whitespace param is passed explicitly
axios.put(this.updateUserEndpoint, { show_whitespace_in_diffs: value });
}
this.loadMetadata();
this.updateDiffView();
},
},
@ -77,5 +85,8 @@ export const useDiffsView = defineStore('diffsView', {
// w: '1' means ignore whitespace, app/helpers/diff_helper.rb#hide_whitespace?
return { view: this.viewType, w: this.showWhitespace ? '0' : '1' };
},
totalFilesCount() {
return this.diffsStats?.diffsCount;
},
},
});

View File

@ -1,6 +1,6 @@
<script>
import { GlModalDirective, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { GlModalDirective, GlIcon, GlButton, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { InternalEvents } from '~/tracking';
import {
@ -19,23 +19,18 @@ export default {
GlIcon,
SearchModal,
GlButton,
GlSprintf,
},
i18n: {
searchBtnText: __('Search or go to…'),
searchKbdHelp: sprintf(
s__('GlobalSearch|Type %{kbdOpen}/%{kbdClose} to search'),
{ kbdOpen: '<kbd>', kbdClose: '</kbd>' },
false,
),
searchKbdHelp: s__('GlobalSearch|Search or go to… (or use the / keyboard shortcut)'),
searchBtnText: s__('GlobalSearch|Search or go to… %{kbdStart}/%{kbdEnd}'),
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
mixins: [glFeatureFlagsMixin(), trackingMixin],
data() {
return {
searchTooltip: this.$options.i18n.searchKbdHelp,
isNarrowScreen: false,
};
},
@ -50,12 +45,6 @@ export default {
handleNarrowScreenChange({ matches }) {
this.isNarrowScreen = matches;
},
hideSearchTooltip() {
this.searchTooltip = '';
},
showSearchTooltip() {
this.searchTooltip = this.$options.i18n.searchKbdHelp;
},
},
};
</script>
@ -63,26 +52,33 @@ export default {
<template>
<div
v-if="glFeatures.searchButtonTopRight"
:class="{ 'border-0 gl-max-w-26 gl-rounded-base': !isNarrowScreen }"
:class="{ 'border-0 gl-rounded-base': !isNarrowScreen }"
>
<gl-button
id="super-sidebar-search"
v-gl-tooltip.bottom.html="searchTooltip"
v-gl-modal="$options.SEARCH_MODAL_ID"
class="focus:!gl-focus"
:title="$options.i18n.searchBtnText"
:aria-label="$options.i18n.searchBtnText"
class="gl-relative focus:!gl-focus"
:title="$options.i18n.searchKbdHelp"
:aria-label="$options.i18n.searchKbdHelp"
:class="
isNarrowScreen
? 'shadow-none bg-transparent gl-border gl-w-6 !gl-p-0'
: 'user-bar-button gl-w-full !gl-justify-start !gl-pr-7'
: 'user-bar-button gl-w-full !gl-justify-start !gl-pr-15'
"
data-testid="super-sidebar-search-button"
@click="trackEvent('click_search_button_to_activate_command_palette', { label: 'top_right' })"
>
<gl-icon name="search" />
<span v-if="!isNarrowScreen">{{ $options.i18n.searchBtnText }}</span>
<span v-if="!isNarrowScreen">
<gl-sprintf :message="$options.i18n.searchBtnText">
<template #kbd="{ content }">
<span class="gl-absolute gl-right-4"
><kbd>{{ content }}</kbd></span
>
</template>
</gl-sprintf>
</span>
</gl-button>
<search-modal @shown="hideSearchTooltip" @hidden="showSearchTooltip" />
<search-modal />
</div>
</template>

View File

@ -20,6 +20,11 @@ export default {
required: false,
default: false,
},
fluidWidth: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: true,
@ -207,6 +212,7 @@ export default {
ref="listbox"
v-model="selected"
:block="block"
:fluid-width="fluidWidth"
:header-text="headerText"
:reset-button-label="resetButtonLabel"
:toggle-text="toggleText"

View File

@ -29,6 +29,11 @@ export default {
required: false,
default: false,
},
fluidWidth: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
@ -141,6 +146,7 @@ export default {
:fetch-items="fetchGroups"
:fetch-initial-selection="fetchInitialGroup"
:block="block"
:fluid-width="fluidWidth"
v-on="$listeners"
>
<template #error>

View File

@ -4,6 +4,13 @@ html {
}
}
// Do not change or manually disable this
// Instead, call `- disable_fixed_body_scroll` inside your page's HAML template if you need to hide the scroll
// Custom class is used instead of Tailwind so people can discover this, do not replace this with Tailwind analog
.body-fixed-scrollbar {
overflow-y: scroll;
}
.container-fluid {
&.limit-container-width {
.flash-container.sticky {

View File

@ -1,5 +1,5 @@
- if !@lazy
- helpers.add_page_startup_api_call @metadata_endpoint
- helpers.add_page_startup_api_call @diffs_stats_endpoint
- helpers.add_page_startup_api_call @diff_files_endpoint
- if @stream_url
- helpers.content_for :startup_js do
@ -11,7 +11,7 @@
streamRequest: fetch('#{Gitlab::UrlSanitizer.sanitize(@stream_url)}', { signal: controller.signal })
}
.rd-app{ data: { rapid_diffs: true, reload_stream_url: @reload_stream_url, metadata_endpoint: @metadata_endpoint, diff_files_endpoint: @diff_files_endpoint } }
.rd-app{ data: { rapid_diffs: true, reload_stream_url: @reload_stream_url, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint } }
.rd-app-header
.rd-app-file-browser-toggle
%div{ data: { file_browser_toggle: true } }

View File

@ -11,7 +11,7 @@ module RapidDiffs
show_whitespace:,
diff_view:,
update_user_endpoint:,
metadata_endpoint:,
diffs_stats_endpoint:,
diff_files_endpoint:,
lazy: false
)
@ -21,7 +21,7 @@ module RapidDiffs
@show_whitespace = show_whitespace
@diff_view = diff_view
@update_user_endpoint = update_user_endpoint
@metadata_endpoint = metadata_endpoint
@diffs_stats_endpoint = diffs_stats_endpoint
@diff_files_endpoint = diff_files_endpoint
@lazy = lazy
end

View File

@ -19,6 +19,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
before_action only: [:index] do
push_frontend_feature_flag(:importer_user_mapping, current_user)
push_frontend_feature_flag(:importer_user_mapping_reassignment_csv, current_user)
push_frontend_feature_flag(:importer_user_mapping_allow_bypass_of_confirmation, @group)
push_frontend_feature_flag(:service_accounts_crud, @group)
end

View File

@ -166,6 +166,7 @@ class Projects::CommitController < Projects::ApplicationController
@stream_url = diffs_stream_url(@commit, streaming_offset, diff_view)
@diffs_slice = @commit.first_diffs_slice(streaming_offset, commit_diff_options)
@diff_files_endpoint = diff_files_metadata_namespace_project_commit_path
@diffs_stats_endpoint = diffs_stats_namespace_project_commit_path
show
end

View File

@ -83,6 +83,7 @@ class Projects::CompareController < Projects::ApplicationController
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@reload_stream_url = diffs_stream_namespace_project_compare_index_path(**compare_params)
@diff_files_endpoint = diff_files_metadata_namespace_project_compare_index_path(**compare_params)
@diffs_stats_endpoint = diffs_stats_namespace_project_compare_index_path(**compare_params)
@update_current_user_path = expose_path(api_v4_user_preferences_path)
show

View File

@ -70,6 +70,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
@reload_stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
@diff_files_endpoint = project_new_merge_request_diff_files_metadata_path(@project, merge_request: merge_request)
@diffs_stats_endpoint = project_new_merge_request_diffs_stats_path(@project, merge_request: merge_request)
define_new_vars
end

View File

@ -113,6 +113,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@stream_url = diffs_stream_url(@merge_request, streaming_offset, diff_view)
@diffs_slice = @merge_request.first_diffs_slice(streaming_offset)
@diff_files_endpoint = diff_files_metadata_namespace_project_merge_request_path
@diffs_stats_endpoint = diffs_stats_namespace_project_merge_request_path
show_merge_request
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
module Mutations
module Ci
module Runner
class BulkPause < BaseMutation
graphql_name 'RunnerBulkPause'
RunnerID = ::Types::GlobalIDType[::Ci::Runner]
argument :ids, [RunnerID],
required: true,
description: 'IDs of the runners to pause or unpause.'
argument :paused, GraphQL::Types::Boolean,
required: true,
description: 'Indicates the runner is not allowed to receive jobs.'
field :updated_count,
::GraphQL::Types::Int,
null: true,
description: 'Number of records effectively updated. ' \
'Only present if operation was performed synchronously.'
field :updated_runners, # rubocop:disable GraphQL/ExtractType -- Same as bulk_delete
[Types::Ci::RunnerType],
null: true,
description: 'Runners after mutation.'
def resolve(**runner_attrs)
response = { updated_count: 0, updated_runners: [], errors: [] }
ids = runner_attrs[:ids]
runner_ids = model_ids_of(ids)
runners = find_all_runners_by_ids(runner_ids)
if runners.any?
result = ::Ci::Runners::BulkPauseRunnersService
.new(runners: runners, current_user: current_user, paused: runner_attrs[:paused])
.execute
result.payload.slice(:updated_count, :updated_runners, :errors)
else
response
end
end
private
def model_ids_of(global_ids)
global_ids.filter_map { |gid| gid.model_id.to_i }
end
def find_all_runners_by_ids(ids)
return ::Ci::Runner.none if ids.blank?
limit = ::Ci::Runners::BulkPauseRunnersService::RUNNER_LIMIT
::Ci::Runner.id_in(ids).limit(limit + 1)
end
end
end
end
end

View File

@ -6,10 +6,10 @@ module Types
graphql_name 'CiRunnerCreationState'
value 'STARTED',
description: 'Applies to a runner that has been created, but not is not yet registered and running.',
description: 'Applies to a runner that has been created, but is not yet registered and running.',
value: 'started'
value 'FINISHED',
description: 'Applies to a runner that has been registered and has polled for CI jobs at least once.',
description: 'Applies to a runner that has been registered and has polled for CI/CD jobs at least once.',
value: 'finished'
end
end

View File

@ -203,6 +203,7 @@ module Types
mount_mutation Mutations::Ci::PipelineTrigger::Update, experiment: { milestone: '16.3' }
mount_mutation Mutations::Ci::ProjectCiCdSettingsUpdate
mount_mutation Mutations::Ci::Runner::BulkDelete, experiment: { milestone: '15.3' }
mount_mutation Mutations::Ci::Runner::BulkPause, experiment: { milestone: '17.11' }
mount_mutation Mutations::Ci::Runner::Cache::Clear
mount_mutation Mutations::Ci::Runner::Create, experiment: { milestone: '15.10' }
mount_mutation Mutations::Ci::Runner::Delete

View File

@ -20,6 +20,10 @@ module Types
GraphQL::Types::Int,
null: false,
description: 'Number of downvotes the work item has received.'
field :new_custom_emoji_path,
GraphQL::Types::String,
null: true,
description: 'Path to create a new custom emoji.'
field :upvotes,
GraphQL::Types::Int,
null: false,
@ -34,6 +38,12 @@ module Types
BatchLoaders::AwardEmojiVotesBatchLoader
.load_upvotes(object.work_item, awardable_class: 'Issue')
end
def new_custom_emoji_path
return unless context[:current_user]&.can?(:create_custom_emoji, object.work_item.project.namespace)
::Gitlab::Routing.url_helpers.new_group_custom_emoji_path(object.work_item.project.namespace)
end
end
# rubocop:enable Graphql/AuthorizeTypes
end

View File

@ -331,6 +331,18 @@ module ApplicationHelper
class_names
end
def disable_fixed_body_scroll
content_for :disable_fixed_body_scroll, true
end
def body_scroll_classes
return '' unless Feature.enabled?(:force_scrollbar, current_user, type: :beta)
return '' if content_for(:disable_fixed_body_scroll).present?
# Custom class is used instead of Tailwind so people can discover this, do not replace this with Tailwind analog
'body-fixed-scrollbar'
end
def system_message_class
class_names = []

View File

@ -495,14 +495,15 @@ module Ci
ensure_runner_queue_value == value if value.present?
end
def heartbeat
def heartbeat(creation_state: nil)
##
# We can safely ignore writes performed by a runner heartbeat. We do
# not want to upgrade database connection proxy to use the primary
# database after heartbeat write happens.
#
::Gitlab::Database::LoadBalancing::SessionMap.current(load_balancer).without_sticky_writes do
values = { contacted_at: Time.current, creation_state: :finished }
values = { contacted_at: Time.current }
values[:creation_state] = creation_state if creation_state.present?
merge_cache_attributes(values)
@ -511,13 +512,6 @@ module Ci
end
end
def clear_heartbeat
cleared_attributes = { contacted_at: nil }
merge_cache_attributes(cleared_attributes)
update_columns(cleared_attributes)
end
def pick_build!(build)
tick_runner_queue if matches_build?(build)
end

View File

@ -140,7 +140,7 @@ module Ci
read_attribute(:contacted_at)
end
def heartbeat(values, update_contacted_at: true)
def heartbeat(values)
##
# We can safely ignore writes performed by a runner heartbeat. We do
# not want to upgrade database connection proxy to use the primary
@ -150,7 +150,7 @@ module Ci
values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config,
:executor, :runtime_features) || {}
values.merge!(contacted_at: Time.current, creation_state: :finished) if update_contacted_at
values.merge!(contacted_at: Time.current, creation_state: :finished)
if values.include?(:executor)
values[:executor_type] = EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown)

View File

@ -18,6 +18,12 @@ module Clusters
completed: 1,
failed: 2
}
enum :deletion_strategy, {
never: 0,
on_stop: 1,
on_delete: 2
}
end
end
end

View File

@ -32,11 +32,12 @@ module Ci
def status
return :stale if stale?
return :never_contacted if contacted_at.nil?
# NOTE: We can't use finished_creation_state? here as we need to check cached value
return :never_contacted unless creation_state == 'finished'
return :online if online? && creation_state == 'finished'
online? ? :online : :offline
:offline
end
def online?

View File

@ -5,8 +5,6 @@ module UseSqlFunctionForPrimaryKeyLookups
class_methods do
def _query_by_sql(sql, ...)
return super unless Feature.enabled?(:use_sql_functions_for_primary_key_lookups, Feature.current_request)
replaced = try_replace_with_function_call(sql)
return super unless replaced
@ -14,9 +12,7 @@ module UseSqlFunctionForPrimaryKeyLookups
super(replaced.arel, ...)
end
def cached_find_by_statement(key, &block)
return super unless Feature.enabled?(:use_sql_functions_for_primary_key_lookups, Feature.current_request)
def cached_find_by_statement(key, &_block)
transformed_block = proc do |params|
original = yield(params)

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
module Ci
module Runners
class BulkPauseRunnersService
attr_reader :current_user, :runners
RUNNER_LIMIT = 50
# @param runners [Array<Ci::Runner>] the runners to pause or unpause
# @param current_user [User] the user performing the operation
# @param paused [Boolean] action of pausing or unpausing
def initialize(runners:, current_user:, paused:)
@runners = runners
@current_user = current_user
@paused = paused
end
def execute
if runners.present?
# pause the runners
return pause_runners(@paused)
end
ServiceResponse.success(payload: { updated_count: 0, updated_runners: [], errors: [] })
end
private
def pause_runners(paused)
active = !paused
runner_count = runners.limit(RUNNER_LIMIT + 1).count
authorized_runners_ids, unauthorized_runners_ids = compute_authorized_runners
runners_to_be_updated = Ci::Runner.id_in(authorized_runners_ids)
runners_to_be_updated.update(active: active)
ServiceResponse.success(
payload: {
updated_count: runners_to_be_updated.count,
updated_runners: runners_to_be_updated,
errors: error_messages(runner_count, authorized_runners_ids, unauthorized_runners_ids)
})
end
def compute_authorized_runners
current_user.ci_owned_runners.load # preload the owned runners to avoid an N+1
authorized_runners, unauthorized_runners =
runners.limit(RUNNER_LIMIT)
.partition { |runner| Ability.allowed?(current_user, :update_runner, runner) }
[authorized_runners.map(&:id), unauthorized_runners.map(&:id)]
end
def error_messages(runner_count, authorized_runners_ids, unauthorized_runners_ids)
errors = []
if runner_count > RUNNER_LIMIT
errors << "Can only pause up to #{RUNNER_LIMIT} runners per call. Ignored the remaining runner(s)."
end
if authorized_runners_ids.empty?
errors << "User does not have permission to update / pause any of the runners"
elsif unauthorized_runners_ids.any?
failed_ids = unauthorized_runners_ids.map { |runner_id| "##{runner_id}" }.join(', ')
errors << "User does not have permission to update / pause runner(s) #{failed_ids}"
end
errors
end
end
end
end

View File

@ -20,8 +20,6 @@ module Ci
runner_manager = runner.runner_managers.find_by_system_xid!(system_id)
runner_manager.destroy!
runner.clear_heartbeat if runner.runner_managers.empty?
ServiceResponse.success
end

View File

@ -1,3 +1,4 @@
- disable_fixed_body_scroll
- page_title _("IDE"), @project.full_name
- add_page_specific_style 'page_bundles/web_ide_loader'

View File

@ -1,6 +1,6 @@
- page_classes = page_class << @html_class
- page_classes = [user_application_color_mode, user_application_theme, page_classes.flatten.compact]
- body_classes = [user_tab_width, @body_class, client_class_list, *custom_diff_color_classes]
- body_classes = [user_tab_width, @body_class, client_class_list, body_scroll_classes, *custom_diff_color_classes]
!!! 5
%html{ lang: I18n.locale, class: page_classes }

View File

@ -13,5 +13,5 @@
.container-fluid{ class: [container_class] }
= render "commit_box"
= render "ci_menu"
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, metadata_endpoint: @endpoint_metadata_url, diff_files_endpoint: @diff_files_endpoint }
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint }
= render ::RapidDiffs::AppComponent.new(**args)

View File

@ -17,7 +17,7 @@
.container-fluid{ class: [container_class] }
= render "projects/commits/commit_list" unless hide_commit_list
.container-fluid
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: nil, show_whitespace: @show_whitespace_default, diff_view: diff_view, metadata_endpoint: nil, update_user_endpoint: @update_current_user_path, diff_files_endpoint: @diff_files_endpoint, lazy: true }
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: nil, show_whitespace: @show_whitespace_default, diff_view: diff_view, diffs_stats_endpoint: @diffs_stats_endpoint, update_user_endpoint: @update_current_user_path, diff_files_endpoint: @diff_files_endpoint, lazy: true }
= render ::RapidDiffs::AppComponent.new(**args)
- else
.container-fluid

View File

@ -3,5 +3,5 @@
- add_page_specific_style 'page_bundles/merge_request_creation_rapid_diffs'
= render "page" do
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: diff_view, update_user_endpoint: expose_path(api_v4_user_preferences_path), metadata_endpoint: nil, diff_files_endpoint: @diff_files_endpoint, lazy: true }
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: diff_view, update_user_endpoint: expose_path(api_v4_user_preferences_path), diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, lazy: true }
= render ::RapidDiffs::AppComponent.new(**args)

View File

@ -3,7 +3,7 @@
- @content_class = 'rd-page-container diffs-container-limited'
= render 'page'
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, metadata_endpoint: @endpoint_metadata_url, diff_files_endpoint: @diff_files_endpoint }
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint }
= render ::RapidDiffs::AppComponent.new(**args) do |c|
- c.with_diffs_list do
= render RapidDiffs::MergeRequestDiffFileComponent.with_collection(@diffs_slice, merge_request: @merge_request, parallel_view: @diff_view == :parallel)

View File

@ -1,3 +1,4 @@
- disable_fixed_body_scroll
- board = local_assigns.fetch(:board, nil)
- @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative gl-pb-0"

View File

@ -1,8 +1,9 @@
---
name: use_sql_functions_for_primary_key_lookups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135196
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429479
milestone: '16.6'
type: development
group: group::database frameworks
name: force_scrollbar
feature_issue_url:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186498
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/534572
milestone: '17.11'
group: group::personal productivity
type: beta
default_enabled: false

View File

@ -1,9 +0,0 @@
---
name: search_sidekiq_default_concurrency_limit
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/498212
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169034
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/498992
milestone: '17.6'
group: group::global search
type: ops
default_enabled: true

View File

@ -0,0 +1,9 @@
---
name: importer_user_mapping_allow_bypass_of_confirmation
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/534328
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/187616
rollout_issue_url:
milestone: '17.11'
group: group::import and integrate
type: wip
default_enabled: false

View File

@ -2,7 +2,7 @@
key_path: counts.orphaned_namespaces
description: Whether orphaned namespaces are present
product_group: organizations
value_type: number
value_type: boolean
status: active
milestone: "17.6"
instrumentation_class: OrphanedNamespacesMetric

View File

@ -8,14 +8,6 @@ description: Persists escalation status information for incidents
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65206
milestone: '14.2'
gitlab_schema: gitlab_main_cell
desired_sharding_key:
namespace_id:
references: namespaces
backfill_via:
parent:
foreign_key: issue_id
table: issues
sharding_key: namespace_id
belongs_to: issue
sharding_key:
namespace_id: namespaces
table_size: small
desired_sharding_key_migration_job_name: BackfillIncidentManagementIssuableEscalationStatusesNamespaceId

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddDeletionStrategyToClustersManagedResources < Gitlab::Database::Migration[2.2]
milestone '17.11'
def change
add_column :clusters_managed_resources, :deletion_strategy, :integer, limit: 2, null: false, default: 0
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddIncidentManagementIssuableEscalationStatusesNamespaceIdNotNull < Gitlab::Database::Migration[2.2]
milestone '17.11'
disable_ddl_transaction!
def up
add_not_null_constraint :incident_management_issuable_escalation_statuses, :namespace_id
end
def down
remove_not_null_constraint :incident_management_issuable_escalation_statuses, :namespace_id
end
end

View File

@ -0,0 +1 @@
cc4d89c14aca832615742df8bf39df63b9b577f7d18a06bdc3db077c87184ba2

View File

@ -0,0 +1 @@
3bd2e48758b0c19d847b7ca71be04196a527d7f206e45863349c61dfa00cca08

View File

@ -12602,6 +12602,7 @@ CREATE TABLE clusters_managed_resources (
status smallint DEFAULT 0 NOT NULL,
template_name text,
tracked_objects jsonb DEFAULT '[]'::jsonb NOT NULL,
deletion_strategy smallint DEFAULT 0 NOT NULL,
CONSTRAINT check_4f81a98847 CHECK ((char_length(template_name) <= 1024))
);
@ -15663,7 +15664,8 @@ CREATE TABLE incident_management_issuable_escalation_statuses (
escalations_started_at timestamp with time zone,
resolved_at timestamp with time zone,
status smallint DEFAULT 0 NOT NULL,
namespace_id bigint
namespace_id bigint,
CONSTRAINT check_ad48232311 CHECK ((namespace_id IS NOT NULL))
);
CREATE SEQUENCE incident_management_issuable_escalation_statuses_id_seq

View File

@ -11,10 +11,10 @@ level: warning
link: https://docs.gitlab.com/development/documentation/topic_types/concept/#concept-topic-titles
ignorecase: true
nonword: true
scope: raw
scope: heading
tokens:
- '\#+ How it works'
- '\#+ Limitations'
- '\#+ Overview'
- '\#+ Use cases?'
- '\#+ Important notes?'
- 'How it works'
- 'Limitations'
- 'Overview'
- 'Use cases?'
- 'Important notes?'

View File

@ -11,4 +11,4 @@ vocab: false
level: error
scope: raw
raw:
- '\n<<<<<<< .+\n|\n=======\n|\n>>>>>>> .+\n'
- '\n(?:<<<<<<< .+|=======|>>>>>>> .+)\n'

View File

@ -16,4 +16,4 @@ ignorecase: true
link: https://docs.gitlab.com/development/documentation/styleguide/#punctuation
scope: raw
raw:
- '[ ]'
- '[\u202F\u00A0\u200B]'

View File

@ -12,4 +12,4 @@ level: suggestion
nonword: true
ignorecase: true
tokens:
- "GitLab v?(2[^[0-9]]|4|5|6|7|8|9|10|11|12|13|14)"
- "GitLab v?(2[^0-9]|[4-9]|1[0-4])"

View File

@ -11,4 +11,4 @@ vocab: false
level: suggestion
scope: raw
raw:
- '!\[[^\]]*\]\([^\)]*_v(1[01234]|[345789])[^\)]*\)'
- '!\[[^\]]*\]\([^\)]*_v(1[0-4]|[3-9])[^\)]*\)'

View File

@ -506,7 +506,7 @@ When a server-side backup is collected, the restore process defaults to use the
node that hosts each repository is responsible for pulling the necessary backup data directly from object storage.
1. [Configure a server-side backup destination in Gitaly](../gitaly/configure_gitaly.md#configure-server-side-backups).
1. Start a server-side backup restore process and specifying the ID of the backup you wish to restore:
1. Start a server-side backup restore process and specifying the [ID of the backup](backup_archive_process.md#backup-id) you wish to restore:
{{< tabs >}}
@ -529,7 +529,7 @@ sudo -u git -H bundle exec rake gitlab:backup:restore BACKUP=11493107454_2018_04
{{< tab title="Helm chart (Kubernetes)" >}}
```shell
kubectl exec <Toolbox pod name> -it -- backup-utility --restore BACKUP=11493107454_2018_04_25_10.6.4-ce --repositories-server-side
kubectl exec <Toolbox pod name> -it -- backup-utility --restore -t <backup_ID> --repositories-server-side
```
When using [cron-based backups](https://docs.gitlab.com/charts/backup-restore/backup.html#cron-based-backup),

View File

@ -206,7 +206,7 @@ Follow the [Geo database replication instructions](../setup/database.md).
If using an external PostgreSQL instance, refer also to
[Geo with external PostgreSQL instances](../setup/external_database.md).
After streaming replication is enabled in the secondary Geo site's read-replica database, then commands such as `gitlab-rake db:migrate:status:geo` will fail, until [configuration of the secondary site is complete](#step-7-copy-secrets-and-add-the-secondary-site-in-the-application), specifically [Geo configuration - Step 3. Add the secondary site](configuration.md#step-3-add-the-secondary-site).
After enabling streaming replication, `gitlab-rake db:migrate:status:geo` fails until [configuration of the secondary site is complete](#step-7-copy-secrets-and-add-the-secondary-site-in-the-application), specifically [Geo configuration - Step 3. Add the secondary site](configuration.md#step-3-add-the-secondary-site).
### Step 4: Configure the frontend application nodes on the Geo **secondary** site
@ -290,18 +290,18 @@ then make the following modifications:
```
{{< alert type="note" >}}
`postgresql['sql_user_password'] = 'md5 digest of secret'`
If you had set up PostgreSQL cluster using the Linux package and had set
`postgresql['sql_user_password'] = 'md5 digest of secret'`, keep in
mind that `gitlab_rails['db_password']` and `geo_secondary['db_password']`
contains the plaintext passwords. This is used to let the Rails
contains the plaintext passwords. These configurations are used to let the Rails
nodes connect to the databases.
{{< /alert >}}
{{< alert type="note" >}}
Make sure that current node's IP is listed in
Ensure that the current node's IP is listed in
`postgresql['md5_auth_cidr_addresses']` setting of the read-replica database to
allow Rails on this node to connect to PostgreSQL.
{{< /alert >}}

View File

@ -945,7 +945,7 @@ On each node perform the following:
When you specify `https` in the `external_url`, as in the previous example,
GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
certificates aren't present, NGINX will fail to start. For more information, see
certificates aren't present, NGINX won't start. For more information, see
the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/ssl/).
### GitLab Rails post-configuration

View File

@ -18,7 +18,7 @@ Use this API to retrieve details about arbitrary tokens and to revoke them. Unli
## Token prefixes
When making a request, `personal`, `project` or `group access` tokens must begin with `glpat` or the current [custom prefix](../../administration/settings/account_and_limit_settings.md#personal-access-token-prefix). If the token begins with a previous custom prefix, the operation will fail. Interest in support for previous custom prefixes is tracked in [issue 165663](https://gitlab.com/gitlab-org/gitlab/-/issues/165663).
When making a request, `personal`, `project` or `group access` tokens must begin with `glpat` or the current [custom prefix](../../administration/settings/account_and_limit_settings.md#personal-access-token-prefix). If the token begins with a previous custom prefix, the operation fails. Interest in support for previous custom prefixes is tracked in [issue 165663](https://gitlab.com/gitlab-org/gitlab/-/issues/165663).
Prerequisites:

View File

@ -10026,6 +10026,32 @@ Input type: `RestorePagesDeploymentInput`
| <a id="mutationrestorepagesdeploymenterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationrestorepagesdeploymentpagesdeployment"></a>`pagesDeployment` | [`PagesDeployment!`](#pagesdeployment) | Restored Pages Deployment. |
### `Mutation.runnerBulkPause`
{{< details >}}
**Introduced** in GitLab 17.11.
**Status**: Experiment.
{{< /details >}}
Input type: `RunnerBulkPauseInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrunnerbulkpauseclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrunnerbulkpauseids"></a>`ids` | [`[CiRunnerID!]!`](#cirunnerid) | IDs of the runners to pause or unpause. |
| <a id="mutationrunnerbulkpausepaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is not allowed to receive jobs. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrunnerbulkpauseclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrunnerbulkpauseerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationrunnerbulkpauseupdatedcount"></a>`updatedCount` | [`Int`](#int) | Number of records effectively updated. Only present if operation was performed synchronously. |
| <a id="mutationrunnerbulkpauseupdatedrunners"></a>`updatedRunners` | [`[CiRunner!]`](#cirunner) | Runners after mutation. |
### `Mutation.runnerCacheClear`
Input type: `RunnerCacheClearInput`
@ -40910,6 +40936,7 @@ Represents the emoji reactions widget.
| ---- | ---- | ----------- |
| <a id="workitemwidgetawardemojiawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | Emoji reactions on the work item. (see [Connections](#connections)) |
| <a id="workitemwidgetawardemojidownvotes"></a>`downvotes` | [`Int!`](#int) | Number of downvotes the work item has received. |
| <a id="workitemwidgetawardemojinewcustomemojipath"></a>`newCustomEmojiPath` | [`String`](#string) | Path to create a new custom emoji. |
| <a id="workitemwidgetawardemojitype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
| <a id="workitemwidgetawardemojiupvotes"></a>`upvotes` | [`Int!`](#int) | Number of upvotes the work item has received. |
@ -42230,8 +42257,8 @@ Runner cloud provider.
| Value | Description |
| ----- | ----------- |
| <a id="cirunnercreationstatefinished"></a>`FINISHED` | Applies to a runner that has been registered and has polled for CI jobs at least once. |
| <a id="cirunnercreationstatestarted"></a>`STARTED` | Applies to a runner that has been created, but not is not yet registered and running. |
| <a id="cirunnercreationstatefinished"></a>`FINISHED` | Applies to a runner that has been registered and has polled for CI/CD jobs at least once. |
| <a id="cirunnercreationstatestarted"></a>`STARTED` | Applies to a runner that has been created, but is not yet registered and running. |
### `CiRunnerJobExecutionStatus`

View File

@ -90,9 +90,9 @@ the database may be ready.
#### Add support to Omnibus GitLab and the Cloud Native GitLab charts
Before you add a new secret to
Before adding a new secret to
[`config/initializers/01_secret_token.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/initializers/01_secret_token.rb),
make sure you also update Omnibus GitLab and the Cloud Native GitLab charts, or the update will fail.
ensure you also update the GitLab Linux package and the Cloud Native GitLab charts, or the update will fail.
Both installation methods are responsible for writing the `config/secrets.yml` file.
If if they don't know about a secret, Rails attempts to write to the file, and fails because it doesn't
have write access.
@ -100,7 +100,7 @@ have write access.
**Examples**
- [Change for self-compiled installation](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175154)
- [Change for Omnibus GitLab installation](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/8026)
- [Change for Linux package installation](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/8026)
- [Change for Cloud Native installation](https://gitlab.com/gitlab-org/charts/gitlab/-/merge_requests/3988)
#### Populate the secrets in live environments

View File

@ -2,7 +2,7 @@
stage: Verify
group: Pipeline Authoring
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
title: Documenting the `.gitlab-ci.yml` keywords
title: Documenting pipeline configuration keywords
---
The [CI/CD YAML syntax reference](../../ci/yaml/_index.md) uses a standard style to make it easier to use and update.

View File

@ -2,7 +2,7 @@
stage: Systems
group: Distribution
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: GitLab's development guidelines for Distribution
description: Development guidelines for Distribution
title: Contribute to GitLab Distribution
---

View File

@ -2,7 +2,7 @@
stage: Foundations
group: Import and Integrate
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: GitLab's development guidelines for Integrations
description: Development guidelines for Integrations
title: Integration development guidelines
---
@ -185,6 +185,27 @@ attribute :wiki_page_events, default: false
If an event attribute for an existing integration changes to `true`,
this requires a data migration to back-fill the attribute value for old records.
### Define metrics
Every new integration should have five [metrics](../internal_analytics/metrics/_index.md):
- Count of active projects with the given integration
- Count of active projects inheriting the given integration
- Count of active groups with the given integration
- Count of active groups inheriting the given integration
- Count of active instance-level integrations for the given integration
Metrics require the model class of the integration to work. You can add metrics only together with or after the model.
To create metric definitions:
1. Copy the metrics created for an existing active integration.
1. Replace all occurrences of the previous integration's name with the new integration's name.
1. Replace `milestone` with the current milestone and `introduced_by_url` with the merge request link.
1. Verify all other attributes have correct values by checking the [metrics guide](../internal_analytics/metrics/metrics_dictionary.md#metrics-definition-and-validation).
For example, to create metric definitions for the Slack integration, you copy the metrics [1](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180122_projects_slack_active.yml), [2](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180124_groups_slack_active.yml), [3](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180127_instances_slack_active.yml), [4](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180127_instances_slack_active.yml), and [5](https://gitlab.com/gitlab-org/gitlab/blob/master/config/metrics/counts_all/20210216180129_projects_inheriting_slack_active.yml)), then replace `Slack` with the name of the new integration.
### Security requirements
#### All HTTP calls must use `Gitlab::HTTP`

View File

@ -2,7 +2,7 @@
stage: Plan
group: Knowledge
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: GitLab's development guidelines for GitLab Pages
description: Development guidelines for GitLab Pages
title: Contribute to GitLab Pages development
---

View File

@ -42,7 +42,7 @@ See also the issue for [support running Jest tests in browsers](https://gitlab.c
### Debugging Jest tests
Running `yarn jest-debug` runs Jest in debug mode, allowing you to debug/inspect as described in the [Jest docs](https://jestjs.io/docs/troubleshooting#tests-are-failing-and-you-don-t-know-why).
Running `yarn jest-debug` runs Jest in debug mode, allowing you to debug/inspect as described in the [Jest documentation](https://jestjs.io/docs/troubleshooting#tests-are-failing-and-you-don-t-know-why).
### Timeout error

View File

@ -2,7 +2,7 @@
stage: Foundations
group: Import and Integrate
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: GitLab's development guidelines for webhooks
description: Development guidelines for webhooks
title: Webhooks developer guide
---

View File

@ -2,7 +2,7 @@
stage: Plan
group: Knowledge
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: GitLab's development guidelines for Wikis
description: Development guidelines for Wikis
title: Wikis development guidelines
---

View File

@ -158,7 +158,7 @@ Now edit the role and add the policy:
"Effect": "Allow",
"Action": [
"q:CreateOAuthAppConnection",
"q:DeleteOAuthAppConnection",
"q:DeleteOAuthAppConnection"
],
"Resource": "*"
},

View File

@ -12,15 +12,15 @@ module API
JOB_TOKEN_PARAM = :token
LEGACY_SYSTEM_XID = '<legacy>'
def authenticate_runner!(ensure_runner_manager: true, update_contacted_at: true)
def authenticate_runner!(ensure_runner_manager: true, creation_state: nil)
track_runner_authentication
forbidden! unless current_runner
current_runner.heartbeat if update_contacted_at
current_runner.heartbeat(creation_state: creation_state) if ensure_runner_manager
return unless ensure_runner_manager
runner_details = get_runner_details_from_request
current_runner_manager&.heartbeat(runner_details, update_contacted_at: update_contacted_at)
current_runner_manager&.heartbeat(runner_details)
end
def get_runner_details_from_request

View File

@ -80,7 +80,7 @@ module API
requires :token, type: String, desc: "The runner's authentication token"
end
delete '/', urgency: :low, feature_category: :runner do
authenticate_runner!(ensure_runner_manager: false, update_contacted_at: false)
authenticate_runner!(ensure_runner_manager: false)
destroy_conditionally!(current_runner) do
::Ci::Runners::UnregisterRunnerService.new(current_runner, params[:token]).execute
@ -120,11 +120,7 @@ module API
optional :system_id, type: String, desc: "The runner's system identifier"
end
post '/verify', urgency: :low, feature_category: :runner do
# For runners that were created in the UI, we want to update the contacted_at value
# only when it starts polling for jobs
registering_created_runner = params[:token].start_with?(::Ci::Runner::CREATED_RUNNER_TOKEN_PREFIX)
authenticate_runner!(update_contacted_at: !registering_created_runner)
authenticate_runner!
status 200
present current_runner, with: Entities::Ci::RunnerRegistrationDetails
@ -194,7 +190,7 @@ module API
parser :build_json, ::Grape::Parser::Json
post '/request', urgency: :low, feature_category: :continuous_integration do
authenticate_runner!
authenticate_runner!(creation_state: :finished)
unless current_runner.active?
header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value

View File

@ -26,7 +26,17 @@ module Gitlab
managed_resource.update!(status: :failed)
raise ManagedResourceError, format_error_message(response.errors)
else
managed_resource.update!(status: :completed, tracked_objects: response.objects.map(&:to_h))
managed_resource.assign_attributes(
status: :completed,
template_name: get_template.name,
tracked_objects: response.objects.map(&:to_h)
)
deletion_strategy = template_yaml['delete_resources']
managed_resource.deletion_strategy = deletion_strategy if deletion_strategy.present?
managed_resource.save!
Gitlab::InternalEvents.track_event(
'ensure_environment_for_managed_resource',
user: build.user,
@ -52,14 +62,8 @@ module Gitlab
end
def ensure_environment
template = begin
get_custom_environment_template
rescue GRPC::NotFound
kas_client.get_default_environment_template
end
rendered_template = kas_client.render_environment_template(
template: template,
template: get_template,
environment: environment,
build: build)
@ -69,6 +73,18 @@ module Gitlab
build: build)
end
def get_template
get_custom_environment_template
rescue GRPC::NotFound
kas_client.get_default_environment_template
end
strong_memoize_attr :get_template
def template_yaml
YAML.safe_load(get_template.data)
end
strong_memoize_attr :template_yaml
def get_custom_environment_template
kas_client.get_environment_template(agent: environment.cluster_agent, template_name: DEFAULT_TEMPLATE_NAME)
end

View File

@ -30,7 +30,10 @@ module Gitlab
authorize_params: { gl_auth_type: 'login' }
}
when ->(provider_name) { AuthHelper.saml_providers.include?(provider_name.to_sym) }
{ attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements }
{
assertion_consumer_service_url: saml_acs_url(provider_name),
attribute_statements: ::Gitlab::Auth::Saml::Config.default_attribute_statements
}
else
{}
end
@ -39,6 +42,10 @@ module Gitlab
def full_host
proc { |_env| Settings.gitlab['base_url'] }
end
def saml_acs_url(provider_name)
"#{full_host}/users/auth/#{provider_name}/callback"
end
end
private

View File

@ -22,12 +22,13 @@ module Gitlab
def initialize(metric_definition)
super
type = options[:type]
return if options[:type]
return if type.in?(allowed_types)
raise ArgumentError, "'type' option is required"
end
prefix = "Invalid type #{type}. " if type
raise ArgumentError, "#{prefix}Type must be one of: #{allowed_types.join(', ')}"
def available?
options[:type].in?(allowed_types)
end
private

View File

@ -33,59 +33,6 @@ module Gitlab
recorded_at
].freeze
MIGRATED_INTEGRATIONS = %w[
amazon_q
apple_app_store
asana
assembla
bamboo
beyond_identity
bugzilla
buildkite
campfire
clickup
confluence
custom_issue_tracker
datadog
diffblue_cover
discord
drone_ci
emails_on_push
ewm
external_wiki
git_guardian
github
gitlab_slack_application
google_play
hangouts_chat
harbor
irker
jenkins
jira
jira_cloud_app
matrix
mattermost
mattermost_slash_commands
microsoft_teams
packagist
phorge
pipelines_email
pivotaltracker
prometheus
pumble
pushover
redmine
slack
slack_slash_commands
squash_tm
teamcity
telegram
unify_circuit
webex_teams
youtrack
zentao
].freeze
class << self
include Gitlab::Utils::UsageData
include Gitlab::Utils::StrongMemoize
@ -175,7 +122,6 @@ module Gitlab
merge_requests: count(MergeRequest),
notes: count(Note)
}.merge(
integrations_usage,
user_preferences_usage,
service_desk_counts
)
@ -267,31 +213,6 @@ module Gitlab
Gitlab::UsageData::Topology.new.topology_usage_data
end
# rubocop: disable CodeReuse/ActiveRecord
def integrations_usage
# rubocop: disable UsageData/LargeTable:
available_integrations.each_with_object({}) do |name, response|
next if MIGRATED_INTEGRATIONS.include?(name)
type = Integration.integration_name_to_type(name)
response[:"projects_#{name}_active"] = count(Integration.active.where.not(project: nil).where(type: type))
response[:"groups_#{name}_active"] = count(Integration.active.where.not(group: nil).where(type: type))
response[:"instances_#{name}_active"] = count(Integration.active.where(instance: true, type: type))
response[:"projects_inheriting_#{name}_active"] = count(Integration.active.where.not(project: nil).where.not(inherit_from_id: nil).where(type: type))
response[:"groups_inheriting_#{name}_active"] = count(Integration.active.where.not(group: nil).where.not(inherit_from_id: nil).where(type: type))
end
end
def successful_deployments_with_cluster(scope)
scope
.joins(cluster: :deployments)
.merge(::Clusters::Cluster.enabled)
.merge(Deployment.success)
end
# rubocop: enable UsageData/LargeTable
# rubocop: enable CodeReuse/ActiveRecord
# augmented in EE
def user_preferences_usage
{

View File

@ -14586,6 +14586,9 @@ msgstr ""
msgid "CodeownersValidation|Entries with spaces"
msgstr ""
msgid "CodeownersValidation|Group has no members with permission to approve merge requests"
msgstr ""
msgid "CodeownersValidation|Group needs at least Developer access to the project for its members to approve merge requests"
msgstr ""
@ -28593,6 +28596,12 @@ msgstr ""
msgid "GlobalSearch|Search labels"
msgstr ""
msgid "GlobalSearch|Search or go to… %{kbdStart}/%{kbdEnd}"
msgstr ""
msgid "GlobalSearch|Search or go to… (or use the / keyboard shortcut)"
msgstr ""
msgid "GlobalSearch|Search results are loading"
msgstr ""
@ -56242,6 +56251,9 @@ msgstr ""
msgid "ServiceAccounts|Name"
msgstr ""
msgid "ServiceAccounts|No service accounts"
msgstr ""
msgid "ServiceAccounts|Service Accounts"
msgstr ""
@ -66699,6 +66711,9 @@ msgstr ""
msgid "Vulnerability|Additional Info"
msgstr ""
msgid "Vulnerability|Archive pending"
msgstr ""
msgid "Vulnerability|Assert:"
msgstr ""
@ -66912,6 +66927,9 @@ msgstr ""
msgid "Vulnerability|Vulnerability identifiers"
msgstr ""
msgid "Vulnerability|Vulnerability will be archived on %{date}. %{linkStart}Why will this vulnerability be archived?%{linkEnd}"
msgstr ""
msgid "Vulnerability|Vulnerable class:"
msgstr ""

View File

@ -61,7 +61,7 @@
"@gitlab/application-sdk-browser": "^0.3.4",
"@gitlab/at.js": "1.5.7",
"@gitlab/cluster-client": "^2.5.0",
"@gitlab/duo-ui": "^8.9.1",
"@gitlab/duo-ui": "^8.10.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/query-language-rust": "0.5.2",
@ -295,7 +295,7 @@
"jest-jasmine2": "^29.7.0",
"jest-junit": "^12.3.0",
"jest-util": "^29.7.0",
"markdownlint-cli2": "^0.17.1",
"markdownlint-cli2": "^0.17.2",
"miragejs": "^0.1.40",
"mock-apollo-client": "1.2.0",
"nodemon": "^2.0.19",
@ -306,7 +306,7 @@
"swagger-cli": "^4.0.4",
"tailwindcss": "^3.4.1",
"timezone-mock": "^1.0.8",
"vite": "^6.2.5",
"vite": "^6.2.6",
"vite-plugin-ruby": "^5.1.1",
"vue-loader-vue3": "npm:vue-loader@17.4.2",
"vue-test-utils-compat": "0.0.14",

View File

@ -21,12 +21,11 @@ module QA
end
let(:runner) do
Resource::Ci::ProjectRunner.fabricate! do |runner|
runner.name = "qa-runner-#{SecureRandom.hex(6)}"
runner.tags = ["runner-for-#{imported_project.name}"]
runner.executor = :docker
runner.project = imported_project
end
create(:project_runner,
name: "qa-runner-#{SecureRandom.hex(6)}",
tags: ["runner-for-#{imported_project.name}"],
executor: :docker,
project: imported_project)
end
before do

View File

@ -61,7 +61,7 @@ then
# shellcheck disable=2059
printf "${COLOR_RED}ERROR: The number of README.md files has changed!${COLOR_RESET} Use index.md instead of README.md.\n" >&2
printf "If removing a README.md file, update NUMBER_READMES in lint-doc.sh.\n" >&2
printf "https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files\n"
printf "https://docs.gitlab.com/development/documentation/site_architecture/folder_structure/#work-with-directories-and-files\n"
((ERRORCODE++))
fi
@ -76,7 +76,7 @@ then
# shellcheck disable=2059
printf "${COLOR_RED}ERROR: The number of directory names containing dashes has changed!${COLOR_RESET} Use underscores instead of dashes for the directory names.\n" >&2
printf "If removing a directory containing dashes, update NUMBER_DASHES in lint-doc.sh.\n" >&2
printf "https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files\n"
printf "https://docs.gitlab.com/development/documentation/site_architecture/folder_structure/#work-with-directories-and-files\n"
((ERRORCODE++))
fi
@ -91,7 +91,7 @@ then
# shellcheck disable=2059
printf "${COLOR_RED}ERROR: The number of filenames containing dashes has changed!${COLOR_RESET} Use underscores instead of dashes for the filenames.\n" >&2
printf "If removing a file containing dashes, update the filename NUMBER_DASHES in lint-doc.sh.\n" >&2
printf "https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files\n"
printf "https://docs.gitlab.com/development/documentation/site_architecture/folder_structure/#work-with-directories-and-files\n"
((ERRORCODE++))
fi
@ -104,7 +104,7 @@ if echo "${FIND_UPPERCASE_DIRS}" | grep . &>/dev/null
then
# shellcheck disable=2059
printf "${COLOR_RED}ERROR: Found one or more directories with an uppercase letter in their name!${COLOR_RESET} Use lowercase instead of uppercase for the directory names.\n" >&2
printf "https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files\n" >&2
printf "https://docs.gitlab.com/development/documentation/site_architecture/folder_structure/#work-with-directories-and-files\n" >&2
echo "${FIND_UPPERCASE_DIRS}"
((ERRORCODE++))
fi
@ -133,23 +133,11 @@ if echo "${FIND_UPPERCASE_FILES}" | grep . &>/dev/null
then
# shellcheck disable=2059
printf "${COLOR_RED}ERROR: Found one or more file names with an uppercase letter in their name!${COLOR_RESET} Use lowercase instead of uppercase for the file names.\n" >&2
printf "https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files\n" >&2
printf "https://docs.gitlab.com/development/documentation/site_architecture/folder_structure/#work-with-directories-and-files\n" >&2
echo "${FIND_UPPERCASE_FILES}"
((ERRORCODE++))
fi
FIND_ALL_DOCS_DIRECTORIES=$(find doc -type d)
# shellcheck disable=2059
printf "${COLOR_GREEN}INFO: Checking for documentation path clashes...${COLOR_RESET}\n"
for directory in $FIND_ALL_DOCS_DIRECTORIES; do
# Markdown files should not have the same path as a directory with an index.md file in it
if [[ -f "${directory}.md" ]] && [[ -f "${directory}/index.md" ]]; then
# shellcheck disable=2059
printf "${COLOR_YELLOW}WARNING: File ${directory}.md clashes with file ${directory}/index.md!${COLOR_RESET} "
printf "For more information, see https://gitlab.com/gitlab-org/gitlab-docs/-/issues/1792.\n"
fi
done
# Run Vale and Markdownlint only on changed files. Only works on merged results
# pipelines, so first checks if a merged results CI variable is present. If not present,
# runs test on all files.
@ -195,7 +183,7 @@ function run_locally_or_in_container() {
local cmd=$1
local args=$2
local files=$3
local registry_url="registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/lint-markdown:alpine-3.21-vale-3.9.3-markdownlint2-0.17.1-lychee-0.18.0"
local registry_url="registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/lint-markdown:alpine-3.21-vale-3.11.2-markdownlint2-0.17.2-lychee-0.18.1"
if hash "${cmd}" 2>/dev/null
then

View File

@ -9,7 +9,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
let(:show_whitespace) { true }
let(:diff_view) { 'inline' }
let(:update_user_endpoint) { '/update_user' }
let(:metadata_endpoint) { '/metadata' }
let(:diffs_stats_endpoint) { '/diffs_stats' }
let(:diff_files_endpoint) { '/diff_files_metadata' }
it "renders diffs slice" do
@ -22,7 +22,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
app = page.find('[data-rapid-diffs]')
expect(app).not_to be_nil
expect(app['data-reload-stream-url']).to eq(reload_stream_url)
expect(app['data-metadata-endpoint']).to eq(metadata_endpoint)
expect(app['data-diffs-stats-endpoint']).to eq(diffs_stats_endpoint)
expect(app['data-diff-files-endpoint']).to eq(diff_files_endpoint)
end
@ -86,7 +86,8 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
it 'preloads' do
instance = create_instance
render_inline(instance)
expect(instance.helpers.page_startup_api_calls).to include(metadata_endpoint)
expect(instance.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
expect(instance.helpers.page_startup_api_calls).to include(diff_files_endpoint)
expect(vc_test_controller.view_context.content_for?(:startup_js)).not_to be_nil
end
@ -113,7 +114,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
show_whitespace:,
diff_view:,
update_user_endpoint:,
metadata_endpoint:,
diffs_stats_endpoint:,
diff_files_endpoint:,
lazy:
)

View File

@ -17,7 +17,7 @@ RSpec.describe 'Global search', :js, feature_category: :global_search do
shared_examples 'header search' do
it 'renders search button' do
expect(page).to have_button('Search or go to…')
expect(page).to have_selector('[data-testid="super-sidebar-search-button"]')
end
it 'opens search modal when shortcut "s" is pressed' do

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > Open MRs dropdown', :js, feature_category: :source_code_management do
include FilteredSearchHelpers
def create_mr(branch_name, title)
project.repository.create_branch(branch_name)
project.repository.commit_files(
user,
branch_name: branch_name,
message: "Update readme file",
actions: [{ action: :update, file_path: file_path, content: "Updated file content" }]
)
create(:merge_request, source_project: project, target_branch: target_branch, source_branch: branch_name,
title: title)
end
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { project.creator }
let_it_be(:file_path) { 'README.md' }
let_it_be(:mr_title) { 'Update README.md' }
let_it_be(:source_branch) { 'open-mrs-badge-test-1' }
let_it_be(:another_mr_title) { 'Second update to README.md' }
let_it_be(:another_source_branch) { 'open-mrs-badge-test-2' }
let_it_be(:target_branch) { 'master' }
before do
sign_in(user)
create_mr(source_branch, mr_title)
create_mr(another_source_branch, another_mr_title)
end
context 'when feature flags are enabled' do
before do
stub_feature_flags(
filter_blob_path: true,
blob_repository_vue_header_app: true
)
end
it 'shows correct count and lists all MRs in dropdown' do
visit project_blob_path(project, "master/#{file_path}")
badge = find_by_testid('open-mr-badge')
expect(badge).to have_content('2 Open')
badge.click
wait_for_requests
within_testid('disclosure-content') do
expect(page).to have_content(mr_title)
expect(page).to have_content(another_mr_title)
click_button mr_title
end
expect(page).to have_content(mr_title)
end
end
context 'when feature flags are disabled' do
before do
stub_feature_flags(
filter_blob_path: false,
blob_repository_vue_header_app: false
)
end
it 'does not display the open MRs badge' do
visit project_blob_path(project, "master/#{file_path}")
expect(page).not_to have_testid('open-mr-badge')
end
end
end

View File

@ -45,7 +45,7 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
context 'when clicking the search button' do
before do
click_button "Search or go to…"
find_by_testid('super-sidebar-search-button').click
wait_for_all_requests
end

View File

@ -20,8 +20,8 @@ import {
DEFAULT_PLATFORM,
EXECUTORS_HELP_URL,
SERVICE_COMMANDS_HELP_URL,
STATUS_NEVER_CONTACTED,
STATUS_ONLINE,
CREATION_STATE_STARTED,
CREATION_STATE_FINISHED,
RUNNER_REGISTRATION_POLLING_INTERVAL_MS,
WINDOWS_PLATFORM,
GOOGLE_CLOUD_PLATFORM,
@ -216,7 +216,7 @@ describe('RegistrationInstructions', () => {
it('when runner is online, stops polling and announces runner is registered', async () => {
expect(wrapper.emitted('runnerRegistered')).toBeUndefined();
mockResolvedRunner({ ...mockRunner, status: STATUS_ONLINE });
mockResolvedRunner({ ...mockRunner, creationState: CREATION_STATE_FINISHED });
await waitForPolling();
expect(wrapper.emitted('runnerRegistered')).toHaveLength(1);
@ -288,7 +288,7 @@ describe('RegistrationInstructions', () => {
await waitForPolling();
mockResolvedRunner({ ...mockRunner, status: STATUS_NEVER_CONTACTED });
mockResolvedRunner({ ...mockRunner, creationState: CREATION_STATE_STARTED });
await waitForPolling();
});
@ -317,7 +317,7 @@ describe('RegistrationInstructions', () => {
createComponent();
await waitForPolling();
mockResolvedRunner({ ...mockRunner, status: STATUS_ONLINE });
mockResolvedRunner({ ...mockRunner, creationState: CREATION_STATE_FINISHED });
await waitForPolling();
});

View File

@ -542,9 +542,11 @@ describe('diffs/components/app', () => {
describe('File browser', () => {
it('should render file browser when files are present', () => {
store.realSize = '20';
store.treeEntries = { 111: { type: 'blob', fileHash: '111', path: '111.js' } };
createComponent();
expect(wrapper.findComponent(DiffsFileTree).exists()).toBe(true);
expect(wrapper.findComponent(DiffsFileTree).props('totalFilesCount')).toBe('20');
});
it('should not render file browser without files', async () => {

View File

@ -194,9 +194,11 @@ describe('DiffsFileTree', () => {
});
});
it('passes down loadedFiles table to tree list', () => {
it('passes down props to tree list', () => {
const loadedFiles = { foo: true };
createComponent({ loadedFiles });
const totalFilesCount = '20';
createComponent({ loadedFiles, totalFilesCount });
expect(wrapper.findComponent(TreeList).props('loadedFiles')).toBe(loadedFiles);
expect(wrapper.findComponent(TreeList).props('totalFilesCount')).toBe(totalFilesCount);
});
});

View File

@ -53,7 +53,6 @@ describe('Diffs tree list component', () => {
useLegacyDiffs().addedLines = 10;
useLegacyDiffs().addedLines = 20;
useLegacyDiffs().mergeRequestDiff = {};
useLegacyDiffs().realSize = '20';
useLegacyDiffs().setTreeOpen.mockReturnValue();
});
@ -165,7 +164,7 @@ describe('Diffs tree list component', () => {
});
it('renders file count', () => {
createComponent();
createComponent({ totalFilesCount: '20' });
expect(wrapper.findByTestId('file-count').text()).toBe('20');
});

View File

@ -28,19 +28,6 @@ describe('Rapid Diffs App', () => {
app = createRapidDiffsApp(options);
};
beforeEach(() => {
createTestingPinia();
useDiffsView(pinia).loadMetadata.mockResolvedValue();
initFileBrowser.mockResolvedValue();
setHTMLFixture(
`
<div data-rapid-diffs data-reload-stream-url="/reload" data-metadata-endpoint="/metadata" data-diff-files-endpoint="/diff-files-metadata">
<div id="js-stream-container" data-diffs-stream-url="/stream"></div>
</div>
`,
);
});
beforeAll(() => {
Object.defineProperty(window, 'customElements', {
value: { define: jest.fn() },
@ -48,23 +35,28 @@ describe('Rapid Diffs App', () => {
});
});
it('initializes the app', async () => {
let res;
const mock = useDiffsView().loadMetadata.mockImplementationOnce(
() =>
new Promise((resolve) => {
res = resolve;
}),
beforeEach(() => {
createTestingPinia();
useDiffsView().loadDiffsStats.mockResolvedValue();
initFileBrowser.mockResolvedValue();
setHTMLFixture(
`
<div data-rapid-diffs data-reload-stream-url="/reload" data-diffs-stats-endpoint="/stats" data-diff-files-endpoint="/diff-files-metadata">
<div id="js-stream-container" data-diffs-stream-url="/stream"></div>
</div>
`,
);
});
it('initializes the app', () => {
createApp();
app.init();
expect(useDiffsView().metadataEndpoint).toBe('/metadata');
expect(mock).toHaveBeenCalled();
expect(useDiffsView().diffsStatsEndpoint).toBe('/stats');
expect(useDiffsView().loadDiffsStats).toHaveBeenCalled();
expect(initViewSettings).toHaveBeenCalledWith({ pinia, streamUrl: '/reload' });
expect(window.customElements.define).toHaveBeenCalledWith('diff-file', DiffFile);
expect(window.customElements.define).toHaveBeenCalledWith('diff-file-mounted', DiffFileMounted);
expect(window.customElements.define).toHaveBeenCalledWith('streaming-error', StreamingError);
await res();
expect(initHiddenFilesWarning).toHaveBeenCalled();
expect(fixWebComponentsStreamingOnSafari).toHaveBeenCalled();
expect(initFileBrowser).toHaveBeenCalledWith('/diff-files-metadata');

View File

@ -1,34 +1,44 @@
import { shallowMount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import Vue, { nextTick } from 'vue';
import Vue from 'vue';
import { PiniaVuePlugin } from 'pinia';
import FileBrowser from '~/rapid_diffs/app/file_browser.vue';
import DiffsFileTree from '~/diffs/components/diffs_file_tree.vue';
import store from '~/mr_notes/stores';
import { useDiffsList } from '~/rapid_diffs/stores/diffs_list';
import { useFileBrowser } from '~/diffs/stores/file_browser';
import { useDiffsView } from '~/rapid_diffs/stores/diffs_view';
Vue.use(PiniaVuePlugin);
describe('FileBrowser', () => {
let wrapper;
let pinia;
const createComponent = () => {
const pinia = createTestingPinia();
useDiffsList();
useFileBrowser();
wrapper = shallowMount(FileBrowser, {
store,
pinia,
});
};
it('passes down loaded files', async () => {
beforeEach(() => {
pinia = createTestingPinia();
useDiffsList();
useDiffsView();
useFileBrowser();
});
it('passes down props', () => {
const loadedFiles = { foo: 1 };
createComponent();
const totalFilesCount = 20;
useDiffsList().loadedFiles = loadedFiles;
await nextTick();
expect(wrapper.findComponent(DiffsFileTree).props('loadedFiles')).toStrictEqual(loadedFiles);
useDiffsView().diffsStats = { diffsCount: totalFilesCount };
createComponent();
const tree = wrapper.findComponent(DiffsFileTree);
expect(tree.props('loadedFiles')).toStrictEqual(loadedFiles);
expect(tree.props('totalFilesCount')).toStrictEqual(totalFilesCount);
expect(tree.props('floatingResize')).toBe(true);
});
it('uses floating resize', () => {
@ -41,10 +51,9 @@ describe('FileBrowser', () => {
expect(wrapper.findComponent(DiffsFileTree).exists()).toBe(true);
});
it('hides file browser', async () => {
createComponent();
it('hides file browser', () => {
useFileBrowser().fileBrowserVisible = false;
await nextTick();
createComponent();
expect(wrapper.findComponent(DiffsFileTree).exists()).toBe(false);
});

View File

@ -71,7 +71,7 @@ describe('View settings', () => {
it('sets diff app controls props', () => {
useDiffsList().loadedFiles = { foo: true };
useDiffsView().diffStats = {
useDiffsView().diffsStats = {
addedLines: 1,
removedLines: 2,
diffsCount: 3,

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