Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-01 00:09:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-01 00:09:09 +0300
commit404895390afe87ce8ab939448bf7dff7dc4b7169 (patch)
tree93c323d7df6b70c84dce7b3e4e4f3d57180394a0 /app
parente9885f7a36065b9b45a35feb6c427c7742a906a4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue2
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue17
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue149
-rw-r--r--app/assets/javascripts/pipelines/constants.js4
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_linked_pipelines.query.graphql (renamed from app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql)0
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_pipeline_stages.query.graphql (renamed from app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql)0
-rw-r--r--app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue45
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue66
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js18
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue42
-rw-r--r--app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue56
-rw-r--r--app/assets/javascripts/super_sidebar/super_sidebar_bundle.js2
-rw-r--r--app/assets/stylesheets/framework/broadcast_messages.scss4
-rw-r--r--app/controllers/projects/ci/pipeline_editor_controller.rb1
-rw-r--r--app/controllers/projects/commit_controller.rb3
-rw-r--r--app/helpers/sidebars_helper.rb32
-rw-r--r--app/models/concerns/project_features_compatibility.rb4
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/views/devise/sessions/email_verification.haml4
-rw-r--r--app/views/groups/settings/_advanced.html.haml4
-rw-r--r--app/views/groups/settings/_lfs.html.haml4
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/shared/_broadcast_message.html.haml25
24 files changed, 444 insertions, 45 deletions
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
index a4dfb401f4c..656b1a6c347 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
@@ -2,7 +2,7 @@
import { __ } from '~/locale';
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
-import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
+import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql';
import { PIPELINE_FAILURE } from '../../constants';
export default {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
index 372f04075ab..bb79a4d74da 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
@@ -9,7 +9,9 @@ import {
getQueryHeaders,
toggleQueryPollingByVisibility,
} from '~/pipelines/components/graph/utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import GraphqlPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue';
import PipelineEditorMiniGraph from './pipeline_editor_mini_graph.vue';
const POLL_INTERVAL = 10000;
@@ -32,11 +34,13 @@ export default {
GlLink,
GlLoadingIcon,
GlSprintf,
+ GraphqlPipelineMiniGraph,
PipelineEditorMiniGraph,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagsMixin()],
inject: ['projectFullPath'],
props: {
commitSha: {
@@ -106,6 +110,9 @@ export default {
hasPipelineData() {
return Boolean(this.pipeline?.id);
},
+ isUsingPipelineMiniGraphQueries() {
+ return this.glFeatures.ciGraphqlPipelineMiniGraph;
+ },
pipelineId() {
return getIdFromGraphQLId(this.pipeline.id);
},
@@ -171,8 +178,14 @@ export default {
</gl-sprintf>
</span>
</div>
- <div class="gl-display-flex gl-flex-wrap">
- <pipeline-editor-mini-graph :pipeline="pipeline" v-on="$listeners" />
+ <div class="gl-display-flex gl-flex-wrap-wrap">
+ <graphql-pipeline-mini-graph
+ v-if="isUsingPipelineMiniGraphQueries"
+ :full-path="projectFullPath"
+ :iid="pipeline.iid"
+ :pipeline-etag="pipelineEtag"
+ />
+ <pipeline-editor-mini-graph v-else :pipeline="pipeline" v-on="$listeners" />
<gl-button
class="gl-ml-3"
category="secondary"
diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue
new file mode 100644
index 00000000000..91630d4cfd4
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue
@@ -0,0 +1,149 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { createAlert } from '~/alert';
+import { __ } from '~/locale';
+import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
+import {
+ getQueryHeaders,
+ toggleQueryPollingByVisibility,
+} from '~/pipelines/components/graph/utils';
+import { PIPELINE_MINI_GRAPH_POLL_INTERVAL } from '~/pipelines/constants';
+import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql';
+import getPipelineStagesQuery from '~/pipelines/graphql/queries/get_pipeline_stages.query.graphql';
+import PipelineMiniGraph from './pipeline_mini_graph.vue';
+
+export default {
+ i18n: {
+ linkedPipelinesFetchError: __('There was a problem fetching linked pipelines.'),
+ stagesFetchError: __('There was a problem fetching the pipeline stages.'),
+ },
+ components: {
+ GlLoadingIcon,
+ PipelineMiniGraph,
+ },
+ props: {
+ pipelineEtag: {
+ type: String,
+ required: true,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ iid: {
+ type: String,
+ required: true,
+ },
+ isMergeTrain: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ pollInterval: {
+ type: Number,
+ required: false,
+ default: PIPELINE_MINI_GRAPH_POLL_INTERVAL,
+ },
+ },
+ data() {
+ return {
+ linkedPipelines: null,
+ pipelineStages: [],
+ };
+ },
+ apollo: {
+ linkedPipelines: {
+ context() {
+ return getQueryHeaders(this.pipelineEtag);
+ },
+ query: getLinkedPipelinesQuery,
+ pollInterval() {
+ return this.pollInterval;
+ },
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ iid: this.iid,
+ };
+ },
+ update({ project }) {
+ return project?.pipeline || this.linkedpipelines;
+ },
+ 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 });
+ },
+ },
+ },
+ computed: {
+ downstreamPipelines() {
+ return keepLatestDownstreamPipelines(this.linkedPipelines?.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 || ''}`,
+ };
+ });
+ },
+ pipelinePath() {
+ return this.linkedPipelines?.path || '';
+ },
+ upstreamPipeline() {
+ return this.linkedPipelines?.upstream;
+ },
+ },
+ mounted() {
+ toggleQueryPollingByVisibility(this.$apollo.queries.linkedPipelines);
+ toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStages);
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon v-if="$apollo.queries.pipelineStages.loading" />
+ <pipeline-mini-graph
+ v-else
+ data-testid="graphql-pipeline-mini-graph"
+ :downstream-pipelines="downstreamPipelines"
+ :is-merge-train="isMergeTrain"
+ :pipeline-path="pipelinePath"
+ :stages="formattedStages"
+ :upstream-pipeline="upstreamPipeline"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index d092c3ca630..a6dd835bb15 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -110,3 +110,7 @@ export const TRACKING_CATEGORIES = {
tabs: 'pipelines_filter_tabs',
search: 'pipelines_filtered_search',
};
+
+// Pipeline Mini Graph
+
+export const PIPELINE_MINI_GRAPH_POLL_INTERVAL = 5000;
diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_linked_pipelines.query.graphql
index 9257cc7de7b..9257cc7de7b 100644
--- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_linked_pipelines.query.graphql
diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_stages.query.graphql
index 69a29947b16..69a29947b16 100644
--- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_stages.query.graphql
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
index 54d13ecc9c8..84e7edb48c1 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
@@ -7,10 +7,12 @@ import {
toggleQueryPollingByVisibility,
} from '~/pipelines/components/graph/utils';
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
+import GraphqlPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql';
+import getPipelineStagesQuery from '~/pipelines/graphql/queries/get_pipeline_stages.query.graphql';
import { formatStages } from '../utils';
-import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql';
-import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql';
import { COMMIT_BOX_POLL_INTERVAL } from '../constants';
export default {
@@ -21,8 +23,10 @@ export default {
},
components: {
GlLoadingIcon,
+ GraphqlPipelineMiniGraph,
PipelineMiniGraph,
},
+ mixins: [glFeatureFlagsMixin()],
inject: {
fullPath: {
default: '',
@@ -47,15 +51,15 @@ export default {
},
query: getLinkedPipelinesQuery,
pollInterval: COMMIT_BOX_POLL_INTERVAL,
+ skip() {
+ return !this.fullPath || !this.iid || this.isUsingPipelineMiniGraphQueries;
+ },
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
- skip() {
- return !this.fullPath || !this.iid;
- },
update({ project }) {
return project?.pipeline;
},
@@ -69,6 +73,9 @@ export default {
},
query: getPipelineStagesQuery,
pollInterval: COMMIT_BOX_POLL_INTERVAL,
+ skip() {
+ return this.isUsingPipelineMiniGraphQueries;
+ },
variables() {
return {
fullPath: this.fullPath,
@@ -95,6 +102,9 @@ export default {
const downstream = this.pipeline?.downstream?.nodes;
return keepLatestDownstreamPipelines(downstream);
},
+ isUsingPipelineMiniGraphQueries() {
+ return this.glFeatures.ciGraphqlPipelineMiniGraph;
+ },
pipelinePath() {
return this.pipeline?.path ?? '';
},
@@ -128,13 +138,22 @@ export default {
<template>
<div>
<gl-loading-icon v-if="$apollo.queries.pipeline.loading" />
- <pipeline-mini-graph
- v-else
- data-testid="commit-box-pipeline-mini-graph"
- :downstream-pipelines="downstreamPipelines"
- :pipeline-path="pipelinePath"
- :stages="formattedStages"
- :upstream-pipeline="upstreamPipeline"
- />
+ <template v-else>
+ <graphql-pipeline-mini-graph
+ v-if="isUsingPipelineMiniGraphQueries"
+ data-testid="commit-box-pipeline-mini-graph"
+ :pipeline-etag="graphqlResourceEtag"
+ :full-path="fullPath"
+ :iid="iid"
+ />
+ <pipeline-mini-graph
+ v-else
+ data-testid="commit-box-pipeline-mini-graph"
+ :downstream-pipelines="downstreamPipelines"
+ :pipeline-path="pipelinePath"
+ :stages="formattedStages"
+ :upstream-pipeline="upstreamPipeline"
+ />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
new file mode 100644
index 00000000000..cfb4c1e447d
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlDisclosureDropdownGroup } from '@gitlab/ui';
+import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { COMMON_HANDLES, COMMAND_HANDLE, COMMANDS_GROUP_TITLE } from './constants';
+
+export default {
+ name: 'CommandPaletteItems',
+ components: {
+ GlDisclosureDropdownGroup,
+ },
+ inject: ['commandPaletteData'],
+ props: {
+ searchQuery: {
+ type: String,
+ required: true,
+ },
+ handle: {
+ type: String,
+ required: true,
+ validator: (value) => {
+ return COMMON_HANDLES.includes(value);
+ },
+ },
+ },
+ computed: {
+ isCommandMode() {
+ return this.handle === COMMAND_HANDLE;
+ },
+ filteredCommands() {
+ return this.searchQuery
+ ? fuzzaldrinPlus.filter(this.commands, this.searchQuery, {
+ key: 'keywords',
+ })
+ : this.commands;
+ },
+ commandsGroup() {
+ return {
+ name: COMMANDS_GROUP_TITLE,
+ items: this.filteredCommands,
+ };
+ },
+ commands() {
+ return this.commandPaletteData.map(({ text, href, keywords = [] }) => ({
+ text,
+ href,
+ keywords: keywords.join(''),
+ }));
+ },
+ hasResults() {
+ return this.commandsGroup.items?.length;
+ },
+ },
+};
+</script>
+
+<template>
+ <ul class="gl-p-0 gl-m-0 gl-list-style-none">
+ <gl-disclosure-dropdown-group
+ v-if="hasResults"
+ :group="commandsGroup"
+ bordered
+ class="gl-mt-0!"
+ />
+ <div v-else class="gl-text-gray-700 gl-pl-5 gl-py-3">{{ __('No results found') }}</div>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
new file mode 100644
index 00000000000..e2d325258ae
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js
@@ -0,0 +1,18 @@
+import { s__, sprintf } from '~/locale';
+
+export const COMMAND_HANDLE = '>';
+
+export const COMMON_HANDLES = [COMMAND_HANDLE];
+export const SEARCH_OR_COMMAND_MODE_PLACEHOLDER = sprintf(
+ s__('CommandPalette|Type %{commandHandle} for command or search...'),
+ {
+ commandHandle: COMMAND_HANDLE,
+ },
+ false,
+);
+
+export const SEARCH_SCOPE = {
+ [COMMAND_HANDLE]: s__('CommandPalette|command'),
+};
+
+export const COMMANDS_GROUP_TITLE = s__('CommandPalette|Commands');
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue
new file mode 100644
index 00000000000..201d21f56fe
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue
@@ -0,0 +1,42 @@
+<script>
+import { COMMON_HANDLES, SEARCH_SCOPE } from './constants';
+
+export default {
+ name: 'FakeSearchInput',
+ props: {
+ userInput: {
+ type: String,
+ required: true,
+ },
+ scope: {
+ type: String,
+ required: true,
+ validator: (value) => COMMON_HANDLES.includes(value),
+ },
+ },
+ computed: {
+ placeholder() {
+ return SEARCH_SCOPE[this.scope];
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-pointer-events-none fake-input">
+ <span class="gl-opacity-0" data-testid="search-scope">{{ scope }}&nbsp;</span>
+ <span
+ v-if="!userInput"
+ data-testid="search-scope-placeholder"
+ class="gl-text-gray-500 gl-pointer-events-none"
+ >{{ placeholder }}</span
+ >
+ </div>
+</template>
+
+<style scoped>
+.fake-input {
+ top: 12px;
+ left: 33px;
+}
+</style>
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
index 55c28661440..2534e62a301 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue
@@ -24,6 +24,7 @@ import {
SEARCH_RESULTS_LOADING,
SEARCH_RESULTS_SCOPE,
} from '~/vue_shared/global_search/constants';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
SEARCH_INPUT_DESCRIPTION,
SEARCH_RESULTS_DESCRIPTION,
@@ -35,6 +36,9 @@ import {
SEARCH_INPUT_SELECTOR,
SEARCH_RESULTS_ITEM_SELECTOR,
} from '../constants';
+import CommandPaletteItems from '../command_palette/command_palette_items.vue';
+import FakeSearchInput from '../command_palette/fake_search_input.vue';
+import { COMMAND_HANDLE, SEARCH_OR_COMMAND_MODE_PLACEHOLDER } from '../command_palette/constants';
import GlobalSearchAutocompleteItems from './global_search_autocomplete_items.vue';
import GlobalSearchDefaultItems from './global_search_default_items.vue';
import GlobalSearchScopedItems from './global_search_scoped_items.vue';
@@ -60,7 +64,10 @@ export default {
GlIcon,
GlToken,
GlModal,
+ CommandPaletteItems,
+ FakeSearchInput,
},
+ mixins: [glFeatureFlagMixin()],
computed: {
...mapState(['search', 'loading', 'searchContext']),
...mapGetters(['searchQuery', 'searchOptions', 'scopedSearchOptions']),
@@ -72,6 +79,9 @@ export default {
this.setSearch(value);
},
},
+ searchPlaceholder() {
+ return this.glFeatures?.commandPalette ? SEARCH_OR_COMMAND_MODE_PLACEHOLDER : SEARCH_GITLAB;
+ },
showDefaultItems() {
return !this.searchText;
},
@@ -104,7 +114,7 @@ export default {
};
},
showScopeHelp() {
- return this.searchTermOverMin;
+ return this.searchTermOverMin && !this.isCommandMode;
},
searchBarItem() {
return this.searchOptions?.[0];
@@ -120,10 +130,26 @@ export default {
scope: this.infieldHelpContent,
});
},
+
+ searchTextFirstChar() {
+ return this.searchText?.trim().charAt(0);
+ },
+ isCommandMode() {
+ return this.glFeatures?.commandPalette && this.searchTextFirstChar === COMMAND_HANDLE;
+ },
+ commandPaletteQuery() {
+ if (this.isCommandMode) {
+ return this.searchText?.trim().substring(1);
+ }
+ return '';
+ },
},
methods: {
...mapActions(['setSearch', 'fetchAutocompleteOptions', 'clearAutocomplete']),
getAutocompleteOptions: debounce(function debouncedSearch(searchTerm) {
+ if (this.isCommandMode) {
+ return;
+ }
if (!searchTerm) {
this.clearAutocomplete();
} else {
@@ -222,12 +248,12 @@ export default {
>
<form
role="search"
- :aria-label="$options.i18n.SEARCH_GITLAB"
+ :aria-label="searchPlaceholder"
class="gl-relative gl-rounded-base gl-w-full"
:class="searchBarClasses"
data-testid="global-search-form"
>
- <div class="gl-p-1">
+ <div class="gl-p-1 gl-relative">
<gl-search-box-by-type
id="search"
ref="searchInputBox"
@@ -236,7 +262,7 @@ export default {
data-testid="global-search-input"
data-qa-selector="global_search_input"
autocomplete="off"
- :placeholder="$options.i18n.SEARCH_GITLAB"
+ :placeholder="searchPlaceholder"
:aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
borderless
@input="getAutocompleteOptions"
@@ -266,6 +292,13 @@ export default {
<span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">
{{ $options.i18n.SEARCH_DESCRIBED_BY_WITH_RESULTS }}
</span>
+
+ <fake-search-input
+ v-if="isCommandMode"
+ :user-input="commandPaletteQuery"
+ :scope="searchTextFirstChar"
+ class="gl-absolute"
+ />
</div>
<span
role="region"
@@ -282,13 +315,20 @@ export default {
class="global-search-results gl-overflow-y-auto gl-w-full gl-pb-2"
@keydown="onKeydown"
>
- <global-search-default-items v-if="showDefaultItems" />
+ <command-palette-items
+ v-if="isCommandMode"
+ :search-query="commandPaletteQuery"
+ :handle="searchTextFirstChar"
+ />
+
<template v-else>
- <global-search-scoped-items v-if="showScopedSearchItems" />
- <global-search-autocomplete-items />
+ <global-search-default-items v-if="showDefaultItems" />
+ <template v-else>
+ <global-search-scoped-items v-if="showScopedSearchItems" />
+ <global-search-autocomplete-items />
+ </template>
</template>
</div>
-
<template v-if="searchContext">
<input
v-if="searchContext.group"
diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
index 63424277ffc..c6392cbc452 100644
--- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
+++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js
@@ -72,6 +72,7 @@ export const initSuperSidebar = () => {
const sidebarData = JSON.parse(sidebar);
const searchData = convertObjectPropsToCamelCase(sidebarData.search);
+ const commandPaletteData = convertObjectPropsToCamelCase(sidebarData.command_palette_commands);
const { searchPath, issuesPath, mrPath, autocompletePath, searchContext } = searchData;
const isImpersonating = parseBoolean(sidebarData.is_impersonating);
@@ -85,6 +86,7 @@ export const initSuperSidebar = () => {
toggleNewNavEndpoint,
isImpersonating,
...getTrialStatusWidgetData(sidebarData),
+ commandPaletteData,
},
store: createStore({
searchPath,
diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss
index a0bfca79dc3..f81371828f2 100644
--- a/app/assets/stylesheets/framework/broadcast_messages.scss
+++ b/app/assets/stylesheets/framework/broadcast_messages.scss
@@ -46,3 +46,7 @@
min-height: 34px;
}
}
+
+.gl-broadcast-message-content p:last-child {
+ margin: 0;
+}
diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb
index 01c34a74b84..8499bf0ced7 100644
--- a/app/controllers/projects/ci/pipeline_editor_controller.rb
+++ b/app/controllers/projects/ci/pipeline_editor_controller.rb
@@ -5,6 +5,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:ci_job_assistant_drawer, @project)
push_frontend_feature_flag(:ai_ci_config_generator, @user)
+ push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, @project)
end
feature_category :pipeline_composition
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 8aca6a3fd5b..ca8382fa301 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -19,6 +19,9 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_commit_box_vars, only: [:show, :pipelines]
before_action :define_note_vars, only: [:show, :diff_for_path, :diff_files]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
+ before_action do
+ push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, @project)
+ end
BRANCH_SEARCH_LIMIT = 1000
COMMIT_DIFFS_PER_PAGE = 20
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 02a912d0227..02b705a4e0d 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -89,7 +89,9 @@ module SidebarsHelper
update_pins_url: pins_url,
is_impersonating: impersonating?,
stop_impersonation_path: admin_impersonation_path,
- shortcut_links: shortcut_links(user, project: project)
+ shortcut_links: shortcut_links(user, project: project),
+ # command palette
+ command_palette_commands: create_command_palette_menu
}
end
@@ -171,6 +173,34 @@ module SidebarsHelper
end
end
+ def create_command_palette_menu
+ menu_items = []
+
+ if current_user.can_create_project?
+ menu_items.push({
+ text: _('New project/repository'),
+ href: new_project_path,
+ keywords: [_('Create a new project/repository')]
+ })
+ end
+
+ if current_user.can_create_group?
+ menu_items.push({
+ text: _('New group'),
+ href: new_group_path,
+ keywords: ['Create a new group']
+ })
+ end
+
+ return unless current_user.can?(:create_snippet)
+
+ menu_items.push({
+ text: _('New snippet'),
+ href: new_snippet_path,
+ keywords: ['Create a new snippet']
+ })
+ end
+
def create_merge_request_menu(user)
[
{
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index b910c0ab5c2..76c733b1c0b 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -114,6 +114,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:infrastructure_access_level, value)
end
+ def model_experiments_access_level=(value)
+ write_feature_attribute_string(:model_experiments_access_level, value)
+ end
+
# TODO: Remove this method after we drop support for project create/edit APIs to set the
# container_registry_enabled attribute. They can instead set the container_registry_access_level
# attribute.
diff --git a/app/models/project.rb b/app/models/project.rb
index 167b9283452..2e94596de5a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -491,6 +491,7 @@ class Project < ApplicationRecord
:operations_access_level, :security_and_compliance_access_level,
:container_registry_access_level, :environments_access_level, :feature_flags_access_level,
:monitor_access_level, :releases_access_level, :infrastructure_access_level,
+ :model_experiments_access_level,
to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 772a82fa173..3c533b4a60d 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -26,6 +26,7 @@ class ProjectFeature < ApplicationRecord
feature_flags
releases
infrastructure
+ model_experiments
].freeze
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
@@ -79,6 +80,7 @@ class ProjectFeature < ApplicationRecord
attribute :infrastructure_access_level, default: ENABLED
attribute :feature_flags_access_level, default: ENABLED
attribute :environments_access_level, default: ENABLED
+ attribute :model_experiments_access_level, default: ENABLED
attribute :package_registry_access_level, default: -> do
if ::Gitlab.config.packages.enabled
diff --git a/app/views/devise/sessions/email_verification.haml b/app/views/devise/sessions/email_verification.haml
index 6cafcb941b4..e0b5a266961 100644
--- a/app/views/devise/sessions/email_verification.haml
+++ b/app/views/devise/sessions/email_verification.haml
@@ -2,7 +2,7 @@
= render 'devise/shared/tab_single', tab_title: s_('IdentityVerification|Help us protect your account')
.login-box.gl-p-5
.login-body
- = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'gl-show-field-errors' }) do |f|
+ = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'gl-show-field-errors' }) do |f|
%p
= s_("IdentityVerification|For added security, you'll need to verify your identity. We've sent a verification code to %{email}").html_safe % { email: "<strong>#{sanitize(obfuscated_email(resource.email))}</strong>".html_safe }
%div
@@ -11,7 +11,7 @@
%p.gl-field-error.gl-mt-2
= resource.errors.full_messages.to_sentence
.gl-mt-5
- = f.submit s_('IdentityVerification|Verify code'), class: 'gl-button btn btn-confirm'
+ = f.submit s_('IdentityVerification|Verify code'), class: 'gl-w-full', pajamas_button: true
- unless send_rate_limited?(resource)
= link_to s_('IdentityVerification|Resend code'), users_resend_verification_code_path, method: :post, class: 'form-control gl-button btn-link gl-mt-3 gl-mb-0'
%p.gl-p-5.gl-text-secondary
diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml
index 21b1986bd34..d92a6b08b60 100644
--- a/app/views/groups/settings/_advanced.html.haml
+++ b/app/views/groups/settings/_advanced.html.haml
@@ -3,7 +3,7 @@
.sub-section
%h4.warning-title= s_('GroupSettings|Change group URL')
- = form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+ = gitlab_ui_form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
= form_errors(@group)
.form-group
%p
@@ -23,7 +23,7 @@
title: group_url_error_message,
maxlength: ::Namespace::URL_MAX_LENGTH,
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- = f.submit s_('GroupSettings|Change group URL'), class: 'btn gl-button btn-danger'
+ = f.submit s_('GroupSettings|Change group URL'), class: 'btn-danger', pajamas_button: true
= render 'groups/settings/transfer', group: @group
= render 'groups/settings/remove', group: @group, remove_form_id: remove_form_id
diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml
index 9f04b579a97..74f9298133b 100644
--- a/app/views/groups/settings/_lfs.html.haml
+++ b/app/views/groups/settings/_lfs.html.haml
@@ -7,6 +7,6 @@
.form-group.gl-mb-3
= f.gitlab_ui_checkbox_component :lfs_enabled,
- _('Allow projects within this group to use Git LFS'),
- help_text: _('Can be overridden in each project.'),
+ _('Projects in this group can use Git LFS'),
+ help_text: _('Possible to override in each project.'),
checkbox_options: { checked: @group.lfs_enabled?, data: { qa_selector: 'lfs_checkbox' } }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 02a69f25985..fd90a941c7b 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -91,7 +91,7 @@
.sub-section.rename-repository
%h4.warning-title= _('Change path')
= render 'projects/errors'
- = form_for @project do |f|
+ = gitlab_ui_form_for @project do |f|
.form-group
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/settings/index', anchor: 'rename-a-repository') }
%p= _("A project’s repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
@@ -107,7 +107,7 @@
.input-group-text
#{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
= f.text_field :path, class: 'form-control h-auto', data: { qa_selector: 'project_path_field' }
- = f.submit _('Change path'), class: "gl-button btn btn-danger", data: { qa_selector: 'change_path_button' }
+ = f.submit _('Change path'), class: "btn-danger", data: { qa_selector: 'change_path_button' }, pajamas_button: true
= render 'transfer', project: @project
diff --git a/app/views/shared/_broadcast_message.html.haml b/app/views/shared/_broadcast_message.html.haml
index a2fed883739..b065d5d2dc9 100644
--- a/app/views/shared/_broadcast_message.html.haml
+++ b/app/views/shared/_broadcast_message.html.haml
@@ -19,21 +19,22 @@
icon: 'close',
size: :small,
button_options: { class: 'gl-close-btn-color-inherit gl-broadcast-message-dismiss js-dismiss-current-broadcast-notification', 'aria-label': _('Close'), data: { id: message.id, expire_date: message.ends_at.iso8601 } },
- icon_classes: 'gl-mx-3! gl-text-white')
+ icon_classes: 'gl-text-white')
- else
- notification_class = "js-broadcast-notification-#{message.id}"
- notification_class << ' preview' if preview
- .broadcast-message.broadcast-notification-message.mt-2{ role: "alert", class: notification_class, data: { qa_selector: 'broadcast_notification_container' } }
- = sprite_icon(icon_name, css_class: 'vertical-align-text-top')
- - if message.message.present?
- %h2.gl-sr-only
- = s_("Admin message")
- = render_broadcast_message(message)
- - else
- = yield
+ .gl-broadcast-message.broadcast-notification-message.gl-mt-3{ role: "alert", class: notification_class, data: { qa_selector: 'broadcast_notification_container' } }
+ .gl-broadcast-message-content
+ .gl-broadcast-message-icon
+ = sprite_icon(icon_name, css_class: 'vertical-align-text-top')
+ - if message.message.present?
+ %h2.gl-sr-only
+ = s_("Admin message")
+ = render_broadcast_message(message)
+ - else
+ = yield
- if !preview
- = render Pajamas::ButtonComponent.new(variant: :link,
+ = render Pajamas::ButtonComponent.new(category: :tertiary,
icon: 'close',
size: :small,
- button_options: { class: 'js-dismiss-current-broadcast-notification', 'aria-label': _('Close'), data: { id: message.id, expire_date: message.ends_at.iso8601, qa_selector: 'close_button' } },
- icon_classes: 'gl-mx-3! gl-text-gray-700')
+ button_options: { class: 'js-dismiss-current-broadcast-notification', 'aria-label': _('Close'), data: { id: message.id, expire_date: message.ends_at.iso8601, qa_selector: 'close_button' } })