Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-06 18:20:19 +00:00
parent 9b5bb10544
commit d562dddd65
81 changed files with 653 additions and 450 deletions

View File

@ -158,6 +158,9 @@ Dangerfile
^[Distribution] @gitlab-org/distribution
/lib/support/
[Upgrade path] @gitlab-org/distribution
/config/upgrade_path.yml
# Secure & Threat Management ownership delineation
# https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries
^[Threat Insights backend] @gitlab-org/govern/threat-insights-backend-team

View File

@ -321,7 +321,7 @@
.ai-gateway-services:
services:
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.5.0
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.6.0
alias: ai-gateway
.use-pg13:

View File

@ -469,7 +469,7 @@ update-minor:
- .update-script
variables:
UPDATE_TYPE: minor
QA_RSPEC_TAGS: --tag smoke
QA_RSPEC_TAGS: --tag health_check
rules:
- !reference [.rules:test:update, rules]
- !reference [.rules:test:manual, rules]
@ -480,7 +480,7 @@ update-major:
- .update-script
variables:
UPDATE_TYPE: major
QA_RSPEC_TAGS: --tag smoke
QA_RSPEC_TAGS: --tag health_check
rules:
- !reference [.rules:test:update, rules]
- !reference [.rules:test:manual, rules]
@ -492,7 +492,7 @@ update-ee-to-ce:
variables:
UPDATE_TYPE: minor
UPDATE_FROM_EDITION: ee
QA_RSPEC_TAGS: --tag smoke
QA_RSPEC_TAGS: --tag health_check
rules:
- !reference [.rules:test:ce-only, rules]
- !reference [.rules:test:update, rules]

View File

@ -0,0 +1,50 @@
query getPipelineMiniGraph($fullPath: ID!, $iid: ID!) {
project(fullPath: $fullPath) {
id
pipeline(iid: $iid) {
id
path
stages {
nodes {
id
name
detailedStatus {
id
icon
group
}
}
}
upstream {
id
path
project {
id
name
}
detailedStatus {
id
group
icon
label
}
}
downstream {
nodes {
id
path
project {
id
name
}
detailedStatus {
id
group
icon
label
}
}
}
}
}
}

View File

@ -5,8 +5,7 @@ import { __ } from '~/locale';
import { keepLatestDownstreamPipelines } from '~/ci/pipeline_details/utils/parsing_utils';
import { getQueryHeaders, toggleQueryPollingByVisibility } from '~/ci/pipeline_details/graph/utils';
import { PIPELINE_MINI_GRAPH_POLL_INTERVAL } from '~/ci/pipeline_details/constants';
import getLinkedPipelinesQuery from '~/ci/pipeline_details/graphql/queries/get_linked_pipelines.query.graphql';
import getPipelineStagesQuery from './graphql/queries/get_pipeline_stages.query.graphql';
import getPipelineMiniGraphQuery from './graphql/queries/get_pipeline_mini_graph.query.graphql';
import LinkedPipelinesMiniList from './linked_pipelines_mini_list.vue';
import PipelineStages from './pipeline_stages.vue';
/**
@ -14,8 +13,7 @@ import PipelineStages from './pipeline_stages.vue';
*/
export default {
i18n: {
linkedPipelinesFetchError: __('There was a problem fetching linked pipelines.'),
stagesFetchError: __('There was a problem fetching the pipeline stages.'),
pipelineMiniGraphFetchError: __('There was a problem fetching the pipeline mini graph.'),
},
arrowStyles: ['arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 !gl-align-middle'],
components: {
@ -50,16 +48,15 @@ export default {
},
data() {
return {
linkedPipelines: null,
pipelineStages: [],
pipeline: {},
};
},
apollo: {
linkedPipelines: {
pipeline: {
context() {
return getQueryHeaders(this.pipelineEtag);
},
query: getLinkedPipelinesQuery,
query: getPipelineMiniGraphQuery,
pollInterval() {
return this.pollInterval;
},
@ -70,78 +67,39 @@ export default {
};
},
update({ project }) {
return project?.pipeline || this.linkedpipelines;
return project?.pipeline || {};
},
error() {
createAlert({ message: this.$options.i18n.linkedPipelinesFetchError });
},
},
pipelineStages: {
context() {
return getQueryHeaders(this.pipelineEtag);
},
query: getPipelineStagesQuery,
pollInterval() {
return this.pollInterval;
},
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update({ project }) {
return project?.pipeline?.stages?.nodes || this.pipelineStages;
},
error() {
createAlert({ message: this.$options.i18n.stagesFetchError });
createAlert({ message: this.$options.i18n.pipelineMiniGraphFetchError });
},
},
},
computed: {
downstreamPipelines() {
return keepLatestDownstreamPipelines(this.linkedPipelines?.downstream?.nodes);
return keepLatestDownstreamPipelines(this.pipeline?.downstream?.nodes);
},
formattedStages() {
return this.pipelineStages.map((stage) => {
const { name, detailedStatus } = stage;
return {
// TODO: Once we fetch stage by ID with GraphQL,
// this method will change.
// see https://gitlab.com/gitlab-org/gitlab/-/issues/384853
id: stage.id,
dropdown_path: `${this.pipelinePath}/stage.json?stage=${name}`,
name,
path: `${this.pipelinePath}#${name}`,
status: {
details_path: `${this.pipelinePath}#${name}`,
has_details: detailedStatus?.hasDetails || false,
...detailedStatus,
},
title: `${name}: ${detailedStatus?.text || ''}`,
};
});
pipelineStages() {
return this.pipeline?.stages?.nodes || [];
},
hasDownstreamPipelines() {
return Boolean(this.downstreamPipelines.length);
},
pipelinePath() {
return this.linkedPipelines?.path || '';
return this.pipeline?.path || '';
},
upstreamPipeline() {
return this.linkedPipelines?.upstream;
return this.pipeline?.upstream;
},
},
mounted() {
toggleQueryPollingByVisibility(this.$apollo.queries.linkedPipelines);
toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStages);
toggleQueryPollingByVisibility(this.$apollo.queries.pipeline);
},
};
</script>
<template>
<div>
<gl-loading-icon v-if="$apollo.queries.pipelineStages.loading" />
<gl-loading-icon v-if="$apollo.queries.pipeline.loading" />
<div v-else data-testid="pipeline-mini-graph">
<linked-pipelines-mini-list
v-if="upstreamPipeline"
@ -158,7 +116,7 @@ export default {
/>
<pipeline-stages
:is-merge-train="isMergeTrain"
:stages="formattedStages"
:stages="pipelineStages"
@miniGraphStageClick="$emit('miniGraphStageClick')"
/>
<gl-icon

View File

@ -2,17 +2,18 @@
import { createAlert } from '~/alert';
import { __ } from '~/locale';
import { PIPELINE_MINI_GRAPH_POLL_INTERVAL } from '~/ci/pipeline_details/constants';
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
import { getQueryHeaders, toggleQueryPollingByVisibility } from '~/ci/pipeline_details/graph/utils';
import getPipelineStageQuery from './graphql/queries/get_pipeline_stage.query.graphql';
import JobItem from './job_item.vue';
// import JobItem from './job_item.vue';
export default {
i18n: {
stageFetchError: __('There was a problem fetching the pipeline stage.'),
},
components: {
JobItem,
// JobItem,
CiIcon,
},
props: {
isMergeTrain: {
@ -29,15 +30,14 @@ export default {
required: false,
default: PIPELINE_MINI_GRAPH_POLL_INTERVAL,
},
stageId: {
type: String,
stage: {
type: Object,
required: true,
},
},
data() {
return {
jobs: [],
stage: null,
};
},
apollo: {
@ -51,11 +51,12 @@ export default {
},
variables() {
return {
id: this.stageId,
id: this.stage.id,
};
},
skip() {
return !this.stageId;
// TODO: This query should occur on click
return true;
},
update(data) {
this.jobs = data?.ciPipelineStage?.jobs.nodes;
@ -74,8 +75,14 @@ export default {
<template>
<div data-testid="pipeline-stage">
<ul v-for="job in jobs" :key="job.id">
<ci-icon
:status="stage.detailedStatus"
:show-tooltip="false"
:use-link="false"
class="gl-mb-0!"
/>
<!-- <ul v-for="job in jobs" :key="job.id">
<job-item :job="job" />
</ul>
</ul> -->
</div>
</template>

View File

@ -34,11 +34,11 @@ export default {
<div class="gl-display-inline gl-align-middle">
<div
v-for="stage in stages"
:key="stage.name"
:key="stage.id"
class="pipeline-mini-graph-stage-container dropdown gl-display-inline-flex gl-mr-2 gl-my-2 gl-align-middle"
>
<pipeline-stage
:stage-id="stage.id"
:stage="stage"
:is-merge-train="isMergeTrain"
:pipeline-etag="pipelineEtag"
@miniGraphStageClick="$emit('miniGraphStageClick')"

View File

@ -198,10 +198,10 @@ export const filtersMap = {
[NORMAL_FILTER]: 'author_username',
},
[OPERATOR_NOT]: {
[NORMAL_FILTER]: 'not[author_username]',
[NORMAL_FILTER]: 'not[author_username][]',
},
[OPERATOR_OR]: {
[ALTERNATIVE_FILTER]: 'or[author_username]',
[ALTERNATIVE_FILTER]: 'or[author_username][]',
},
},
},

View File

@ -1,5 +1,5 @@
import Wikis from '~/pages/shared/wikis/wikis';
import { mountApplications } from '~/pages/shared/wikis/async_edit';
import { mountApplications } from '~/pages/shared/wikis/edit';
mountApplications();

View File

@ -3,19 +3,53 @@ import { GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale';
import WikiHeader from './components/wiki_header.vue';
import WikiContent from './components/wiki_content.vue';
import WikiEditForm from './components/wiki_form.vue';
import WikiAlert from './components/wiki_alert.vue';
export default {
components: {
GlAlert,
WikiHeader,
WikiContent,
WikiEditForm,
WikiAlert,
},
inject: ['contentApi', 'isPageHistorical', 'wikiUrl', 'historyUrl'],
inject: ['isEditingPath', 'isPageHistorical', 'wikiUrl', 'historyUrl', 'error'],
i18n: {
alertText: s__('WikiHistoricalPage|This is an old version of this page.'),
alertPrimaryButton: s__('WikiHistoricalPage|Go to most recent version'),
alertSecondaryButton: s__('WikiHistoricalPage|Browse history'),
},
data() {
return {
isEditing: false,
};
},
watch: {
isEditing() {
const url = new URL(window.location);
if (this.isEditing) {
url.searchParams.set('edit', 'true');
} else {
url.searchParams.delete('edit');
}
window.history.pushState({}, '', url);
},
},
mounted() {
const url = new URL(window.location);
if (url.searchParams.has('edit')) {
this.setEditingMode(true);
}
},
methods: {
setEditingMode(value) {
this.isEditing = value;
},
},
};
</script>
@ -33,7 +67,9 @@ export default {
>
{{ $options.i18n.alertText }}
</gl-alert>
<wiki-header />
<wiki-content v-if="contentApi" :get-wiki-content-url="contentApi" />
<wiki-alert v-if="error" :error="error" :wiki-page-path="wikiUrl" class="gl-mt-5" />
<wiki-header v-if="!isEditing" @is-editing="setEditingMode" />
<wiki-edit-form v-if="isEditingPath || isEditing" @is-editing="setEditingMode" />
<wiki-content v-else :is-editing="isEditing" />
</div>
</template>

View File

@ -1,11 +0,0 @@
export const mountApplications = async () => {
const el = document.querySelector('.js-wiki-edit-page');
if (el) {
const { mountApplications: mountEditApplications } = await import(
/* webpackChunkName: 'wiki_edit' */ './edit'
);
mountEditApplications();
}
};

View File

@ -14,7 +14,7 @@ export default {
directives: {
'gl-modal': GlModalDirective,
},
inject: ['wikiUrl', 'pageTitle', 'csrfToken', 'pagePersisted'],
inject: ['wikiUrl', 'pageHeading', 'csrfToken', 'pagePersisted'],
props: {
showAsDropdownItem: {
type: Boolean,
@ -30,7 +30,7 @@ export default {
? this.$options.i18n.deleteTemplateTitle
: this.$options.i18n.deletePageTitle,
{
pageTitle: escape(this.pageTitle),
pageTitle: escape(this.pageHeading),
},
false,
);

View File

@ -1,11 +1,11 @@
<script>
import { GlSkeletonLoader, GlAlert } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { createAlert } from '~/alert';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { handleLocationHash } from '~/lib/utils/common_utils';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { __ } from '~/locale';
export default {
components: {
@ -15,17 +15,13 @@ export default {
directives: {
SafeHtml,
},
props: {
getWikiContentUrl: {
type: String,
required: true,
},
},
inject: ['contentApi'],
data() {
return {
content: '',
isLoadingContent: false,
loadingContentFailed: false,
content: null,
};
},
mounted() {
@ -39,7 +35,7 @@ export default {
try {
const {
data: { content },
} = await axios.get(this.getWikiContentUrl, { params: { render_html: true } });
} = await axios.get(this.contentApi, { params: { render_html: true } });
this.content = content;
this.$nextTick()

View File

@ -119,7 +119,7 @@ export default {
DeleteWikiModal,
},
mixins: [trackingMixin],
inject: ['formatOptions', 'pageInfo', 'drawioUrl', 'templates'],
inject: ['isEditingPath', 'formatOptions', 'pageInfo', 'drawioUrl', 'templates', 'pageHeading'],
data() {
const title = window.location.href.includes('random_title=true') ? '' : getTitle(this.pageInfo);
return {
@ -207,6 +207,13 @@ export default {
drawioEnabled() {
return typeof this.drawioUrl === 'string' && this.drawioUrl.length > 0;
},
cancelFormHref() {
if (this.isEditingPath) {
return this.cancelFormPath;
}
return null;
},
},
watch: {
title() {
@ -312,6 +319,13 @@ export default {
setTemplate(template) {
this.$refs.markdownEditor.setTemplate(template);
},
cancelFormAction() {
this.isFormDirty = false;
if (!this.isEditingPath) {
this.$emit('is-editing', false);
}
},
},
};
</script>
@ -321,7 +335,8 @@ export default {
ref="form"
:action="formAction"
method="post"
class="wiki-form common-note-form gl-mt-3 js-quick-submit"
class="wiki-form common-note-form js-quick-submit gl-mt-4"
:class="{ 'gl-mt-5': !isEditingPath }"
@submit="handleFormSubmit"
@input="isFormDirty = true"
>
@ -336,7 +351,11 @@ export default {
<div class="row">
<div class="col-12">
<gl-form-group :label="$options.i18n.title.label" label-for="wiki_title">
<gl-form-group
:label="$options.i18n.title.label"
label-for="wiki_title"
label-class="gl-sr-only"
>
<template v-if="!isTemplate" #description>
<gl-icon name="bulb" />
{{ titleHelpText }}
@ -456,8 +475,8 @@ export default {
>
<gl-button
data-testid="wiki-cancel-button"
:href="cancelFormPath"
@click="isFormDirty = false"
:href="cancelFormHref"
@click="cancelFormAction"
>
{{ $options.i18n.cancel }}</gl-button
>

View File

@ -2,6 +2,7 @@
import { GlButton, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import WikiMoreDropdown from './wiki_more_dropdown.vue';
export default {
@ -11,18 +12,20 @@ export default {
GlSprintf,
WikiMoreDropdown,
TimeAgo,
PageHeading,
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: [
'pageTitle',
'pageHeading',
'showEditButton',
'isPageTemplate',
'editButtonUrl',
'lastVersion',
'pageVersion',
'authorUrl',
'isEditingPath',
],
computed: {
editTooltipText() {
@ -43,11 +46,14 @@ export default {
methods: {
onKeyUp(event) {
if (event.key === 'e') {
window.location.href = this.editButtonUrl;
this.$emit('is-editing', true);
}
return false;
},
setEditingMode() {
this.$emit('is-editing', true);
},
},
i18n: {
edit: __('Edit'),
@ -60,22 +66,16 @@ export default {
<template>
<div
class="wiki-page-header has-sidebar-toggle detail-page-header border-bottom-0 gl-pt-5 gl-flex gl-flex-wrap"
class="wiki-page-header has-sidebar-toggle detail-page-header border-bottom-0 !gl-pt-0 gl-flex gl-flex-wrap"
>
<div class="gl-flex gl-w-full">
<h1
class="gl-heading-1 !gl-my-0 gl-inline-block gl-grow gl-break-anywhere"
data-testid="wiki-page-title"
>
{{ pageTitle }}
</h1>
<div class="detail-page-header-actions gl-self-start gl-flex gl-gap-3">
<page-heading :heading="pageHeading" class="gl-w-full">
<template v-if="!isEditingPath" #actions>
<gl-button
v-if="showEditButton"
v-gl-tooltip.html
:title="editTooltip"
:href="editButtonUrl"
data-testid="wiki-edit-button"
@click="setEditingMode"
>
{{ $options.i18n.edit }}
</gl-button>
@ -85,8 +85,8 @@ export default {
class="js-sidebar-wiki-toggle md:gl-hidden"
/>
<wiki-more-dropdown />
</div>
</div>
</template>
</page-heading>
<div
v-if="lastVersion"
class="wiki-last-version gl-leading-20 gl-text-secondary gl-mt-3 gl-mb-5"

View File

@ -99,7 +99,7 @@ export default {
v-gl-tooltip="showDropdownTooltip"
icon="ellipsis_v"
category="tertiary"
placement="right"
placement="bottom-end"
no-caret
data-testid="wiki-more-dropdown"
@shown="showDropdown"

View File

@ -1,70 +1,60 @@
import $ from 'jquery';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createApolloClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import GLForm from '~/gl_form';
import ZenMode from '~/zen_mode';
import WikiContentApp from './app.vue';
import wikiAlert from './components/wiki_alert.vue';
import wikiForm from './components/wiki_form.vue';
const mountWikiEditApp = () => {
const el = document.querySelector('#js-vue-wiki-edit-app');
const createAlertVueApp = () => {
const el = document.getElementById('js-wiki-error');
if (el) {
const { error, wikiPagePath } = el.dataset;
if (!el) return false;
const {
pageHeading,
pageInfo,
isPageTemplate,
wikiUrl,
pagePersisted,
templates,
formatOptions,
error,
} = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
render(createElement) {
return createElement(wikiAlert, {
props: {
error,
wikiPagePath,
},
});
},
});
}
};
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: createApolloClient() });
const createWikiFormApp = () => {
const el = document.getElementById('js-wiki-form');
if (el) {
const { pageInfo, formatOptions, templates, wikiUrl, pageTitle, pagePersisted } = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: createApolloClient() });
// eslint-disable-next-line no-new
new Vue({
el,
apolloProvider,
provide: {
formatOptions: JSON.parse(formatOptions),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
drawioUrl: gon.diagramsnet_url,
templates: JSON.parse(templates),
pageTitle,
wikiUrl,
csrfToken: csrf.token,
pagePersisted: parseBoolean(pagePersisted),
},
render(createElement) {
return createElement(wikiForm);
},
});
}
return new Vue({
el,
apolloProvider,
provide: {
isEditingPath: true,
pageHeading,
contentApi: null,
showEditButton: null,
editButtonUrl: null,
lastVersion: null,
pageVersion: null,
authorUrl: null,
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
isPageTemplate: parseBoolean(isPageTemplate),
isPageHistorical: false,
formatOptions: JSON.parse(formatOptions),
csrfToken: csrf.token,
templates: JSON.parse(templates),
drawioUrl: gon.diagramsnet_url,
historyUrl: '',
wikiUrl,
pagePersisted: parseBoolean(pagePersisted),
error,
},
render(createElement) {
return createElement(WikiContentApp);
},
});
};
export const mountApplications = () => {
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form')); // eslint-disable-line no-new
createAlertVueApp();
createWikiFormApp();
mountWikiEditApp();
};

View File

@ -1,5 +1,7 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import VueApollo from 'vue-apollo';
import createApolloClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import Wikis from './wikis';
import WikiContentApp from './app.vue';
@ -9,9 +11,10 @@ const mountWikiContentApp = () => {
if (!el) return false;
const {
pageTitle,
pageHeading,
contentApi,
showEditButton,
pageInfo,
isPageTemplate,
isPageHistorical,
editButtonUrl,
@ -27,14 +30,22 @@ const mountWikiContentApp = () => {
cloneLinkClass,
wikiUrl,
pagePersisted,
templates,
formatOptions,
} = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: createApolloClient() });
return new Vue({
el,
apolloProvider,
provide: {
pageTitle,
isEditingPath: false,
pageHeading,
contentApi,
showEditButton: parseBoolean(showEditButton),
pageInfo: convertObjectPropsToCamelCase(JSON.parse(pageInfo)),
isPageTemplate: parseBoolean(isPageTemplate),
isPageHistorical: parseBoolean(isPageHistorical),
editButtonUrl,
@ -46,11 +57,15 @@ const mountWikiContentApp = () => {
cloneHttpUrl,
newUrl,
historyUrl,
csrfToken: csrf.token,
templatesUrl,
cloneLinkClass,
wikiUrl,
formatOptions: JSON.parse(formatOptions),
csrfToken: csrf.token,
templates: JSON.parse(templates),
drawioUrl: gon.diagramsnet_url,
pagePersisted: parseBoolean(pagePersisted),
error: null,
},
render(createElement) {
return createElement(WikiContentApp);
@ -60,6 +75,5 @@ const mountWikiContentApp = () => {
export const mountApplications = () => {
mountWikiContentApp();
// eslint-disable-next-line no-new
new Wikis();
new Wikis(); // eslint-disable-line no-new
};

View File

@ -19,7 +19,7 @@ export default {
</h1>
<div
v-if="$scopedSlots.actions"
class="gl-flex gl-items-center gl-gap-5"
class="gl-flex gl-items-center gl-gap-3"
data-testid="page-heading-actions"
>
<slot name="actions"></slot>

View File

@ -247,6 +247,7 @@ export default {
updateDraft(this.autosaveKey, this.descriptionText);
},
handleDescriptionTextUpdated(newText) {
this.disableTruncation = true;
this.descriptionText = newText;
this.$emit('updateDraft', this.descriptionText);
this.updateWorkItem();

View File

@ -123,6 +123,7 @@ module WikiActions
# Assign vars expected by MarkupHelper
@ref = params[:version_id]
@path = page.path
@templates = templates_list
render 'shared/wikis/show'
elsif file_blob
@ -398,7 +399,7 @@ module WikiActions
end
def load_content?
skip_actions = Feature.enabled?(:wiki_front_matter_title, container) ? %w[history destroy diff] : %w[history destroy diff show]
skip_actions = %w[history destroy diff]
return false if skip_actions.include?(params[:action])

View File

@ -12,7 +12,11 @@ module PersonalAccessTokens
# We _only_ want to update last_used_at and not also updated_at (which
# would be updated when using #touch).
without_sticky_writes { @personal_access_token.update_column(:last_used_at, Time.zone.now) } if update?
return unless update?
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
@personal_access_token.update_column(:last_used_at, Time.zone.now)
end
end
private
@ -26,15 +30,5 @@ module PersonalAccessTokens
last_used <= 10.minutes.ago
end
def without_sticky_writes
if Feature.enabled?(:disable_sticky_writes_for_pat_last_used, @personal_access_token.user)
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
yield
end
else
yield
end
end
end
end

View File

@ -1,7 +1,7 @@
- add_to_breadcrumbs _("Labels"), admin_labels_path
- breadcrumb_title _("Edit Label")
- breadcrumb_title _("Edit")
- page_title _("Edit"), @label.name, _("Labels")
%h1.page-title.gl-font-size-h-display
= _('Edit Label')
%hr
= _('Edit label')
= render 'shared/labels/form', url: admin_label_path(@label), back_path: admin_labels_path

View File

@ -4,6 +4,6 @@
- show_lock_on_merge = @group.supports_lock_on_merge?
%h1.page-title.gl-font-size-h-display
= _('Edit Label')
= _('Edit label')
= render 'shared/labels/form', url: group_label_path(@group, @label), back_path: @previous_labels_path, show_lock_on_merge: show_lock_on_merge

View File

@ -4,7 +4,6 @@
- render "header_title"
%h1.page-title.gl-font-size-h-display
= _('Edit Milestone')
%hr
= _('Edit milestone')
= render "form"

View File

@ -32,7 +32,6 @@
= render_if_exists 'groups/settings/wiki', f: f, group: @group
= render 'groups/settings/lfs', f: f
= render_if_exists 'groups/settings/auto_assign_duo_pro', f: f, group: @group
= render_if_exists 'groups/settings/code_suggestions', f: f, group: @group
= render_if_exists 'groups/settings/security/security_policy_management', f: f, group: @group
= render_if_exists 'groups/settings/duo_features_enabled', f: f, group: @group
= render_if_exists 'groups/settings/experimental_settings', f: f, group: @group

View File

@ -4,6 +4,6 @@
- show_lock_on_merge = @project.supports_lock_on_merge?
%h1.page-title.gl-font-size-h-display
= _('Edit Label')
= _('Edit label')
= render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project), show_lock_on_merge: show_lock_on_merge

View File

@ -3,8 +3,6 @@
- page_title _('Edit'), @milestone.title, _('Milestones')
%h1.page-title.gl-font-size-h-display
= _('Edit Milestone')
%hr
= _('Edit milestone')
= render 'form'

View File

@ -1,12 +0,0 @@
.gl-mt-3
= form_errors(@page, truncate: :title)
- templates = @templates.map { |t| wiki_page_basic_info(t) }
#js-wiki-form{ data: { page_info: wiki_page_info(@page, uploads_path: uploads_path).to_json,
templates: templates.to_json,
format_options: wiki_markup_hash_by_name_id.to_json,
wiki_url: wiki_page_path(@wiki, @page),
page_title: @page.human_title,
page_persisted: (@page.persisted? && can?(current_user, :create_wiki, @wiki.container)).to_s,
} }

View File

@ -24,7 +24,7 @@
- if can?(current_user, :create_wiki, @wiki)
- edit_sidebar_url = wiki_page_path(@wiki, Wiki::SIDEBAR, action: :edit)
- sidebar_link_class = (editing && @page&.slug == Wiki::SIDEBAR) ? 'active' : ''
= render Pajamas::ButtonComponent.new(href: edit_sidebar_url, category: :tertiary, size: :small, icon: 'pencil', button_options: { class: "gl-border-l gl-pl-3 has-tooltip #{sidebar_link_class}", title: edit_sidebar_text, aria: { label: edit_sidebar_text }})
= render Pajamas::ButtonComponent.new(href: edit_sidebar_url, category: :tertiary, size: :small, icon: 'settings', button_options: { class: "gl-border-l gl-pl-3 has-tooltip #{sidebar_link_class}", title: edit_sidebar_text, aria: { label: edit_sidebar_text }})
- if @sidebar_wiki_entries
%ul.wiki-pages{ class: (@sidebar_page ? 'gl-border-b !gl-pb-3' : '' ) }

View File

@ -3,19 +3,25 @@
- add_page_specific_style 'page_bundles/wiki'
- @gfm_form = true
- @noteable_type = 'Wiki'
- templates = @templates.map { |t| wiki_page_basic_info(t) }
- if @error
#js-wiki-error{ data: { error: @error, wiki_page_path: wiki_page_path(@wiki, @page) } }
- if @page.persisted?
- page_title = @page.template? ? s_("Wiki|Edit template") : s_("Wiki|Edit page")
- else
- page_title = @page.template? ? s_("Wiki|New template") : s_("Wiki|New page")
.js-wiki-edit-page.wiki-page-header.has-sidebar-toggle.flex-column.flex-lg-row
= wiki_sidebar_toggle_button
= form_errors(@page, truncate: :title)
%h1.page-title.gl-font-size-h-display
- if @page.persisted?
= @page.template? ? s_("Wiki|Edit template") : s_("Wiki|Edit page")
- else
= @page.template? ? s_("Wiki|New template") : s_("Wiki|New page")
= render 'shared/wikis/form', uploads_path: wiki_attachment_upload_url
#js-vue-wiki-edit-app{ data: {
testid: 'wiki-page-edit-app',
error: @error,
page_heading: page_title,
page_info: wiki_page_info(@page, uploads_path: wiki_attachment_upload_url).to_json,
is_page_template: @page.template?.to_s,
page_persisted: (@page.persisted? && can?(current_user, :create_wiki, @wiki.container)).to_s,
wiki_url: wiki_page_path(@wiki, @page),
format_options: wiki_markup_hash_by_name_id.to_json,
templates: templates.to_json,
} }
= render 'shared/wikis/sidebar', editing: true

View File

@ -8,7 +8,7 @@
.wiki-page-header.top-area.gl-flex-direction-column.gl-lg-flex-direction-row
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'wiki-page-title' } }= @page ? @page.human_title : _('Failed to retrieve page')
%h2.gl-mt-0.gl-mb-5{ data: { testid: 'page-heading' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { testid: 'wiki-page-content' } }
= _('The page could not be displayed because it timed out.')
= html_escape(_('You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}')) % { linkStart: "<a href=\"#{git_access_url}\">".html_safe, linkEnd: '</a>'.html_safe, cloneIcon: sprite_icon('download', css_class: 'gl-mr-2').html_safe }

View File

@ -1,12 +1,18 @@
- wiki_page_title @page
- add_page_specific_style 'page_bundles/wiki'
- page_history = @page&.persisted? ? wiki_page_path(@wiki, @page, action: :history) : ''
- @gfm_form = true
- @noteable_type = 'Wiki'
- templates = @templates.map { |t| wiki_page_basic_info(t) }
= form_errors(@page, truncate: :title)
#js-vue-wiki-content-app{ data: {
testid: 'wiki-page-content-app',
page_title: @page.human_title,
page_heading: @page.human_title,
content_api: wiki_page_render_api_endpoint(@page),
show_edit_button: (can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding).to_s,
page_info: wiki_page_info(@page, uploads_path: wiki_attachment_upload_url).to_json,
is_page_template: @page.template?.to_s,
is_page_historical: @page.historical?.to_s,
last_version: @page.last_version,
@ -22,6 +28,8 @@
author_url: wiki_page_version_author_url(@page.version),
history_url: page_history,
templates_url: wiki_page_path(@wiki, Wiki::TEMPLATES_DIR),
format_options: wiki_markup_hash_by_name_id.to_json,
templates: templates.to_json,
} }
= render 'shared/wikis/sidebar'

View File

@ -5,5 +5,4 @@
%h1.page-title.gl-font-size-h-display
= _("Edit snippet")
%hr
= render 'shared/snippets/form', url: gitlab_snippet_path(@snippet)

View File

@ -1,9 +0,0 @@
---
name: disable_sticky_writes_for_pat_last_used
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/462379
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153707
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/462823
milestone: '17.1'
group: group::package registry
type: gitlab_com_derisk
default_enabled: false

72
config/upgrade_path.yml Normal file
View File

@ -0,0 +1,72 @@
---
- major: 8
minor: 11
- major: 8
minor: 12
- major: 8
minor: 17
- major: 9
minor: 5
- major: 10
minor: 8
- major: 11
minor: 11
- major: 12
minor: 0
- major: 12
minor: 1
- major: 12
minor: 10
- major: 13
minor: 0
- major: 13
minor: 1
- major: 13
minor: 8
- major: 13
minor: 12
- major: 14
minor: 0
comments: "**Migrations can take a long time!**"
- major: 14
minor: 3
comments: "See [GitLab Issue #354211](https://gitlab.com/gitlab-org/gitlab/-/issues/354211#note_962223568)"
- major: 14
minor: 9
comments: "See [GitLab Issue #354211](https://gitlab.com/gitlab-org/gitlab/-/issues/354211#note_962223568)"
- major: 14
minor: 10
- major: 15
minor: 0
- major: 15
minor: 4
- major: 15
minor: 11
- major: 16
minor: 3
- major: 16
minor: 7
- major: 16
minor: 11

View File

@ -156,3 +156,5 @@ but it does increase the overall size of your project's repository.
- If the VM image does not include the specific software version you need for your job, the required software must be fetched and installed. This causes an increase in job execution time.
- It is not possible to bring your own OS image.
- The keychain for user `gitlab` is not publicly available. You must create a keychain instead.
- Hosted runners on macOS run in headless mode.
Any workloads that require UI interactions such as `testmanagerd` are not supported.

View File

@ -157,12 +157,10 @@ Slack channel.
Before releasing a known required stop, complete these steps. If the required stop
is identified after release, the following steps must still be completed:
1. Update [upgrade paths](../update/index.md#upgrade-paths) documentation to include the new
required stop.
1. In the same MR, update the [upgrade paths](../update/index.md#upgrade-paths) documentation to include the new
required stop, and the [`upgrade_path.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/upgrade_path.yml).
The `upgrade_path.yml` is the single source of truth (SSoT) for all our required stops.
1. Communicate the changes with the customer Support and Release management teams.
1. Update the [`upgrade-path.yml`](https://gitlab.com/gitlab-com/support/toolbox/upgrade-path/-/blob/main/upgrade-path.yml).
GitLab Release Tools uses this file to update Omnibus GitLab with the required stop, as well as to feed the
the Upgrade Path tool.
1. If the required stops is database related, file an issue with the Database group to
squash migrations to that version in the next release. Use this template for your issue:
@ -187,6 +185,18 @@ is identified after release, the following steps must still be completed:
[upgrade check hook](https://gitlab.com/gitlab-org/charts/gitlab/-/blame/master/templates/_runcheck.tpl#L32)
to the required stop version.
## GitLab-maintained projects which depend on `upgrade_path.yml`
We have multiple projects depending on the `upgrade_path.yml` SSoT. Therefore,
any change to the structure of this file needs to take into consideration that
it might affect one of the following projects:
- [Release Tools](https://gitlab.com/gitlab-org/release-tools)
- [Support Upgrade Path](https://gitlab.com/gitlab-com/support/toolbox/upgrade-path)
- [Upgrade Tester](https://gitlab.com/gitlab-org/quality/upgrade-tester)
- [GitLab QA](https://gitlab.com/gitlab-org/gitlab-qa)
- [PostgreSQL Dump Generator](https://gitlab.com/gitlab-org/quality/pg-dump-generator)
## Further reading
- [Documentation: Database required stops](database/required_stops.md)
@ -198,4 +208,5 @@ is identified after release, the following steps must still be completed:
- [Issue: Put in place measures to avoid addition/proliferation of GitLab upgrade path stops](https://gitlab.com/gitlab-org/gitlab/-/issues/375553)
- [Issue: Brainstorm ways for background migrations to be finalized without introducing a required upgrade step](https://gitlab.com/gitlab-org/gitlab/-/issues/357561)
- [Issue: Scheduled required paths for GitLab upgrades to improve UX](https://gitlab.com/gitlab-org/gitlab/-/issues/358417)
- [Issue: Automate upgrade stop planning process](https://gitlab.com/gitlab-org/gitlab/-/issues/438921)
- [Epic: GitLab Releases and Maintenance policies](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/988)

View File

@ -661,7 +661,7 @@ WARNING:
[very specific cases](https://handbook.gitlab.com/handbook/engineering/workflow/#criteria-for-merging-during-broken-master).
For other cases, follow these [handbook instructions](https://handbook.gitlab.com/handbook/engineering/workflow/#merging-during-broken-master).
- If the latest pipeline was created before the merge request was approved, start a new pipeline to ensure that full RSpec suite has been run. You may skip this step only if the merge request does not contain any backend change.
- If the **latest [merged results pipeline](../ci/pipelines/merged_results_pipelines.md)** was **created less than 4 hours ago**, you
- If the **latest [merged results pipeline](../ci/pipelines/merged_results_pipelines.md)** was **created less than 8 hours ago**, you
may merge without starting a new pipeline as the merge request is close enough to the target branch.
- When you set the MR to auto-merge, you should take over
subsequent revisions for anything that would be spotted after that.

View File

@ -232,19 +232,39 @@ graph LR
A --"artifact: list of test files"--> B & C
```
## Merge Trains
## Merge trains
### Why do we need to have a "stable" master branch to enable merge trains?
### Current usage
If the master branch is unstable (i.e. CI/CD pipelines for the master branch are failing frequently), all of the merge requests pipelines that were added AFTER a faulty merge request pipeline would have to be **cancelled** and **added back to the train**, which would create a lot of delays if the merge train is long.
[We started using merge trains in June 2024](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154540).
### How stable does the master branch have to be for us to enable merge trains?
At the moment, **Merge train pipelines don't run any tests**: they only enforce the
["Merging a merge request" guidelines](../code_review.md#merging-a-merge-request)
that already existed before the enablement of merge trains, but that we couldn't easily enforce.
We don't have a specific number, but we need to have better numbers for flaky tests failures and infrastructure failures (see the [Master Broken Incidents RCA Dashboard](https://app.periscopedata.com/app/gitlab/1082465/Master-Broken-Incidents-Root-Cause-Analysis)).
Merge train pipelines run a single `pre-merge-checks` job which ensures the latest pipeline before merge is:
### Could we gradually move to merge trains in our CI/CD configuration?
1. A [Merged Results pipeline](../../ci/pipelines/merged_results_pipelines.md)
1. A [`tier-3` pipeline](#pipeline-tiers) (i.e. full pipeline, not predictive one)
1. Created at most 8 hours ago
There was a proposal from a contributor, but the approach is not without some downsides: [see the original proposal and discussion](https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/195#note_1117151994).
We opened [a feedback issue](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/513)
to iterate on this solution.
### Next iterations
We opened [a dedicated issue to discuss the next iteration for merge trains](https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/516)
to actually start running tests in merge train pipelines.
### Challenges for enabling merge trains running "full" test pipelines
#### Why do we need to have a "stable" default branch?
If the default branch is unstable (i.e. CI/CD pipelines for the default branch are failing frequently), all of the merge requests pipelines that were added AFTER a faulty merge request pipeline would have to be **cancelled** and **added back to the train**, which would create a lot of delays if the merge train is long.
#### How stable does the default branch have to be?
We don't have a specific number, but we need to have better numbers for flaky tests failures and infrastructure failures (see the [Master Broken Incidents RCA Dashboard](https://10az.online.tableau.com/#/site/gitlab/workbooks/2296993/views)).
## Faster feedback for some merge requests

View File

@ -50,6 +50,7 @@ This is a partial list of the [RSpec metadata](https://rspec.info/features/3-12/
| `:skip_fips_env` | The test is excluded when run against an environment in FIPS mode. |
| `:skip_signup_disabled` | The test uses UI to sign up a new user and is skipped in any environment that does not allow new user registration via the UI. |
| `:smoke` | The test belongs to the test suite which verifies basic functionality of a GitLab instance. |
| `:health_check` | The test belongs to the smallest test suite, a subset of smoke. Used to monitor the status and health of the application |
| `:smtp` | The test requires a GitLab instance to be configured to use an SMTP server. Tests SMTP notification email delivery from GitLab by using MailHog. |
| `:testcase` | The link to the test case issue in the [GitLab Project test cases](https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases). |
| `:transient` | The test tests transient bugs. It is excluded by default. |

View File

@ -24,6 +24,11 @@ Our suite consists of this basic functionality coverage:
Smoke tests have the `:smoke` RSpec metadata.
## Health Check suite
This is a very small subset smoke tests with the `:health_check` RSpec metadata.
Its function is to monitor the status and health of the application.
See [End-to-end Testing](end_to_end/index.md) for more details about
end-to-end tests.

View File

@ -220,14 +220,14 @@ The following GitLab application features are not available:
- View the [list of AI features to see which ones are supported](../../user/ai_features.md).
- Refer to our [direction page](https://about.gitlab.com/direction/saas-platforms/dedicated/#supporting-ai-features-on-gitlab-dedicated) for more information.
- Features other than [available features](#available-features) that must be configured outside of the GitLab user interface
- Interacting with GitLab [Feature Flags](../../administration/feature_flags.md)
- Any functionality or feature behind a Feature Flag that is toggled `off` by default
- Any functionality or feature behind a Feature Flag that is toggled `off` by default.
The following features will not be supported:
- Mattermost
- [Server-side Git hooks](../../administration/server_hooks.md).
GitLab Dedicated is a SaaS service, and access to the underlying infrastructure is only available to GitLab Inc. team members. Due to the nature of server side configuration, there is a possible security concern of running arbitrary code on Dedicated services, as well as the possible impact that can have on the service SLA. Use the alternative [push rules](../../user/project/repository/push_rules.md) or [webhooks](../../user/project/integrations/webhooks.md) instead.
- Interacting with GitLab [Feature Flags](../../administration/feature_flags.md). [Feature flags support the development and rollout of new or experimental features](https://handbook.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags) on GitLab.com. Features behind feature flags are not considered ready for production use, are experimental and therefore unsafe for GitLab Dedicated. Stability and SLAs may be affected by changing default settings.
### GitLab Dedicated service features

View File

@ -211,6 +211,13 @@ environments so that they do not attempt and fail to reach out to GitLab service
For more information, see [Enable or disable service ping](../../administration/settings/usage_statistics.md#enable-or-disable-service-ping).
### Disable runner version management
Runner version management retrieves the latest runner versions from GitLab to
[determine which runners in your environment are out of date](../../ci/runners/runners_scope.md#determine-which-runners-need-to-be-upgraded).
You must [disable runner version management](../../administration/settings/continuous_integration.md#disable-runner-version-management)
for offline environments.
### Configure NTP
In GitLab 15.4 and 15.5, Gitaly Cluster assumes `pool.ntp.org` is accessible. If `pool.ntp.org` is not accessible, [customize the time server setting](../../administration/gitaly/praefect.md#customize-time-server-setting) on the Gitaly

View File

@ -111,7 +111,7 @@ use the file finder.
For more information, see:
- [Code intelligence](../project/code_intelligence.md)
- [File finder](../project/repository/file_finder.md)
- [Files](../project/repository/files/index.md)
## Step 6: Migrate projects into GitLab

View File

@ -29,7 +29,7 @@ GitLab does not limit the number of private projects you can create.
- [Search](../../user/search/index.md)
- [Badges](../../user/project/badges.md)
- [Code intelligence](../../user/project/code_intelligence.md)
- [File finder](../../user/project/repository/file_finder.md)
- [Files](../../user/project/repository/files/index.md)
- [Migrate projects by using file exports](../../user/project/settings/import_export.md)
- [System notes](../../user/project/system_notes.md)
- [Transfer a project to another namespace](../../user/project/import/index.md)

View File

@ -67,6 +67,21 @@ plugin support. Refer to the JetBrains documentation for specifics on your IDE.
For languages not listed in the table, Code Suggestions might not function as expected.
## View Multiple Code Suggestions
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/1325) in GitLab 17.1.
For a code completion suggestion in VS Code, multiple suggestion options
might be available. To view all available suggestions:
1. Hover over the code completion suggestion.
1. Scroll through the alternatives. Either:
- Use keyboard shortcuts. Press <kbd>Option</kbd> + <kbd>`]`</kbd> to view the
next suggestion, and <kbd>Option</kbd> + <kbd>`[`</kbd> to view the previous
suggestions.
- Select the right or left arrow to see next or previous options.
1. Press <kbd>Tab</kbd> to apply the suggestion you prefer.
## Experimental features
### Add support for more languages for Code Suggestions in VS Code

View File

@ -1,34 +1,11 @@
---
stage: Create
group: IDE
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: "Search for files in your GitLab repository directly from the GitLab user interface."
redirect_to: 'files/index.md'
remove_date: '2024-09-07'
---
# File finder
This document was moved to [another location](files/index.md).
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148025) to a dialog in GitLab 16.11.
With file finder, you can search for a file in a repository directly from the GitLab UI.
File finder is powered by the [`fuzzaldrin-plus`](https://github.com/jeancroy/fuzz-aldrin-plus) library, which uses fuzzy search and highlights results as you type.
## Search for a file
To search for a file, press <kbd>t</kbd> anywhere in your project, or:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Repository**.
1. In the upper right, select **Find file**.
1. On the dialog, start entering the filename.
1. From the dropdown list, select the file.
To go back to **Files**, press <kbd>Esc</kbd>.
To narrow down your results, include `/` in your search.
![Find file button](img/file_finder_find_file_v12_10.png)
<!-- This redirect file can be deleted after <2024-09-07>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -0,0 +1,34 @@
---
stage: Create
group: IDE
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: "Search for files in your GitLab repository directly from the GitLab user interface."
---
# File finder
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148025) to a dialog in GitLab 16.11.
With file finder, you can search for a file in a repository directly from the GitLab UI.
File finder is powered by the [`fuzzaldrin-plus`](https://github.com/jeancroy/fuzz-aldrin-plus) library, which uses fuzzy search and highlights results as you type.
## Search for a file
To search for a file, press <kbd>t</kbd> anywhere in your project, or:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Repository**.
1. In the upper right, select **Find file**.
1. On the dialog, start entering the filename.
1. From the dropdown list, select the file.
To go back to **Files**, press <kbd>Esc</kbd>.
To narrow down your results, include `/` in your search.
![Find file button](../img/file_finder_find_file_v12_10.png)

View File

@ -302,7 +302,7 @@ because they can't follow redirects:
- [GitLab Workflow VS Code extension](../../../editor_extensions/visual_studio_code/index.md)
- [Lock files and prevent change conflicts](../file_lock.md)
- [Repository API](../../../api/repositories.md)
- [Find files](file_finder.md)
- [Files](files/index.md)
- [Branches](branches/index.md)
- [Create a directory](web_editor.md#create-a-directory)
- [Find file history](git_history.md)

View File

@ -34,15 +34,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def find_groups(params, parent_id = nil)
find_params = params.slice(
:all_available,
:custom_attributes,
:owned, :min_access_level,
:include_parent_descendants,
:repository_storage,
:marked_for_deletion_on,
:search, :visibility
)
find_params = params.slice(*allowable_find_params)
find_params[:parent] = if params[:top_level_only]
[nil]
@ -60,6 +52,13 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
def allowable_find_params
[:all_available,
:custom_attributes,
:owned, :min_access_level,
:include_parent_descendants, :search, :visibility]
end
# This is a separate method so that EE can extend its behaviour, without
# having to modify this code directly.
#

View File

@ -12661,21 +12661,6 @@ msgstr ""
msgid "CodeOwner|Pattern"
msgstr ""
msgid "CodeSuggestionsSM|Code Suggestions free access has ended"
msgstr ""
msgid "CodeSuggestionsSM|Contact Sales"
msgstr ""
msgid "CodeSuggestionsSM|Manage user access for Code Suggestions on the %{subscription_details_link_start}subscription details page%{subscription_details_link_end}."
msgstr ""
msgid "CodeSuggestionsSM|Manage user access for Code Suggestions on the %{usage_quotas_link_start}usage quotas%{usage_quotas_link_end} page."
msgstr ""
msgid "CodeSuggestionsSM|Purchase the Duo Pro add-on to use Code Suggestions."
msgstr ""
msgid "CodeSuggestions|%{linkStart}Code Suggestions%{linkEnd} uses generative AI to suggest code while you're developing."
msgstr ""
@ -17543,6 +17528,9 @@ msgstr ""
msgid "Dependencies|There was an error fetching the components for this group. Please try again later."
msgstr ""
msgid "Dependencies|There was an error fetching the package managers for this group. Please try again later."
msgstr ""
msgid "Dependencies|There was an error fetching the projects for this group. Please try again later."
msgstr ""
@ -19448,12 +19436,6 @@ msgstr ""
msgid "Edit Identity"
msgstr ""
msgid "Edit Label"
msgstr ""
msgid "Edit Milestone"
msgstr ""
msgid "Edit Password"
msgstr ""
@ -19535,12 +19517,18 @@ msgstr ""
msgid "Edit inline"
msgstr ""
msgid "Edit label"
msgstr ""
msgid "Edit link"
msgstr ""
msgid "Edit merge request"
msgstr ""
msgid "Edit milestone"
msgstr ""
msgid "Edit page"
msgstr ""
@ -37314,6 +37302,9 @@ msgstr ""
msgid "PackageRegistry|published by %{author}"
msgstr ""
msgid "Packager"
msgstr ""
msgid "Packages"
msgstr ""
@ -53197,6 +53188,9 @@ msgstr ""
msgid "There was a problem fetching the latest pipeline status."
msgstr ""
msgid "There was a problem fetching the pipeline mini graph."
msgstr ""
msgid "There was a problem fetching the pipeline stage."
msgstr ""

View File

@ -41,7 +41,7 @@ module QA
end
def has_title?(title)
has_element?('wiki-page-title', title)
has_element?('page-heading', title)
end
def has_content?(content)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Govern', :smoke, :mobile, product_group: :authentication do
RSpec.describe 'Govern', :smoke, :health_check, :mobile, product_group: :authentication do
describe 'basic user login' do
it 'user logs in using basic credentials and logs out',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347880' do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Govern' do
describe 'Project access tokens', :smoke, product_group: :authentication do
describe 'Project access tokens', :smoke, :health_check, product_group: :authentication do
let(:project_access_token) { QA::Resource::ProjectAccessToken.fabricate_via_browser_ui! }
after do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :smoke, product_group: :project_management do
RSpec.describe 'Plan', :smoke, :health_check, product_group: :project_management do
let!(:user) do
create(:user,
name: "QA User <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;",

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Plan', :smoke, product_group: :project_management do
RSpec.describe 'Plan', :smoke, :health_check, product_group: :project_management do
describe 'Issue creation' do
let(:project) do
Resource::Project.fabricate_via_api_unless_fips! do |project|

View File

@ -13,7 +13,7 @@ module QA
it(
'creates a basic merge request',
:smoke, :skip_fips_env,
:smoke, :skip_fips_env, :health_check,
quarantine: {
only: { job: 'update-ee-to-ce' },
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/412361',
@ -35,7 +35,7 @@ module QA
end
it(
'creates a merge request with a milestone and label', :smoke,
'creates a merge request with a milestone and label', :smoke, :health_check,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347762'
) do
gitlab_account_user_name = Resource::User.default.reload!.name

View File

@ -20,7 +20,7 @@ module QA
Flow::Login.sign_in
end
it 'pushes code to the repository via SSH', :smoke, :skip_fips_env,
it 'pushes code to the repository via SSH', :smoke, :health_check, :skip_fips_env,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347825' do
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
@ -36,7 +36,7 @@ module QA
end
end
it 'pushes multiple branches and tags together', :smoke, :skip_fips_env,
it 'pushes multiple branches and tags together', :smoke, :health_check, :skip_fips_env,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347826' do
branches = []
tags = []

View File

@ -52,6 +52,8 @@ COMMIT_REF_NAME_DESTINATION="${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME}"
if skopeo inspect "docker://${COMMIT_ASSETS_HASH_DESTINATION}" > /dev/null; then
echosuccess "Image ${COMMIT_ASSETS_HASH_DESTINATION} already exists, no need to rebuild it."
echo "Copying assets image for destinations: ${COMMIT_REF_SLUG_DESTINATION} and ${COMMIT_SHA_DESTINATION}"
skopeo copy "docker://${COMMIT_ASSETS_HASH_DESTINATION}" "docker://${COMMIT_REF_SLUG_DESTINATION}"
skopeo copy "docker://${COMMIT_ASSETS_HASH_DESTINATION}" "docker://${COMMIT_SHA_DESTINATION}"

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'UpgradePath', feature_category: :shared do
it 'is parsed correctly' do
upgrade_path = YAML.safe_load_file(Rails.root.join('config/upgrade_path.yml'))
expect(upgrade_path.first).to eq({ "major" => 8, "minor" => 11 })
expect(upgrade_path[13]).to eq({ "major" => 14, "minor" => 0,
"comments" => "**Migrations can take a long time!**" })
end
end

View File

@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
include FilteredSearchHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, title: 'v1.0', project: project) }
@ -12,10 +13,10 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
before do
sign_in(user)
visit project_issues_path(project)
end
def request_csv(params = {})
visit project_issues_path(project, params)
def request_csv
click_button 'Actions'
click_button 'Export as CSV'
click_on 'Export issues'
@ -63,7 +64,8 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
end
it 'uses filters from issue index', :sidekiq_inline do
request_csv(state: :closed)
click_link 'Closed'
request_csv
expect(csv.count).to eq 0
end
@ -71,7 +73,8 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
it 'ignores sorting from issue index', :sidekiq_inline do
issue2 = create(:labeled_issue, project: project, author: user, labels: [feature_label])
request_csv(sort: :label_priority)
change_sort_by("Label priority")
request_csv
expected = [issue.iid.to_s, issue2.iid.to_s]
expect(csv.map { |row| row['Issue ID'] }).to eq expected
@ -80,8 +83,35 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
it 'uses array filters, such as label_name', :sidekiq_inline do
issue.update!(labels: [idea_label])
request_csv("label_name[]" => 'Bug')
select_tokens 'Label', '||', feature_label.title, idea_label.title, submit: true
request_csv
expect(csv.count).to eq 0
expect(csv.count).to eq 1
end
context "with multiple issue authors" do
let(:user2) { create(:user, developer_of: project) }
let!(:issue2) { create(:issue, project: project, author: user2) }
it 'exports issues by selected author', :sidekiq_inline do
select_tokens 'Author', '=', user2.username, submit: true
request_csv
expect(csv.count).to eq 1
end
it 'exports issues by selected multiple authors', :sidekiq_inline do
select_tokens 'Author', '||', user2.username, user.username, submit: true
request_csv
expect(csv.count).to eq 2
end
it 'does not export issues by excluded multiple authors', :sidekiq_inline do
select_tokens 'Author', '!=', user.username, user2.username, submit: true
request_csv
expect(csv.count).to eq 0
end
end
end

View File

@ -82,7 +82,8 @@ RSpec.describe 'Work item linked items', :js, feature_category: :team_planning d
verify_linked_item_added("##{task.iid}")
end
it 'links a new item with work item reference', :aggregate_failures do
it 'links a new item with work item reference', :aggregate_failures,
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/445635' do
verify_linked_item_added(task.to_reference(full: true))
end

View File

@ -8,7 +8,7 @@ import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/h
import LegacyPipelineStage from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_pipeline_stage.vue';
import eventHub from '~/ci/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
import { stageReply } from '../mock_data';
import { legacyStageReply } from '../mock_data';
const dropdownPath = 'path.json';
@ -73,7 +73,7 @@ describe('Pipelines stage component', () => {
beforeEach(async () => {
createComponent({ updateDropdown: true });
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, legacyStageReply);
await openStageDropdown();
});
@ -112,7 +112,7 @@ describe('Pipelines stage component', () => {
describe('when user opens dropdown and stage request is successful', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, legacyStageReply);
createComponent();
await openStageDropdown();
@ -121,8 +121,8 @@ describe('Pipelines stage component', () => {
});
it('renders the received data and emits the correct events', () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(findDropdownMenuTitle().text()).toContain(stageReply.name);
expect(findDropdownMenu().text()).toContain(legacyStageReply.latest_statuses[0].name);
expect(findDropdownMenuTitle().text()).toContain(legacyStageReply.name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]);
});
@ -152,7 +152,7 @@ describe('Pipelines stage component', () => {
describe('update endpoint correctly', () => {
beforeEach(async () => {
const copyStage = { ...stageReply };
const copyStage = { ...legacyStageReply };
copyStage.latest_statuses[0].name = 'this is the updated content';
mock.onGet('bar.json').reply(HTTP_STATUS_OK, copyStage);
createComponent({
@ -179,8 +179,10 @@ describe('Pipelines stage component', () => {
describe('job update in dropdown', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(HTTP_STATUS_OK);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, legacyStageReply);
mock
.onPost(`${legacyStageReply.latest_statuses[0].status.action.path}.json`)
.reply(HTTP_STATUS_OK);
createComponent();
await waitForPromises();
@ -205,7 +207,7 @@ describe('Pipelines stage component', () => {
describe('With merge trains enabled', () => {
it('shows a warning on the dropdown', async () => {
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, legacyStageReply);
createComponent({
isMergeTrain: true,
});
@ -222,7 +224,7 @@ describe('Pipelines stage component', () => {
describe('With merge trains disabled', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, legacyStageReply);
createComponent();
await openStageDropdown();

View File

@ -1,4 +1,4 @@
export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true } = {}) => ({
export const mockDownstreamPipelinesGraphql = () => ({
nodes: [
{
id: 'gid://gitlab/Ci::Pipeline/612',
@ -15,10 +15,6 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
label: 'passed',
__typename: 'DetailedStatus',
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/532',
retried: includeSourceJobRetried ? false : null,
},
__typename: 'Pipeline',
},
{
@ -36,10 +32,6 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
label: 'passed',
__typename: 'DetailedStatus',
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/531',
retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
{
@ -57,16 +49,24 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
label: 'passed',
__typename: 'DetailedStatus',
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/530',
retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
],
__typename: 'PipelineConnection',
});
export const pipelineStage = {
__typename: 'CiStage',
id: 'gid://gitlab/Ci::Stage/409',
name: 'build',
detailedStatus: {
__typename: 'DetailedStatus',
id: 'success-409-409',
icon: 'status_success',
group: 'success',
},
};
const upstream = {
id: 'gid://gitlab/Ci::Pipeline/610',
path: '/root/trigger-downstream/-/pipelines/610',
@ -85,26 +85,16 @@ const upstream = {
__typename: 'Pipeline',
};
export const mockPipelineStagesQueryResponse = {
export const mockPipelineMiniGraphQueryResponse = {
data: {
project: {
id: 'gid://gitlab/Project/20',
pipeline: {
id: 'gid://gitlab/Ci::Pipeline/320',
downstream: mockDownstreamPipelinesGraphql(),
upstream,
stages: {
nodes: [
{
__typename: 'CiStage',
id: 'gid://gitlab/Ci::Stage/409',
name: 'build',
detailedStatus: {
__typename: 'DetailedStatus',
id: 'success-409-409',
icon: 'status_success',
group: 'success',
},
},
],
nodes: [pipelineStage],
},
},
},
@ -146,10 +136,9 @@ export const mockUpstreamDownstreamQueryResponse = {
},
};
export const linkedPipelinesFetchError = 'There was a problem fetching linked pipelines.';
export const stagesFetchError = 'There was a problem fetching the pipeline stages.';
export const pipelineMiniGraphFetchError = 'There was a problem fetching the pipeline mini graph.';
export const stageReply = {
export const legacyStageReply = {
name: 'deploy',
title: 'deploy: running',
latest_statuses: [

View File

@ -7,38 +7,25 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import getLinkedPipelinesQuery from '~/ci/pipeline_details/graphql/queries/get_linked_pipelines.query.graphql';
import getPipelineStagesQuery from '~/ci/pipeline_mini_graph/graphql/queries/get_pipeline_stages.query.graphql';
import getPipelineMiniGraphQuery from '~/ci/pipeline_mini_graph/graphql/queries/get_pipeline_mini_graph.query.graphql';
import PipelineMiniGraph from '~/ci/pipeline_mini_graph/pipeline_mini_graph.vue';
import * as sharedGraphQlUtils from '~/graphql_shared/utils';
import {
linkedPipelinesFetchError,
stagesFetchError,
mockPipelineStagesQueryResponse,
mockUpstreamDownstreamQueryResponse,
} from './mock_data';
import { pipelineMiniGraphFetchError, mockPipelineMiniGraphQueryResponse } from './mock_data';
Vue.use(VueApollo);
jest.mock('~/alert');
describe('PipelineMiniGraph', () => {
let wrapper;
let linkedPipelinesResponse;
let pipelineStagesResponse;
let pipelineMiniGraphResponse;
const fullPath = 'gitlab-org/gitlab';
const iid = '315';
const pipelineEtag = '/api/graphql:pipelines/id/315';
const createComponent = ({
pipelineStagesHandler = pipelineStagesResponse,
linkedPipelinesHandler = linkedPipelinesResponse,
} = {}) => {
const handlers = [
[getLinkedPipelinesQuery, linkedPipelinesHandler],
[getPipelineStagesQuery, pipelineStagesHandler],
];
const createComponent = ({ pipelineMiniGraphHandler = pipelineMiniGraphResponse } = {}) => {
const handlers = [[getPipelineMiniGraphQuery, pipelineMiniGraphHandler]];
const mockApollo = createMockApollo(handlers);
wrapper = shallowMountExtended(PipelineMiniGraph, {
@ -57,11 +44,10 @@ describe('PipelineMiniGraph', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
beforeEach(() => {
linkedPipelinesResponse = jest.fn().mockResolvedValue(mockUpstreamDownstreamQueryResponse);
pipelineStagesResponse = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
pipelineMiniGraphResponse = jest.fn().mockResolvedValue(mockPipelineMiniGraphQueryResponse);
});
describe('when initial queries are loading', () => {
describe('when initial query is loading', () => {
beforeEach(() => {
createComponent();
});
@ -72,7 +58,7 @@ describe('PipelineMiniGraph', () => {
});
});
describe('when queries have loaded', () => {
describe('when query has loaded', () => {
it('does not show a loading icon', async () => {
await createComponent();
@ -85,11 +71,10 @@ describe('PipelineMiniGraph', () => {
expect(findPipelineMiniGraph().exists()).toBe(true);
});
it('fires the queries', async () => {
it('fires the query', async () => {
await createComponent();
expect(linkedPipelinesResponse).toHaveBeenCalledWith({ iid, fullPath });
expect(pipelineStagesResponse).toHaveBeenCalledWith({ iid, fullPath });
expect(pipelineMiniGraphResponse).toHaveBeenCalledWith({ iid, fullPath });
});
});
@ -101,22 +86,19 @@ describe('PipelineMiniGraph', () => {
await waitForPromises();
expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledTimes(2);
expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledTimes(1);
});
});
describe('when pipeline queries are unsuccessful', () => {
describe('when the pipeline query is unsuccessful', () => {
const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
it.each`
query | handlerName | errorMessage
${'pipeline stages'} | ${'pipelineStagesHandler'} | ${stagesFetchError}
${'linked pipelines'} | ${'linkedPipelinesHandler'} | ${linkedPipelinesFetchError}
`('throws an error for the $query query', async ({ errorMessage, handlerName }) => {
await createComponent({ [handlerName]: failedHandler });
it('throws an error for the pipeline query', async () => {
await createComponent({ pipelineMiniGraphHandler: failedHandler });
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({ message: errorMessage });
expect(createAlert).toHaveBeenCalledWith({ message: pipelineMiniGraphFetchError });
});
});
});

View File

@ -6,6 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineStageQuery from '~/ci/pipeline_mini_graph/graphql/queries/get_pipeline_stage.query.graphql';
import PipelineStage from '~/ci/pipeline_mini_graph/pipeline_stage.vue';
import { pipelineStage } from './mock_data';
Vue.use(VueApollo);
@ -15,7 +16,7 @@ describe('PipelineStage', () => {
const defaultProps = {
pipelineEtag: '/etag',
stageId: '1',
stage: pipelineStage,
};
const createComponent = ({ pipelineStageHandler = pipelineStageResponse } = {}) => {
@ -39,7 +40,7 @@ describe('PipelineStage', () => {
createComponent();
});
it('renders job item', () => {
it('renders the pipeline stage', () => {
expect(findPipelineStage().exists()).toBe(true);
});
});

View File

@ -42,7 +42,7 @@ import {
setIdTypePreferenceMutationResponse,
setIdTypePreferenceMutationResponseWithErrors,
} from 'jest/issues/list/mock_data';
import { stageReply } from 'jest/ci/pipeline_mini_graph/mock_data';
import { legacyStageReply } from 'jest/ci/pipeline_mini_graph/mock_data';
import { users, mockSearch, branches } from '../pipeline_details/mock_data';
Vue.use(VueApollo);
@ -769,7 +769,7 @@ describe('Pipelines', () => {
mock
.onGet(mockPipelineWithStages.details.stages[0].dropdown_path)
.reply(HTTP_STATUS_OK, stageReply);
.reply(HTTP_STATUS_OK, legacyStageReply);
// cancelMock is getting overwritten in pipelines_service.js#L29
// so we have to spy on it again here

View File

@ -175,9 +175,9 @@ export const setIdTypePreferenceMutationResponseWithErrors = {
export const locationSearch = [
'?search=find+issues',
'author_username=homer',
'not[author_username]=marge',
'or[author_username]=burns',
'or[author_username]=smithers',
'not[author_username][]=marge',
'or[author_username][]=burns',
'or[author_username][]=smithers',
'assignee_username[]=bart',
'assignee_username[]=lisa',
'assignee_username[]=5',
@ -376,8 +376,8 @@ export const apiParamsWithSpecialValues = {
export const urlParams = {
search: 'find issues',
author_username: 'homer',
'not[author_username]': 'marge',
'or[author_username]': ['burns', 'smithers'],
'not[author_username][]': 'marge',
'or[author_username][]': ['burns', 'smithers'],
'assignee_username[]': ['bart', 'lisa', '5'],
'not[assignee_username][]': ['patty', 'selma'],
'or[assignee_username][]': ['carl', 'lenny'],

View File

@ -20,7 +20,7 @@ describe('DeleteWikiModal', () => {
},
provide: {
wikiUrl: 'delete-wiki-url',
pageTitle: 'Page title',
pageHeading: 'Page title',
csrfToken: 'csrf-token',
pagePersisted: true,
},

View File

@ -19,7 +19,10 @@ describe('pages/shared/wikis/components/wiki_content', () => {
function buildWrapper(propsData = {}) {
wrapper = shallowMount(WikiContent, {
propsData: { getWikiContentUrl: PATH, ...propsData },
provide: {
contentApi: PATH,
},
propsData: { ...propsData },
stubs: {
GlSkeletonLoader,
GlAlert,

View File

@ -79,6 +79,7 @@ describe('WikiForm', () => {
wrapper = extendedWrapper(
mountFn(WikiForm, {
provide: {
isEditingPath: true,
templates,
formatOptions,
glFeatures,
@ -87,7 +88,7 @@ describe('WikiForm', () => {
...pageInfo,
},
wikiUrl: '',
pageTitle: '',
pageHeading: '',
csrfToken: '',
pagePersisted: false,
...provide,

View File

@ -1,6 +1,7 @@
import { GlSprintf } from '@gitlab/ui';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PageHeading from '~/vue_shared/components/page_heading.vue';
import WikiHeader from '~/pages/shared/wikis/components/wiki_header.vue';
describe('pages/shared/wikis/components/wiki_header', () => {
@ -9,8 +10,9 @@ describe('pages/shared/wikis/components/wiki_header', () => {
function buildWrapper(provide = {}) {
wrapper = shallowMountExtended(WikiHeader, {
provide: {
pageTitle: 'Wiki page heading',
pageHeading: 'Wiki page heading',
isPageTemplate: false,
isEditingPath: false,
showEditButton: true,
editButtonUrl: 'http://edit.url',
lastVersion: '2024-06-03T01:53:28.000Z',
@ -26,11 +28,12 @@ describe('pages/shared/wikis/components/wiki_header', () => {
stubs: {
GlSprintf,
TimeAgo,
PageHeading,
},
});
}
const findPageHeading = () => wrapper.findByTestId('wiki-page-title');
const findPageHeading = () => wrapper.findByTestId('page-heading');
const findEditButton = () => wrapper.findByTestId('wiki-edit-button');
const findLastVersion = () => wrapper.findByTestId('wiki-page-last-version');

View File

@ -17,7 +17,7 @@ describe('pages/shared/wikis/components/wiki_more_dropdown', () => {
provide: {
newUrl: 'https://new.url/path',
historyUrl: 'https://history.url/path',
pageTitle: 'Wiki title',
pageHeading: 'Wiki title',
csrfToken: '',
wikiUrl: 'https://delete.url/path',
wikiPath: '',

View File

@ -103,6 +103,7 @@ describe('WorkItemDescription', () => {
const updatedDescription = `- [x] todo 1\n- [x] todo 2`;
expect(wrapper.emitted('descriptionUpdated')).toEqual([[updatedDescription]]);
expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(false);
});
it('disables checkbox while updating', async () => {
@ -120,6 +121,7 @@ describe('WorkItemDescription', () => {
const updatedDescription = `- [ ] todo 1\n- [ ] todo 2`;
expect(wrapper.emitted('descriptionUpdated')).toEqual([[updatedDescription]]);
expect(wrapper.find('[data-test-id="description-read-more"]').exists()).toBe(false);
});
});
});

View File

@ -30,21 +30,6 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
expect(::Gitlab::Database::LoadBalancing::Session.current).to be_performed_write
expect(::Gitlab::Database::LoadBalancing::Session.current).not_to be_using_primary
end
context 'with disable_sticky_writes_for_pat_last_used disabled' do
before do
stub_feature_flags(disable_sticky_writes_for_pat_last_used: false)
end
it 'does stick to primary' do
::Gitlab::Database::LoadBalancing::Session.clear_session
expect(::Gitlab::Database::LoadBalancing::Session.current).not_to be_performed_write
expect { service.execute }.to change { personal_access_token.last_used_at }
expect(::Gitlab::Database::LoadBalancing::Session.current).to be_performed_write
expect(::Gitlab::Database::LoadBalancing::Session.current).to be_using_primary
end
end
end
end

View File

@ -227,6 +227,14 @@ module FilteredSearchHelpers
end
end
def change_sort_by(value)
within_element '.sort-dropdown-container' do
find_by_testid('base-dropdown-toggle').click
find('li', text: value).click
wait_for_requests
end
end
def expect_visible_suggestions_list
expect(page).to have_css('.gl-filtered-search-suggestion-list')
end

View File

@ -26,7 +26,6 @@ RSpec.shared_examples 'wiki file attachments' do
click_button 'Cancel'
end
expect(page).to have_selector('[data-testid="button-attach-file"]')
expect(page).not_to have_button('Cancel')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
end

View File

@ -43,12 +43,11 @@ RSpec.shared_examples 'User updates wiki page' do
first(:link, text: 'three').click
expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
expect(find('[data-testid="page-heading"]')).to have_content('three')
click_on('Edit')
expect(page).to have_current_path(%r{one/two/three-test}, ignore_query: true)
expect(page).to have_content('Edit page')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
@ -65,7 +64,7 @@ RSpec.shared_examples 'User updates wiki page' do
before do
visit(wiki_path(wiki))
click_link('Edit')
click_on('Edit')
end
it 'updates a page', :js do

View File

@ -15,6 +15,6 @@ RSpec.shared_examples 'User uses wiki shortcuts' do
it 'visit edit wiki page using "e" keyboard shortcut', :js do
find('body').native.send_key('e')
expect(find('.page-title')).to have_content('Edit page')
expect(find('#wiki_title').value).to eq('home')
end
end

View File

@ -61,12 +61,12 @@ RSpec.shared_examples 'User views a wiki page' do
first(:link, text: 'three').click
expect(find('[data-testid="wiki-page-title"]')).to have_content('three')
expect(find('[data-testid="page-heading"]')).to have_content('three')
click_on('Edit')
expect(page).to have_current_path(%r{one/two/three-test})
expect(page).to have_content('Edit page')
expect(page).to have_css('#wiki_title')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
@ -215,7 +215,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'preserves the special characters' do
visit(wiki_page_path(wiki, wiki_page))
expect(page).to have_css('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_css('[data-testid="page-heading"]', text: title)
expect(page).to have_css('.wiki-pages li', text: title)
end
end
@ -230,7 +230,7 @@ RSpec.shared_examples 'User views a wiki page' do
it 'safely displays the page' do
visit(wiki_page_path(wiki, wiki_page))
expect(page).to have_selector('[data-testid="wiki-page-title"]', text: title)
expect(page).to have_selector('[data-testid="page-heading"]', text: title)
expect(page).to have_content('foo bar')
end
end