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
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/api.js1
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_link_button.vue89
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue3
-rw-r--r--app/assets/javascripts/content_editor/extensions/link.js4
-rw-r--r--app/assets/javascripts/content_editor/services/utils.js5
-rw-r--r--app/assets/javascripts/diffs/store/utils.js107
-rw-r--r--app/assets/javascripts/diffs/utils/workers.js107
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js2
-rw-r--r--app/assets/javascripts/groups_select.js17
-rw-r--r--app/assets/javascripts/serverless/components/empty_state.vue4
-rw-r--r--app/assets/javascripts/serverless/components/missing_prometheus.vue4
-rw-r--r--app/controllers/application_controller.rb13
-rw-r--r--app/helpers/clusters_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/presenters/project_presenter.rb2
-rw-r--r--app/views/clusters/clusters/show.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml3
-rw-r--r--app/views/import/shared/_errors.html.haml10
-rw-r--r--app/views/projects/project_members/index.html.haml4
-rw-r--r--app/views/projects/services/prometheus/_configuration_banner.html.haml10
-rw-r--r--app/views/projects/settings/operations/_configuration_banner.html.haml10
-rw-r--r--app/views/shared/members/_invite_group.html.haml4
-rw-r--r--config/initializers/6_labkit_middleware.rb (renamed from config/initializers/labkit_middleware.rb)2
-rw-r--r--config/initializers/7_prometheus_metrics.rb2
-rw-r--r--db/migrate/20210526135911_create_ci_minutes_additional_packs.rb25
-rw-r--r--db/schema_migrations/202105261359111
-rw-r--r--db/structure.sql30
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/api/projects.md7
-rw-r--r--doc/development/architecture.md23
-rw-r--r--doc/development/i18n/externalization.md330
-rw-r--r--doc/development/i18n/index.md53
-rw-r--r--doc/development/kubernetes.md9
-rw-r--r--doc/subscriptions/self_managed/index.md2
-rw-r--r--doc/topics/autodevops/quick_start_guide.md1
-rw-r--r--doc/topics/autodevops/requirements.md46
-rw-r--r--doc/topics/autodevops/stages.md5
-rw-r--r--doc/user/clusters/cost_management.md6
-rw-r--r--doc/user/group/clusters/index.md14
-rw-r--r--doc/user/project/canary_deployments.md2
-rw-r--r--doc/user/project/clusters/add_eks_clusters.md5
-rw-r--r--doc/user/project/clusters/add_gke_clusters.md7
-rw-r--r--doc/user/project/clusters/add_remove_clusters.md36
-rw-r--r--doc/user/project/clusters/index.md31
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md4
-rw-r--r--doc/user/project/clusters/runbooks/index.md99
-rw-r--r--doc/user/project/clusters/serverless/index.md64
-rw-r--r--doc/user/project/integrations/prometheus.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md22
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md22
-rw-r--r--doc/user/project/merge_requests/approvals/rules.md2
-rw-r--r--doc/user/project/merge_requests/approvals/settings.md2
-rw-r--r--doc/user/project/merge_requests/changes.md2
-rw-r--r--doc/user/project/repository/jupyter_notebooks/index.md9
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/helpers.rb5
-rw-r--r--lib/api/internal/base.rb2
-rw-r--r--lib/gitlab/application_context.rb4
-rw-r--r--lib/gitlab/etag_caching/middleware.rb5
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb7
-rw-r--r--locale/gitlab.pot42
-rw-r--r--qa/qa/page/component/issuable/sidebar.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb5
-rw-r--r--spec/features/groups/members/manage_groups_spec.rb52
-rw-r--r--spec/features/projects/serverless/functions_spec.rb1
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap36
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js151
-rw-r--r--spec/frontend/content_editor/test_utils.js18
-rw-r--r--spec/frontend/diffs/store/actions_spec.js6
-rw-r--r--spec/frontend/diffs/store/utils_spec.js306
-rw-r--r--spec/frontend/diffs/utils/workers_spec.js309
-rw-r--r--spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap2
-rw-r--r--spec/frontend/serverless/components/missing_prometheus_spec.js2
-rw-r--r--spec/helpers/environments_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/application_context_spec.rb18
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb18
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb27
-rw-r--r--spec/presenters/project_presenter_spec.rb2
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb2
79 files changed, 1349 insertions, 954 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 5d69340c9a8..41cc2036a6b 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -18,6 +18,7 @@ const Api = {
groupMembersPath: '/api/:version/groups/:id/members',
groupMilestonesPath: '/api/:version/groups/:id/milestones',
subgroupsPath: '/api/:version/groups/:id/subgroups',
+ descendantGroupsPath: '/api/:version/groups/:id/descendant_groups',
namespacesPath: '/api/:version/namespaces.json',
groupInvitationsPath: '/api/:version/groups/:id/invitations',
groupPackagesPath: '/api/:version/groups/:id/packages',
diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
new file mode 100644
index 00000000000..369eadceff9
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
@@ -0,0 +1,89 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownForm,
+ GlButton,
+ GlFormInputGroup,
+ GlDropdownDivider,
+ GlDropdownItem,
+} from '@gitlab/ui';
+import { Editor as TiptapEditor } from '@tiptap/vue-2';
+import { hasSelection } from '../services/utils';
+
+export const linkContentType = 'link';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownForm,
+ GlFormInputGroup,
+ GlDropdownDivider,
+ GlDropdownItem,
+ GlButton,
+ },
+ props: {
+ tiptapEditor: {
+ type: TiptapEditor,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ linkHref: '',
+ };
+ },
+ computed: {
+ isActive() {
+ return this.tiptapEditor.isActive(linkContentType);
+ },
+ },
+ mounted() {
+ this.tiptapEditor.on('selectionUpdate', ({ editor }) => {
+ const { href } = editor.getAttributes(linkContentType);
+
+ this.linkHref = href;
+ });
+ },
+ methods: {
+ updateLink() {
+ this.tiptapEditor.chain().focus().unsetLink().setLink({ href: this.linkHref }).run();
+
+ this.$emit('execute', { contentType: linkContentType });
+ },
+ selectLink() {
+ const { tiptapEditor } = this;
+
+ // a selection has already been made by the user, so do nothing
+ if (!hasSelection(tiptapEditor)) {
+ tiptapEditor.chain().focus().extendMarkRange(linkContentType).run();
+ }
+ },
+ removeLink() {
+ this.tiptapEditor.chain().focus().unsetLink().run();
+
+ this.$emit('execute', { contentType: linkContentType });
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ :toggle-class="{ active: isActive }"
+ size="small"
+ category="tertiary"
+ icon="link"
+ @show="selectLink()"
+ >
+ <gl-dropdown-form class="gl-px-3!">
+ <gl-form-input-group v-model="linkHref" :placeholder="__('Link URL')">
+ <template #append>
+ <gl-button variant="confirm" @click="updateLink()">{{ __('Apply') }}</gl-button>
+ </template>
+ </gl-form-input-group>
+ </gl-dropdown-form>
+ <gl-dropdown-divider v-if="isActive" />
+ <gl-dropdown-item v-if="isActive" @click="removeLink()">
+ {{ __('Remove link') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index e0acee681be..4da230cbde6 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -4,6 +4,7 @@ import { CONTENT_EDITOR_TRACKING_LABEL, TOOLBAR_CONTROL_TRACKING_ACTION } from '
import { ContentEditor } from '../services/content_editor';
import Divider from './divider.vue';
import ToolbarButton from './toolbar_button.vue';
+import ToolbarLinkButton from './toolbar_link_button.vue';
import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue';
const trackingMixin = Tracking.mixin({
@@ -14,6 +15,7 @@ export default {
components: {
ToolbarButton,
ToolbarTextStyleDropdown,
+ ToolbarLinkButton,
Divider,
},
mixins: [trackingMixin],
@@ -70,6 +72,7 @@ export default {
:tiptap-editor="contentEditor.tiptapEditor"
@execute="trackToolbarControlExecution"
/>
+ <toolbar-link-button :tiptap-editor="contentEditor.tiptapEditor" />
<divider />
<toolbar-button
data-testid="blockquote"
diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js
index 9a2fa7a5c98..5b368e72900 100644
--- a/app/assets/javascripts/content_editor/extensions/link.js
+++ b/app/assets/javascripts/content_editor/extensions/link.js
@@ -1,5 +1,7 @@
import { Link } from '@tiptap/extension-link';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
-export const tiptapExtension = Link;
+export const tiptapExtension = Link.configure({
+ openOnClick: false,
+});
export const serializer = defaultMarkdownSerializer.marks.link;
diff --git a/app/assets/javascripts/content_editor/services/utils.js b/app/assets/javascripts/content_editor/services/utils.js
new file mode 100644
index 00000000000..cf5234bbff8
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/utils.js
@@ -0,0 +1,5 @@
+export const hasSelection = (tiptapEditor) => {
+ const { from, to } = tiptapEditor.state.selection;
+
+ return from < to;
+};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 7fa51b9ddea..5eda509163e 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -1,6 +1,5 @@
import { property, isEqual } from 'lodash';
import { diffModes, diffViewerModes } from '~/ide/constants';
-import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
@@ -11,7 +10,6 @@ import {
OLD_LINE_TYPE,
MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
- TREE_TYPE,
INLINE_DIFF_LINES_KEY,
SHOW_WHITESPACE,
NO_SHOW_WHITESPACE,
@@ -485,111 +483,6 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && line_code === discussion.line_code;
}
-export const getLowestSingleFolder = (folder) => {
- const getFolder = (blob, start = []) =>
- blob.tree.reduce(
- (acc, file) => {
- const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
- const currentFileTypeTree = file.type === TREE_TYPE;
- const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
- const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
-
- if (shouldGetFolder) {
- const firstFolder = getFolder(file);
-
- path.push(...firstFolder.path);
- tree.push(...firstFolder.tree);
- }
-
- return {
- ...acc,
- path,
- tree,
- };
- },
- { path: start, tree: [] },
- );
- const { path, tree } = getFolder(folder, [folder.name]);
-
- return {
- path: truncatePathMiddleToLength(path.join('/'), 40),
- treeAcc: tree.length ? tree[tree.length - 1].tree : null,
- };
-};
-
-export const flattenTree = (tree) => {
- const flatten = (blobTree) =>
- blobTree.reduce((acc, file) => {
- const blob = file;
- let treeToFlatten = blob.tree;
-
- if (file.type === TREE_TYPE && file.tree.length === 1) {
- const { treeAcc, path } = getLowestSingleFolder(file);
-
- if (treeAcc) {
- blob.name = path;
- treeToFlatten = flatten(treeAcc);
- }
- }
-
- blob.tree = flatten(treeToFlatten);
-
- return acc.concat(blob);
- }, []);
-
- return flatten(tree);
-};
-
-export const generateTreeList = (files) => {
- const { treeEntries, tree } = files.reduce(
- (acc, file) => {
- const split = file.new_path.split('/');
-
- split.forEach((name, i) => {
- const parent = acc.treeEntries[split.slice(0, i).join('/')];
- const path = `${parent ? `${parent.path}/` : ''}${name}`;
-
- if (!acc.treeEntries[path]) {
- const type = path === file.new_path ? 'blob' : 'tree';
- acc.treeEntries[path] = {
- key: path,
- path,
- name,
- type,
- tree: [],
- };
-
- const entry = acc.treeEntries[path];
-
- if (type === 'blob') {
- Object.assign(entry, {
- changed: true,
- tempFile: file.new_file,
- deleted: file.deleted_file,
- fileHash: file.file_hash,
- addedLines: file.added_lines,
- removedLines: file.removed_lines,
- parentPath: parent ? `${parent.path}/` : '/',
- submodule: file.submodule,
- });
- } else {
- Object.assign(entry, {
- opened: true,
- });
- }
-
- (parent ? parent.tree : acc.tree).push(entry);
- }
- });
-
- return acc;
- },
- { treeEntries: {}, tree: [] },
- );
-
- return { treeEntries, tree: flattenTree(tree) };
-};
-
export const getDiffMode = (diffFile) => {
const diffModeKey = Object.keys(diffModes).find((key) => diffFile[`${key}_file`]);
return (
diff --git a/app/assets/javascripts/diffs/utils/workers.js b/app/assets/javascripts/diffs/utils/workers.js
new file mode 100644
index 00000000000..985e75d1a17
--- /dev/null
+++ b/app/assets/javascripts/diffs/utils/workers.js
@@ -0,0 +1,107 @@
+import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
+import { TREE_TYPE } from '../constants';
+
+export const getLowestSingleFolder = (folder) => {
+ const getFolder = (blob, start = []) =>
+ blob.tree.reduce(
+ (acc, file) => {
+ const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
+ const currentFileTypeTree = file.type === TREE_TYPE;
+ const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
+ const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
+
+ if (shouldGetFolder) {
+ const firstFolder = getFolder(file);
+
+ path.push(...firstFolder.path);
+ tree.push(...firstFolder.tree);
+ }
+
+ return {
+ ...acc,
+ path,
+ tree,
+ };
+ },
+ { path: start, tree: [] },
+ );
+ const { path, tree } = getFolder(folder, [folder.name]);
+
+ return {
+ path: truncatePathMiddleToLength(path.join('/'), 40),
+ treeAcc: tree.length ? tree[tree.length - 1].tree : null,
+ };
+};
+
+export const flattenTree = (tree) => {
+ const flatten = (blobTree) =>
+ blobTree.reduce((acc, file) => {
+ const blob = file;
+ let treeToFlatten = blob.tree;
+
+ if (file.type === TREE_TYPE && file.tree.length === 1) {
+ const { treeAcc, path } = getLowestSingleFolder(file);
+
+ if (treeAcc) {
+ blob.name = path;
+ treeToFlatten = flatten(treeAcc);
+ }
+ }
+
+ blob.tree = flatten(treeToFlatten);
+
+ return acc.concat(blob);
+ }, []);
+
+ return flatten(tree);
+};
+
+export const generateTreeList = (files) => {
+ const { treeEntries, tree } = files.reduce(
+ (acc, file) => {
+ const split = file.new_path.split('/');
+
+ split.forEach((name, i) => {
+ const parent = acc.treeEntries[split.slice(0, i).join('/')];
+ const path = `${parent ? `${parent.path}/` : ''}${name}`;
+
+ if (!acc.treeEntries[path]) {
+ const type = path === file.new_path ? 'blob' : 'tree';
+ acc.treeEntries[path] = {
+ key: path,
+ path,
+ name,
+ type,
+ tree: [],
+ };
+
+ const entry = acc.treeEntries[path];
+
+ if (type === 'blob') {
+ Object.assign(entry, {
+ changed: true,
+ tempFile: file.new_file,
+ deleted: file.deleted_file,
+ fileHash: file.file_hash,
+ addedLines: file.added_lines,
+ removedLines: file.removed_lines,
+ parentPath: parent ? `${parent.path}/` : '/',
+ submodule: file.submodule,
+ });
+ } else {
+ Object.assign(entry, {
+ opened: true,
+ });
+ }
+
+ (parent ? parent.tree : acc.tree).push(entry);
+ }
+ });
+
+ return acc;
+ },
+ { treeEntries: {}, tree: [] },
+ );
+
+ return { treeEntries, tree: flattenTree(tree) };
+};
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
index 2fa1934439e..6d1bc78ba1c 100644
--- a/app/assets/javascripts/diffs/workers/tree_worker.js
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -1,5 +1,5 @@
import { sortTree } from '~/ide/stores/utils';
-import { generateTreeList } from '../store/utils';
+import { generateTreeList } from '../utils/workers';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', (e) => {
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index 93fbbf07ae2..bd71c5ebc11 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -5,6 +5,17 @@ import Api from './api';
import { loadCSSFile } from './lib/utils/css_utils';
import { select2AxiosTransport } from './lib/utils/select2_utils';
+const groupsPath = (groupsFilter, parentGroupID) => {
+ switch (groupsFilter) {
+ case 'descendant_groups':
+ return Api.descendantGroupsPath.replace(':id', parentGroupID);
+ case 'subgroups':
+ return Api.subgroupsPath.replace(':id', parentGroupID);
+ default:
+ return Api.groupsPath;
+ }
+};
+
const groupsSelect = () => {
loadCSSFile(gon.select2_css_path)
.then(() => {
@@ -16,9 +27,7 @@ const groupsSelect = () => {
const allAvailable = $select.data('allAvailable');
const skipGroups = $select.data('skipGroups') || [];
const parentGroupID = $select.data('parentId');
- const groupsPath = parentGroupID
- ? Api.subgroupsPath.replace(':id', parentGroupID)
- : Api.groupsPath;
+ const groupsFilter = $select.data('groupsFilter');
$select.select2({
placeholder: __('Search for a group'),
@@ -26,7 +35,7 @@ const groupsSelect = () => {
multiple: $select.hasClass('multiselect'),
minimumInputLength: 0,
ajax: {
- url: Api.buildUrl(groupsPath),
+ url: Api.buildUrl(groupsPath(groupsFilter, parentGroupID)),
dataType: 'json',
quietMillis: 250,
transport: select2AxiosTransport,
diff --git a/app/assets/javascripts/serverless/components/empty_state.vue b/app/assets/javascripts/serverless/components/empty_state.vue
index 49922ad8e6c..8a5ed9debb3 100644
--- a/app/assets/javascripts/serverless/components/empty_state.vue
+++ b/app/assets/javascripts/serverless/components/empty_state.vue
@@ -9,7 +9,7 @@ export default {
GlSprintf,
},
computed: {
- ...mapState(['clustersPath', 'emptyImagePath', 'helpPath']),
+ ...mapState(['emptyImagePath', 'helpPath']),
},
};
</script>
@@ -18,8 +18,6 @@ export default {
<gl-empty-state
:svg-path="emptyImagePath"
:title="s__('Serverless|Getting started with serverless')"
- :primary-button-link="clustersPath"
- :primary-button-text="s__('Serverless|Install Knative')"
>
<template #description>
<gl-sprintf
diff --git a/app/assets/javascripts/serverless/components/missing_prometheus.vue b/app/assets/javascripts/serverless/components/missing_prometheus.vue
index 0b83d4b36eb..0023c64e3e4 100644
--- a/app/assets/javascripts/serverless/components/missing_prometheus.vue
+++ b/app/assets/javascripts/serverless/components/missing_prometheus.vue
@@ -26,7 +26,7 @@ export default {
return this.missingData
? s__(`ServerlessDetails|Invocation metrics loading or not available at this time.`)
: s__(
- `ServerlessDetails|Function invocation metrics require Prometheus to be installed first.`,
+ `ServerlessDetails|Function invocation metrics require the Prometheus cluster integration.`,
);
},
},
@@ -48,7 +48,7 @@ export default {
<div v-if="!missingData" class="text-left">
<gl-button :href="clustersPath" variant="success" category="primary">
- {{ s__('ServerlessDetails|Install Prometheus') }}
+ {{ s__('ServerlessDetails|Configure cluster.') }}
</gl-button>
</div>
</div>
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a08f1f5d391..07ecde1181f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -263,7 +263,6 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
- headers[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
end
def default_cache_headers
@@ -456,17 +455,17 @@ class ApplicationController < ActionController::Base
end
def set_current_context(&block)
- Gitlab::ApplicationContext.with_context(
+ Gitlab::ApplicationContext.push(
user: -> { context_user },
project: -> { @project if @project&.persisted? },
namespace: -> { @group if @group&.persisted? },
caller_id: caller_id,
remote_ip: request.ip,
- feature_category: feature_category) do
- yield
- ensure
- @current_context = Gitlab::ApplicationContext.current
- end
+ feature_category: feature_category
+ )
+ yield
+ ensure
+ @current_context = Gitlab::ApplicationContext.current
end
def set_locale(&block)
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 439628f40c6..14783882f5e 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -44,7 +44,7 @@ module ClustersHelper
base_domain: cluster.base_domain,
application_ingress_external_ip: cluster.application_ingress_external_ip,
auto_devops_help_path: help_page_path('topics/autodevops/index'),
- external_endpoint_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint')
+ external_endpoint_help_path: help_page_path('user/project/clusters/index.md', anchor: 'base-domain')
}
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 177c73bd3f0..5927c82abe9 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -36,7 +36,7 @@ module EnvironmentsHelper
"environment_name": environment.name,
"environments_path": api_v4_projects_environments_path(id: project.id),
"environment_id": environment.id,
- "cluster_applications_documentation_path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "cluster_applications_documentation_path" => help_page_path('user/clusters/integrations.md', anchor: 'elastic-stack-cluster-integration'),
"clusters_path": project_clusters_path(project, format: :json)
}
end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 4f803ba34f4..fcd3189296a 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -264,7 +264,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
'original_branch' => default_branch_or_main,
'can_push_code' => 'true',
'path' => project_create_blob_path(project, default_branch_or_main),
- 'project_path' => project.path
+ 'project_path' => project.full_path
}
)
end
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 7639b472a0b..8e27b6e5c75 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -26,7 +26,7 @@
cluster_status_reason: @cluster.status_reason,
provider_type: @cluster.provider_type,
pre_installed_knative: @cluster.knative_pre_installed? ? 'true': 'false',
- help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
+ help_path: help_page_path('user/project/clusters/index.md'),
helm_help_path: help_page_path('user/clusters/applications.md', anchor: 'helm'),
ingress_help_path: help_page_path('user/clusters/applications.md', anchor: 'determining-the-external-endpoint-automatically'),
ingress_dns_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint'),
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 4e4ea4c2de7..63459466b3b 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,5 +1,6 @@
- add_page_specific_style 'page_bundles/members'
- page_title _('Group members')
+- groups_select_tag_data = @group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy ? { groups_filter: 'descendant_groups', parent_id: @group.root_ancestor.id, skip_groups: @skip_groups } : { skip_groups: @skip_groups }
.js-remove-member-modal
.row.gl-mt-3
@@ -28,7 +29,7 @@
.tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
= render_invite_member_for_group(@group, @group_member.access_level)
.tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'shared/members/invite_group', submit_url: group_group_links_path(@group), access_levels: GroupMember.access_level_roles, default_access_level: @group_member.access_level, group_link_field: 'shared_with_group_id', group_access_field: 'shared_group_access'
+ = render 'shared/members/invite_group', submit_url: group_group_links_path(@group), access_levels: GroupMember.access_level_roles, default_access_level: @group_member.access_level, group_link_field: 'shared_with_group_id', group_access_field: 'shared_group_access', groups_select_tag_data: groups_select_tag_data
= render_if_exists 'groups/group_members/ldap_sync'
diff --git a/app/views/import/shared/_errors.html.haml b/app/views/import/shared/_errors.html.haml
index 32b4a39924b..badd8c1278f 100644
--- a/app/views/import/shared/_errors.html.haml
+++ b/app/views/import/shared/_errors.html.haml
@@ -1,6 +1,8 @@
- if @errors.present?
.gl-alert.gl-alert-danger.gl-mb-5
- = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-body
- - @errors.each do |error|
- = error
+ .gl-alert-container
+ = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ .gl-alert-body
+ - @errors.each do |error|
+ = error
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9f795344713..352d418efa9 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -51,11 +51,11 @@
.tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
= render 'shared/members/invite_member', submit_url: project_project_members_path(@project), access_levels: ProjectMember.access_level_roles, default_access_level: @project_member.access_level, can_import_members?: can_import_members?, import_path: import_project_project_members_path(@project)
.tab-pane{ id: 'invite-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) }
- = render 'shared/members/invite_group', submit_url: project_group_links_path(@project), access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, group_link_field: 'link_group_id', group_access_field: 'link_group_access'
+ = render 'shared/members/invite_group', submit_url: project_group_links_path(@project), access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, group_link_field: 'link_group_id', group_access_field: 'link_group_access', groups_select_tag_data: { skip_groups: @skip_groups }
- elsif !membership_locked?
.invite-member= render 'shared/members/invite_member', submit_url: project_project_members_path(@project), access_levels: ProjectMember.access_level_roles, default_access_level: @project_member.access_level, can_import_members?: can_import_members?, import_path: import_project_project_members_path(@project)
- elsif @project.allowed_to_share_with_group?
- .invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access'
+ .invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access', groups_select_tag_data: { skip_groups: @skip_groups }
.js-project-members-list-app{ data: { members_data: project_members_app_data_json(@project,
members: @project_members,
group_links: @group_links,
diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml
index dfcadabe6c5..a34aa22acbb 100644
--- a/app/views/projects/services/prometheus/_configuration_banner.html.haml
+++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml
@@ -1,9 +1,9 @@
%h4
- = s_('PrometheusService|Auto configuration')
+ = s_('PrometheusService|Prometheus cluster integration')
- if integration.manual_configuration?
.info-well
- = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration.')
+ = s_('PrometheusService|To use a Prometheus installed on a cluster, deactivate the manual configuration.')
- else
.container-fluid
.row
@@ -13,14 +13,14 @@
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.gl-mt-3
- = s_('PrometheusService|GitLab is managing Prometheus on your clusters.')
+ = s_('PrometheusService|You have a cluster with the Prometheus integration enabled.')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn gl-button'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.gl-mt-3
- = s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments.')
- = link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'btn gl-button btn-confirm'
+ = s_('PrometheusService|Configure GitLab to query a Prometheus installed in one of your clusters.')
+ = link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn gl-button btn-confirm'
%hr
diff --git a/app/views/projects/settings/operations/_configuration_banner.html.haml b/app/views/projects/settings/operations/_configuration_banner.html.haml
index 6fa6b23b0da..9803ffc3c4e 100644
--- a/app/views/projects/settings/operations/_configuration_banner.html.haml
+++ b/app/views/projects/settings/operations/_configuration_banner.html.haml
@@ -1,9 +1,9 @@
%b
- = s_('PrometheusService|Auto configuration')
+ = s_('PrometheusService|Prometheus cluster integration')
- if service.manual_configuration?
.info-well.p-2.mt-2
- = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration.')
+ = s_('PrometheusService|To use a Prometheus installed on a cluster, deactivate the manual configuration.')
- else
.container-fluid
.row
@@ -13,12 +13,12 @@
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.gl-mt-3
- = s_('PrometheusService|GitLab manages Prometheus on your clusters.')
+ = s_('PrometheusService|You have a cluster with the Prometheus integration enabled.')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'gl-button btn btn-default'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.gl-mt-3
- = s_('PrometheusService|Monitor your project’s environments by deploying and configuring Prometheus on your clusters.')
- = link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(project), class: 'gl-button btn btn-confirm'
+ = s_('PrometheusService|Configure GitLab to query a Prometheus installed in one of your clusters.')
+ = link_to s_('PrometheusService|Manage clusters'), project_clusters_path(project), class: 'btn gl-button btn-confirm'
diff --git a/app/views/shared/members/_invite_group.html.haml b/app/views/shared/members/_invite_group.html.haml
index 58f658ce9c3..d8b4eb0a3a2 100644
--- a/app/views/shared/members/_invite_group.html.haml
+++ b/app/views/shared/members/_invite_group.html.haml
@@ -3,12 +3,14 @@
- submit_url = local_assigns[:submit_url]
- group_link_field = local_assigns[:group_link_field]
- group_access_field = local_assigns[:group_access_field]
+- groups_select_tag_data = local_assigns[:groups_select_tag_data]
+
.row
.col-sm-12
= form_tag submit_url, class: 'invite-group-form js-requires-input', method: :post do
.form-group
= label_tag group_link_field, _("Select a group to invite"), class: "label-bold"
- = groups_select_tag(group_link_field, data: { skip_groups: @skip_groups }, class: 'input-clamp qa-group-select-field', required: true)
+ = groups_select_tag(group_link_field, data: groups_select_tag_data, class: 'input-clamp qa-group-select-field', required: true)
.form-text.text-muted.gl-mb-3
= _('Group sharing provides access to all group members (including members who inherited group membership from a parent group).')
.form-group
diff --git a/config/initializers/labkit_middleware.rb b/config/initializers/6_labkit_middleware.rb
index 771adabfeeb..9aad1e3f92b 100644
--- a/config/initializers/labkit_middleware.rb
+++ b/config/initializers/6_labkit_middleware.rb
@@ -17,4 +17,4 @@ unless Rails::Configuration::MiddlewareStackProxy.method_defined?(:move)
end
Rails.application.config.middleware.move(1, ActionDispatch::RequestId)
-Rails.application.config.middleware.insert_after(ActionDispatch::RequestId, Labkit::Middleware::Rack)
+Rails.application.config.middleware.insert(1, Labkit::Middleware::Rack)
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 35acf26c796..8dee21016f9 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -27,7 +27,7 @@ end
Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware
- config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
+ config.middleware.insert_after(Labkit::Middleware::Rack, Gitlab::Metrics::RequestsRackMiddleware)
end
Sidekiq.configure_server do |config|
diff --git a/db/migrate/20210526135911_create_ci_minutes_additional_packs.rb b/db/migrate/20210526135911_create_ci_minutes_additional_packs.rb
new file mode 100644
index 00000000000..3464268a77f
--- /dev/null
+++ b/db/migrate/20210526135911_create_ci_minutes_additional_packs.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CreateCiMinutesAdditionalPacks < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ def up
+ create_table_with_constraints :ci_minutes_additional_packs, if_not_exists: true do |t|
+ t.timestamps_with_timezone
+
+ t.references :namespace, index: false, null: false, foreign_key: { on_delete: :cascade }
+ t.date :expires_at, null: true
+ t.integer :number_of_minutes, null: false
+ t.text :purchase_xid, null: true
+ t.text_limit :purchase_xid, 32
+
+ t.index [:namespace_id, :purchase_xid], name: 'index_ci_minutes_additional_packs_on_namespace_id_purchase_xid'
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :ci_minutes_additional_packs
+ end
+ end
+end
diff --git a/db/schema_migrations/20210526135911 b/db/schema_migrations/20210526135911
new file mode 100644
index 00000000000..be8d46e3cb0
--- /dev/null
+++ b/db/schema_migrations/20210526135911
@@ -0,0 +1 @@
+9f3edf905be3bd3c7fe0149c9b97c68783590b808a96ad08873d983e3d901419 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fd94bcbbdff..9887ac3cdf5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10796,6 +10796,26 @@ CREATE SEQUENCE ci_job_variables_id_seq
ALTER SEQUENCE ci_job_variables_id_seq OWNED BY ci_job_variables.id;
+CREATE TABLE ci_minutes_additional_packs (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ namespace_id bigint NOT NULL,
+ expires_at date,
+ number_of_minutes integer NOT NULL,
+ purchase_xid text,
+ CONSTRAINT check_d7ef254af0 CHECK ((char_length(purchase_xid) <= 32))
+);
+
+CREATE SEQUENCE ci_minutes_additional_packs_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE ci_minutes_additional_packs_id_seq OWNED BY ci_minutes_additional_packs.id;
+
CREATE TABLE ci_namespace_monthly_usages (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
@@ -19681,6 +19701,8 @@ ALTER TABLE ONLY ci_job_artifacts ALTER COLUMN id SET DEFAULT nextval('ci_job_ar
ALTER TABLE ONLY ci_job_variables ALTER COLUMN id SET DEFAULT nextval('ci_job_variables_id_seq'::regclass);
+ALTER TABLE ONLY ci_minutes_additional_packs ALTER COLUMN id SET DEFAULT nextval('ci_minutes_additional_packs_id_seq'::regclass);
+
ALTER TABLE ONLY ci_namespace_monthly_usages ALTER COLUMN id SET DEFAULT nextval('ci_namespace_monthly_usages_id_seq'::regclass);
ALTER TABLE ONLY ci_pending_builds ALTER COLUMN id SET DEFAULT nextval('ci_pending_builds_id_seq'::regclass);
@@ -20862,6 +20884,9 @@ ALTER TABLE ONLY ci_job_artifacts
ALTER TABLE ONLY ci_job_variables
ADD CONSTRAINT ci_job_variables_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY ci_minutes_additional_packs
+ ADD CONSTRAINT ci_minutes_additional_packs_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY ci_namespace_monthly_usages
ADD CONSTRAINT ci_namespace_monthly_usages_pkey PRIMARY KEY (id);
@@ -22792,6 +22817,8 @@ CREATE INDEX index_ci_job_variables_on_job_id ON ci_job_variables USING btree (j
CREATE UNIQUE INDEX index_ci_job_variables_on_key_and_job_id ON ci_job_variables USING btree (key, job_id);
+CREATE INDEX index_ci_minutes_additional_packs_on_namespace_id_purchase_xid ON ci_minutes_additional_packs USING btree (namespace_id, purchase_xid);
+
CREATE UNIQUE INDEX index_ci_namespace_monthly_usages_on_namespace_id_and_date ON ci_namespace_monthly_usages USING btree (namespace_id, date);
CREATE UNIQUE INDEX index_ci_pending_builds_on_build_id ON ci_pending_builds USING btree (build_id);
@@ -27462,6 +27489,9 @@ ALTER TABLE ONLY analytics_cycle_analytics_group_stages
ALTER TABLE ONLY bulk_import_export_uploads
ADD CONSTRAINT fk_rails_dfbfb45eca FOREIGN KEY (export_id) REFERENCES bulk_import_exports(id) ON DELETE CASCADE;
+ALTER TABLE ONLY ci_minutes_additional_packs
+ ADD CONSTRAINT fk_rails_e0e0c4e4b1 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY label_priorities
ADD CONSTRAINT fk_rails_e161058b0f FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6d0a47bd03d..04946224c09 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9551,6 +9551,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="groupvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="groupvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="groupvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
+| <a id="groupvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
| <a id="groupvulnerabilityseveritiescountseverity"></a>`severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| <a id="groupvulnerabilityseveritiescountstate"></a>`state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
@@ -9719,6 +9720,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="instancesecuritydashboardvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
+| <a id="instancesecuritydashboardvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountseverity"></a>`severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| <a id="instancesecuritydashboardvulnerabilityseveritiescountstate"></a>`state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
@@ -11955,6 +11957,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="projectvulnerabilityseveritiescountprojectid"></a>`projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| <a id="projectvulnerabilityseveritiescountreporttype"></a>`reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| <a id="projectvulnerabilityseveritiescountscanner"></a>`scanner` | [`[String!]`](#string) | Filter vulnerabilities by scanner. |
+| <a id="projectvulnerabilityseveritiescountscannerid"></a>`scannerId` | [`[VulnerabilitiesScannerID!]`](#vulnerabilitiesscannerid) | Filter vulnerabilities by scanner ID. |
| <a id="projectvulnerabilityseveritiescountseverity"></a>`severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| <a id="projectvulnerabilityseveritiescountstate"></a>`state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 3b951105e91..2d2e3285356 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -313,7 +313,7 @@ When the user is authenticated and `simple` is not set this returns something li
```
NOTE:
-The `tag_list` attribute has been deprecated
+The `tag_list` attribute has been deprecated
and is removed in API v5 in favor of the `topics` attribute.
NOTE:
@@ -977,7 +977,7 @@ GET /projects/:id
```
NOTE:
-The `tag_list` attribute has been deprecated
+The `tag_list` attribute has been deprecated
and is removed in API v5 in favor of the `topics` attribute.
Users of [GitLab Premium or higher](https://about.gitlab.com/pricing/)
@@ -2046,7 +2046,8 @@ POST /projects/:id/restore
## Upload a file
Uploads a file to the specified project to be used in an issue or merge request
-description, or a comment.
+description, or a comment. GitLab versions 14.0 and later
+[enforce](#max-attachment-size-enforcement) this limit.
```plaintext
POST /projects/:id/uploads
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 9c9a82fbeb6..215b8fad92b 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -234,7 +234,6 @@ Component statuses are linked to configuration documentation for each component.
| [GitLab Exporter](#gitlab-exporter) | Generates a variety of GitLab metrics | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⚙ | ❌ | ❌ | ✅ | ❌ | ⚙ | EE Only |
| [GitLab Kubernetes Agent](#gitlab-kubernetes-agent) | Integrate Kubernetes clusters in a cloud-native way | ⚙ | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | EE Only |
-| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy Helm, Ingress, Cert-Manager, Prometheus, GitLab Runner, JupyterHub, or Knative to a cluster | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
| [GitLab Pages](#gitlab-pages) | Hosts static websites | ⚙ | ⚙ | ❌ | ❌ | ✅ | ⚙ | ⚙ | CE & EE |
| [GitLab Kubernetes Agent](#gitlab-kubernetes-agent) | Integrate Kubernetes clusters in a cloud-native way | ⚙ | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | EE Only |
| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | ⚙ | ⚙ | ✅ | ⚙ | ✅ | ❌ | ❌ | CE & EE |
@@ -739,28 +738,6 @@ Starting with GitLab 13.0, Puma is the default web server.
- Layer: Core Service (Processor)
- GitLab.com: [Mail configuration](../user/gitlab_com/index.md#mail-configuration)
-#### GitLab Managed Apps
-
-- Configuration:
- - [Omnibus](../user/project/clusters/index.md#installing-applications)
- - [Charts](../user/project/clusters/index.md#installing-applications)
- - [Source](../user/project/clusters/index.md#installing-applications)
- - [GDK](../user/project/clusters/index.md#installing-applications)
-- Layer: Core Service (Processor)
-
-GitLab provides [GitLab Managed Apps](../user/project/clusters/index.md#installing-applications),
-a one-click install for various applications which can be added directly to your configured cluster.
-These applications are needed for Review Apps and deployments when using Auto DevOps.
-You can install them after you create a cluster. This includes:
-
-- [Helm](https://helm.sh/docs/)
-- [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
-- [Cert-Manager](https://cert-manager.io/docs/)
-- [Prometheus](https://prometheus.io/docs/introduction/overview/)
-- [GitLab Runner](https://docs.gitlab.com/runner/)
-- [JupyterHub](https://jupyter.org)
-- [Knative](https://cloud.google.com/knative/)
-
## GitLab by request type
GitLab provides two "interfaces" for end users to access the service:
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 890bd5f2325..7ea8378b6db 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -10,16 +10,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
For working with internationalization (i18n),
[GNU gettext](https://www.gnu.org/software/gettext/) is used given it's the most
-used tool for this task and there are a lot of applications that help us
-work with it.
+used tool for this task and there are many applications that help us work with it.
NOTE:
-All `rake` commands described on this page must be run on a GitLab instance, usually GDK.
+All `rake` commands described on this page must be run on a GitLab instance. This instance is
+usually the GitLab Development Kit (GDK).
-## Setting up GitLab Development Kit (GDK)
+## Setting up the GitLab Development Kit (GDK)
-In order to be able to work on the [GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-foss)
-project you must download and configure it through [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/set-up-gdk.md).
+To work on the [GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-foss)
+project, you must download and configure it through the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/set-up-gdk.md).
After you have the GitLab project ready, you can start working on the translation.
@@ -27,34 +27,33 @@ After you have the GitLab project ready, you can start working on the translatio
The following tools are used:
-1. [`gettext_i18n_rails`](https://github.com/grosser/gettext_i18n_rails): this
- gem allow us to translate content from models, views and controllers. Also
- it gives us access to the following Rake tasks:
- - `rake gettext:find`: Parses almost all the files from the
- Rails application looking for content that has been marked for
- translation. Finally, it updates the PO files with the new content that
- it has found.
- - `rake gettext:pack`: Processes the PO files and generates the
- MO files that are binary and are finally used by the application.
-
-1. [`gettext_i18n_rails_js`](https://github.com/webhippie/gettext_i18n_rails_js):
- this gem is useful to make the translations available in JavaScript. It
- provides the following Rake task:
- - `rake gettext:po_to_json`: Reads the contents from the PO files and
- generates JSON files containing all the available translations.
-
-1. PO editor: there are multiple applications that can help us to work with PO
- files, a good option is [Poedit](https://poedit.net/download) which is
- available for macOS, GNU/Linux and Windows.
+- [`gettext_i18n_rails`](https://github.com/grosser/gettext_i18n_rails):
+ this gem allows us to translate content from models, views, and controllers. It also gives us
+ access to the following Rake tasks:
+
+ - `rake gettext:find`: parses almost all the files from the Rails application looking for content
+ marked for translation. It then updates the PO files with this content.
+ - `rake gettext:pack`: processes the PO files and generates the binary MO files that the
+ application uses.
+
+- [`gettext_i18n_rails_js`](https://github.com/webhippie/gettext_i18n_rails_js):
+ this gem makes the translations available in JavaScript. It provides the following Rake task:
+
+ - `rake gettext:po_to_json`: reads the contents of the PO files and generates JSON files that
+ contain all the available translations.
+
+- PO editor: there are multiple applications that can help us work with PO files. A good option is
+ [Poedit](https://poedit.net/download),
+ which is available for macOS, GNU/Linux, and Windows.
## Preparing a page for translation
-We basically have 4 types of files:
+There are four file types:
-1. Ruby files: basically Models and Controllers.
-1. HAML files: these are the view files.
-1. ERB files: used for email templates.
-1. JavaScript files: we mostly need to work with Vue templates.
+- Ruby files: models and controllers.
+- HAML files: view files.
+- ERB files: used for email templates.
+- JavaScript files: we mostly work with Vue templates.
### Ruby files
@@ -72,7 +71,7 @@ Or:
hello = "Hello world!"
```
-You can easily mark that content for translation with:
+You can mark that content for translation with:
```ruby
def hello
@@ -86,26 +85,21 @@ Or:
hello = _("Hello world!")
```
-Be careful when translating strings at the class or module level since these would only be
-evaluated once at class load time.
-
-For example:
+Be careful when translating strings at the class or module level since these are only evaluated once
+at class load time. For example:
```ruby
validates :group_id, uniqueness: { scope: [:project_id], message: _("already shared with this group") }
```
-This would be translated when the class is loaded and result in the error message
-always being in the default locale.
-
-Active Record's `:message` option accepts a `Proc`, so we can do this instead:
+This is translated when the class loads and results in the error message always being in the default
+locale. Active Record's `:message` option accepts a `Proc`, so do this instead:
```ruby
validates :group_id, uniqueness: { scope: [:project_id], message: -> (object, data) { _("already shared with this group") } }
```
-Messages in the API (`lib/api/` or `app/graphql`) do
-not need to be externalized.
+Messages in the API (`lib/api/` or `app/graphql`) do not need to be externalized.
### HAML files
@@ -145,13 +139,20 @@ import { __ } from '~/locale';
const label = __('Subscribe');
```
-In order to test JavaScript translations you have to change the GitLab
-localization to another language than English and you have to generate JSON files
-using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
+To test JavaScript translations you must:
+
+- Change the GitLab localization to a language other than English.
+- Generate JSON files by using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
### Vue files
-In Vue files we make both the `__()` (double underscore parenthesis) function and the `s__()` (namespaced double underscore parenthesis) function available that you can import from the `~/locale` file. For instance:
+In Vue files, we make the following functions available:
+
+- `__()` (double underscore parenthesis)
+- `s__()` (namespaced double underscore parenthesis)
+
+You can therefore import from the `~/locale` file.
+For example:
```javascript
import { __, s__ } from '~/locale';
@@ -228,24 +229,24 @@ For the static text strings we suggest two patterns for using these translations
</template>
```
-In order to visually test the Vue translations you have to change the GitLab
-localization to another language than English and you have to generate JSON files
-using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
+To visually test the Vue translations:
-### Dynamic translations
+1. Change the GitLab localization to another language than English.
+1. Generate JSON files using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
-Sometimes there are some dynamic translations that can't be found by the
-parser when running `bin/rake gettext:find`. For these scenarios you can
-use the [`N_` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
+### Dynamic translations
-There is also and alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a).
+Sometimes there are dynamic translations that the parser can't find when running
+`bin/rake gettext:find`. For these scenarios you can use the [`N_` method](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#unfound-translations-with-rake-gettextfind).
+There's also an alternative method to [translate messages from validation errors](https://github.com/grosser/gettext_i18n_rails/blob/c09e38d481e0899ca7d3fc01786834fa8e7aab97/Readme.md#option-a).
## Working with special content
### Interpolation
-Placeholders in translated text should match the code style of the respective source file.
-For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to [avoid splitting sentences when adding links](#avoid-splitting-sentences-when-adding-links).
+Placeholders in translated text should match the respective source file's code style. For example
+use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to
+[avoid splitting sentences when adding links](#avoid-splitting-sentences-when-adding-links).
- In Ruby/HAML:
@@ -257,9 +258,9 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
Use the [`GlSprintf`](https://gitlab-org.gitlab.io/gitlab-ui/?path=/docs/utilities-sprintf--sentence-with-link) component if:
- - you need to include child components in the translation string.
- - you need to include HTML in your translation string.
- - you are using `sprintf` and need to pass `false` as the third argument to
+ - You need to include child components in the translation string.
+ - You need to include HTML in your translation string.
+ - You're using `sprintf` and need to pass `false` as the third argument to
prevent it from escaping placeholder values.
For example:
@@ -272,7 +273,7 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
</gl-sprintf>
```
- In other cases it may be simpler to use `sprintf`, perhaps in a computed
+ In other cases, it might be simpler to use `sprintf`, perhaps in a computed
property. For example:
```html
@@ -344,7 +345,8 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
# => When size == 2: 'There are 2 mice.'
```
- Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in
+ some languages.
- In JavaScript:
@@ -363,13 +365,12 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
The `n_` method should only be used to fetch pluralized translations of the same
string, not to control the logic of showing different strings for different
-quantities. Some languages have different quantities of target plural forms -
-Chinese (simplified), for example, has only one target plural form in our
-translation tool. This means the translator would have to choose to translate
-only one of the strings and the translation would not behave as intended in the
-other case.
+quantities. Some languages have different quantities of target plural forms.
+For example, Chinese (simplified) has only one target plural form in our
+translation tool. This means the translator has to choose to translate only one
+of the strings, and the translation doesn't behave as intended in the other case.
-For example, prefer to use:
+For example, use this:
```ruby
if selected_projects.one?
@@ -379,7 +380,7 @@ else
end
```
-rather than:
+Instead of this:
```ruby
# incorrect usage example
@@ -388,21 +389,22 @@ n_("%{project_name}", "%d projects selected", count) % { project_name: 'GitLab'
### Namespaces
-A namespace is a way to group translations that belong together. They provide context to our translators by adding a prefix followed by the bar symbol (`|`). For example:
+A namespace is a way to group translations that belong together. They provide context to our
+translators by adding a prefix followed by the bar symbol (`|`). For example:
```ruby
'Namespace|Translated string'
```
-A namespace provide the following benefits:
+A namespace:
-- It addresses ambiguity in words, for example: `Promotions|Promote` vs `Epic|Promote`
-- It allows translators to focus on translating externalized strings that belong to the same product area rather than arbitrary ones.
-- It gives a linguistic context to help the translator.
+- Addresses ambiguity in words. For example: `Promotions|Promote` vs `Epic|Promote`.
+- Allows translators to focus on translating externalized strings that belong to the same product
+ area, rather than arbitrary ones.
+- Gives a linguistic context to help the translator.
-In some cases, namespaces don't make sense, for example,
-for ubiquitous UI words and phrases such as "Cancel" or phrases like "Save changes" a namespace could
-be counterproductive.
+In some cases, namespaces don't make sense. For example, for ubiquitous UI words and phrases such as
+"Cancel" or phrases like "Save changes," a namespace could be counterproductive.
Namespaces should be PascalCase.
@@ -412,7 +414,7 @@ Namespaces should be PascalCase.
s_('OpenedNDaysAgo|Opened')
```
- In case the translation is not found it returns `Opened`.
+ If the translation isn't found, `Opened` is returned.
- In JavaScript:
@@ -420,18 +422,19 @@ Namespaces should be PascalCase.
s__('OpenedNDaysAgo|Opened')
```
-The namespace should be removed from the translation. See the
-[translation guidelines for more details](translation.md#namespaced-strings).
+The namespace should be removed from the translation. For more details, see the
+[translation guidelines](translation.md#namespaced-strings).
### HTML
-We no longer include HTML directly in the strings that are submitted for translation. This is for a couple of reasons:
+We no longer include HTML directly in the strings that are submitted for translation. This is
+because:
-1. It introduces a chance for the translated string to accidentally include invalid HTML.
-1. It introduces a security risk where translated strings become an attack vector for XSS, as noted by the
+1. The translated string can accidentally include invalid HTML.
+1. Translated strings can become an attack vector for XSS, as noted by the
[Open Web Application Security Project (OWASP)](https://owasp.org/www-community/attacks/xss/).
-To include formatting in the translated string, we can do the following:
+To include formatting in the translated string, you can do the following:
- In Ruby/HAML:
@@ -449,18 +452,18 @@ To include formatting in the translated string, we can do the following:
// => 'Some <strong>bold</strong> text.'
```
-- In Vue
+- In Vue:
See the section on [interpolation](#interpolation).
-When [this translation helper issue](https://gitlab.com/gitlab-org/gitlab/-/issues/217935) is complete, we plan to update the
-process of including formatting in translated strings.
+When [this translation helper issue](https://gitlab.com/gitlab-org/gitlab/-/issues/217935)
+is complete, we plan to update the process of including formatting in translated strings.
#### Including Angle Brackets
-If a string contains angles brackets (`<`/`>`) that are not used for HTML, it is still flagged by the
-`rake gettext:lint` linter.
-To avoid this error, use the applicable HTML entity code (`&lt;` or `&gt;`) instead:
+If a string contains angle brackets (`<`/`>`) that are not used for HTML, the `rake gettext:lint`
+linter still flags it. To avoid this error, use the applicable HTML entity code (`&lt;` or `&gt;`)
+instead:
- In Ruby/HAML:
@@ -493,12 +496,12 @@ To avoid this error, use the applicable HTML entity code (`&lt;` or `&gt;`) inst
### Numbers
-Different locales may use different number formats. To support localization of numbers, we use `formatNumber`,
-which leverages [`toLocaleString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString).
+Different locales may use different number formats. To support localization of numbers, we use
+`formatNumber`, which leverages [`toLocaleString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString).
-`formatNumber` formats numbers as strings using the current user locale by default.
+By default, `formatNumber` formats numbers as strings using the current user locale.
-- In JavaScript
+- In JavaScript:
```javascript
import { formatNumber } from '~/locale';
@@ -509,7 +512,7 @@ const tenThousand = formatNumber(10000); // "10,000" (uses comma as decimal symb
const fiftyPercent = formatNumber(0.5, { style: 'percent' }) // "50%" (other options are passed to toLocaleString)
```
-- In Vue templates
+- In Vue templates:
```html
<script>
@@ -546,27 +549,29 @@ console.log(dateFormat.format(new Date('2063-04-05'))) // April 5, 2063
This makes use of [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat).
-- In Ruby/HAML, we have two ways of adding format to dates and times:
+- In Ruby/HAML, there are two ways of adding format to dates and times:
- 1. **Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
- [dates](https://gitlab.com/gitlab-org/gitlab/-/blob/4ab54c2233e91f60a80e5b6fa2181e6899fdcc3e/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab/-/blob/4ab54c2233e91f60a80e5b6fa2181e6899fdcc3e/config/locales/en.yml#L262).
- If you need to add a new format, because other parts of the code could benefit from it,
- you can add it to [en.yml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/locales/en.yml) file.
- 1. **Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
- defined on [en.yml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/locales/en.yml) matches the date/time
- specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
+ - **Using the `l` helper**: for example, `l(active_session.created_at, format: :short)`. We have
+ some predefined formats for [dates](https://gitlab.com/gitlab-org/gitlab/-/blob/4ab54c2233e91f60a80e5b6fa2181e6899fdcc3e/config/locales/en.yml#L54)
+ and [times](https://gitlab.com/gitlab-org/gitlab/-/blob/4ab54c2233e91f60a80e5b6fa2181e6899fdcc3e/config/locales/en.yml#L262).
+ If you need to add a new format, because other parts of the code could benefit from it, add it
+ to the file [`en.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/locales/en.yml).
+ - **Using `strftime`**: for example, `milestone.start_date.strftime('%b %-d')`. We use `strftime`
+ in case none of the formats defined in [`en.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/locales/en.yml)
+ match the date/time specifications we need, and if there's no need to add it as a new format
+ because it's very particular (for example, it's only used in a single view).
## Best practices
### Minimize translation updates
-Updates can result in the loss of the translations for this string. To minimize risks,
-avoid changes to strings, unless they:
+Updates can result in the loss of the translations for this string. To minimize risks, avoid changes
+to strings unless they:
-- Add value to the user.
+- Add value for the user.
- Include extra context for translators.
-For example, we should avoid changes like this:
+For example, avoid changes like this:
```diff
- _('Number of things: %{count}') % { count: 10 }
@@ -582,9 +587,10 @@ Examples:
- Mappings for a dropdown list
- Error messages
-To store these kinds of data, using a constant seems like the best choice, however this doesn't work for translations.
+To store these kinds of data, using a constant seems like the best choice. However, this doesn't
+work for translations.
-Bad, avoid it:
+For example, avoid this:
```ruby
class MyPresenter
@@ -596,11 +602,13 @@ class MyPresenter
end
```
-The translation method (`_`) is called when the class is loaded for the first time and translates the text to the default locale. Regardless of the user's locale, these values are not translated a second time.
+The translation method (`_`) is called when the class loads for the first time and translates the
+text to the default locale. Regardless of the user's locale, these values are not translated a
+second time.
-Similar thing happens when using class methods with memoization.
+A similar thing happens when using class methods with memoization.
-Bad, avoid it:
+For example, avoid this:
```ruby
class MyModel
@@ -614,7 +622,7 @@ class MyModel
end
```
-This method memorizes the translations using the locale of the user, who first "called" this method.
+This method memoizes the translations using the locale of the user who first called this method.
To avoid these problems, keep the translations dynamic.
@@ -634,10 +642,10 @@ end
### Splitting sentences
-Please never split a sentence as that would assume the sentence grammar and
-structure is the same in all languages.
+Never split a sentence, as it assumes the sentence's grammar and structure is the same in all
+languages.
-For instance, the following:
+For example, this:
```javascript
{{ s__("mrWidget|Set by") }}
@@ -645,7 +653,7 @@ For instance, the following:
{{ s__("mrWidget|to be merged automatically when the pipeline succeeds") }}
```
-should be externalized as follows:
+Should be externalized as follows:
```javascript
{{ sprintf(s__("mrWidget|Set by %{author} to be merged automatically when the pipeline succeeds"), { author: author.name }) }}
@@ -653,7 +661,8 @@ should be externalized as follows:
#### Avoid splitting sentences when adding links
-This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
+This also applies when using links in between translated sentences. Otherwise, these texts are not
+translatable in certain languages.
- In Ruby/HAML, instead of:
@@ -662,7 +671,7 @@ This also applies when using links in between translated sentences, otherwise th
= s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
```
- Set the link starting and ending HTML fragments as variables like so:
+ Set the link starting and ending HTML fragments as variables:
```haml
- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
@@ -687,7 +696,7 @@ This also applies when using links in between translated sentences, otherwise th
</template>
```
- Set the link starting and ending HTML fragments as placeholders like so:
+ Set the link starting and ending HTML fragments as placeholders:
```html
<template>
@@ -714,7 +723,7 @@ This also applies when using links in between translated sentences, otherwise th
}}
```
- Set the link starting and ending HTML fragments as placeholders like so:
+ Set the link starting and ending HTML fragments as placeholders:
```javascript
{{
@@ -725,50 +734,47 @@ This also applies when using links in between translated sentences, otherwise th
}}
```
-The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
+The reasoning behind this is that in some languages words change depending on context. For example,
+in Japanese は is added to the subject of a sentence and を to the object. This is impossible to
+translate correctly if you extract individual words from the sentence.
-When in doubt, try to follow the best practices described in this [Mozilla
-Developer documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_content_best_practices#Splitting).
+When in doubt, try to follow the best practices described in this [Mozilla Developer documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_content_best_practices#Splitting).
## Updating the PO files with the new content
-Now that the new content is marked for translation, we need to update
-`locale/gitlab.pot` files with the following command:
+Now that the new content is marked for translation, run this command to update the
+`locale/gitlab.pot` files:
```shell
bin/rake gettext:regenerate
```
-This command updates `locale/gitlab.pot` file with the newly externalized
-strings and remove any strings that aren't used anymore. You should check this
-file in. Once the changes are on the default branch, they are picked up by
-[CrowdIn](https://translate.gitlab.com) and be presented for
-translation.
+This command updates the `locale/gitlab.pot` file with the newly externalized strings and removes
+any unused strings. Once the changes are on the default branch, [CrowdIn](https://translate.gitlab.com)
+picks them up and presents them for translation.
-We don't need to check in any changes to the `locale/[language]/gitlab.po` files.
-They are updated automatically when [translations from CrowdIn are merged](merging_translations.md).
+You don't need to check in any changes to the `locale/[language]/gitlab.po` files. They are updated
+automatically when [translations from CrowdIn are merged](merging_translations.md).
-If there are merge conflicts in the `gitlab.pot` file, you can delete the file
-and regenerate it using the same command.
+If there are merge conflicts in the `gitlab.pot` file, you can delete the file and regenerate it
+using the same command.
### Validating PO files
-To make sure we keep our translation files up to date, there's a linter that is
-running on CI as part of the `static-analysis` job.
-
-To lint the adjustments in PO files locally you can run `rake gettext:lint`.
+To make sure we keep our translation files up to date, there's a linter that runs on CI as part of
+the `static-analysis` job. To lint the adjustments in PO files locally, you can run
+`rake gettext:lint`.
The linter takes the following into account:
-- Valid PO-file syntax
-- Variable usage
- - Only one unnamed (`%d`) variable, since the order of variables might change
- in different languages
- - All variables used in the message ID are used in the translation
- - There should be no variables used in a translation that aren't in the
- message ID
+- Valid PO-file syntax.
+- Variable usage.
+ - Only one unnamed (`%d`) variable, since the order of variables might change in different
+ languages.
+ - All variables used in the message ID are used in the translation.
+ - There should be no variables used in a translation that aren't in the message ID.
- Errors during translation.
-- Presence of angle brackets (`<` or `>`)
+- Presence of angle brackets (`<` or `>`).
The errors are grouped per file, and per message ID:
@@ -789,9 +795,8 @@ Errors in `locale/zh_TW/gitlab.po`:
Failure translating to zh_TW with []: too few arguments
```
-In this output the `locale/zh_HK/gitlab.po` has syntax errors.
-The `locale/zh_TW/gitlab.po` has variables that are used in the translation that
-aren't in the message with ID `1 pipeline`.
+In this output, `locale/zh_HK/gitlab.po` has syntax errors. The file `locale/zh_TW/gitlab.po` has
+variables in the translation that aren't in the message with ID `1 pipeline`.
## Adding a new language
@@ -803,9 +808,9 @@ NOTE:
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221012) in GitLab 13.3:
Languages with less than 2% of translations are not available in the UI.
-Let's suppose you want to add translations for a new language, let's say French.
+Suppose you want to add translations for a new language, for example, French:
-1. The first step is to register the new language in `lib/gitlab/i18n.rb`:
+1. Register the new language in `lib/gitlab/i18n.rb`:
```ruby
...
@@ -816,38 +821,33 @@ Let's suppose you want to add translations for a new language, let's say French.
...
```
-1. Next, you need to add the language:
+1. Add the language:
```shell
bin/rake gettext:add_language[fr]
```
- If you want to add a new language for a specific region, the command is similar,
- you just need to separate the region with an underscore (`_`). For example:
+ If you want to add a new language for a specific region, the command is similar. You must
+ separate the region with an underscore (`_`), specify the region in capital letters. For example:
```shell
bin/rake gettext:add_language[en_GB]
```
- Please note that you need to specify the region part in capitals.
-
-1. Now that the language is added, a new directory has been created under the
- path: `locale/fr/`. You can now start using your PO editor to edit the PO file
- located in: `locale/fr/gitlab.edit.po`.
+1. Adding the language also creates a new directory at the path `locale/fr/`. You can now start
+ using your PO editor to edit the PO file located at `locale/fr/gitlab.edit.po`.
-1. After you're done updating the translations, you need to process the PO files
- in order to generate the binary MO files and finally update the JSON files
- containing the translations:
+1. After updating the translations, you must process the PO files to generate the binary MO files,
+ and update the JSON files containing the translations:
```shell
bin/rake gettext:compile
```
-1. In order to see the translated content we need to change our preferred language
- which can be found under the user's **Settings** (`/profile`).
+1. To see the translated content, you must change your preferred language. You can find this under
+ the user's **Settings** (`/profile`).
-1. After checking that the changes are ok, you can proceed to commit the new files.
- For example:
+1. After checking that the changes are ok, commit the new files. For example:
```shell
git add locale/fr/ app/assets/javascripts/locale/fr/
diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md
index b0e34052d2a..c22bb6ff020 100644
--- a/doc/development/i18n/index.md
+++ b/doc/development/i18n/index.md
@@ -6,52 +6,49 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Translate GitLab to your language
-The text in the GitLab user interface is in American English by default.
-Each string can be translated to other languages.
-As each string is translated, it is added to the languages translation file,
-and is made available in future releases of GitLab.
+The text in the GitLab user interface is in American English by default. Each string can be
+translated to other languages. As each string is translated, it's added to the languages translation
+file and made available in future GitLab releases.
-Contributions to translations are always needed.
-Many strings are not yet available for translation because they have not been externalized.
-Helping externalize strings benefits all languages.
-Some translations are incomplete or inconsistent.
-Translating strings helps complete and improve each language.
-
-## How to contribute
+Contributions to translations are always needed. Many strings are not yet available for translation
+because they have not been externalized. Helping externalize strings benefits all languages. Some
+translations are incomplete or inconsistent. Translating strings helps complete and improve each
+language.
There are many ways you can contribute in translating GitLab.
-### Externalize strings
+## Externalize strings
-Before a string can be translated, it must be externalized.
-This is the process where English strings in the GitLab source code are wrapped in a function that
-retrieves the translated string for the user's language.
+Before a string can be translated, it must be externalized. This is the process where English
+strings in the GitLab source code are wrapped in a function that retrieves the translated string for
+the user's language.
-As new features are added and existing features are updated, the surrounding strings are being
-externalized, however, there are many parts of GitLab that still need more work to externalize all
+As new features are added and existing features are updated, the surrounding strings are
+externalized. However, there are many parts of GitLab that still need more work to externalize all
strings.
See [Externalization for GitLab](externalization.md).
-### Translate strings
+## Translate strings
-The translation process is managed at <https://translate.gitlab.com>
+The translation process is managed at [https://translate.gitlab.com](https://translate.gitlab.com)
using [CrowdIn](https://crowdin.com/).
-You need to create an account before you can submit translations.
-Once you are signed in, select the language you wish to contribute translations to.
+You must create a CrowdIn account before you can submit translations. Once you are signed in, select
+the language you wish to contribute translations to.
-Voting for translations is also valuable, helping to confirm good and flag inaccurate translations.
+Voting for translations is also valuable, helping to confirm good translations and flag inaccurate
+ones.
See [Translation guidelines](translation.md).
-### Proofreading
+## Proofreading
-Proofreading helps ensure the accuracy and consistency of translations. All
-translations are proofread before being accepted. If a translations requires
-changes, you are notified with a comment explaining why.
+Proofreading helps ensure the accuracy and consistency of translations. All translations are
+proofread before being accepted. If a translation requires changes, a comment explaining why
+notifies you.
-See [Proofreading Translations](proofreader.md) for more information on who's
-able to proofread and instructions on becoming a proofreader yourself.
+See [Proofreading Translations](proofreader.md) for more information on who can proofread and
+instructions on becoming a proofreader yourself.
## Release
diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md
index 3bc5c05c5e9..9e67227ec7f 100644
--- a/doc/development/kubernetes.md
+++ b/doc/development/kubernetes.md
@@ -172,12 +172,3 @@ they are written:
```shell
kubectl logs <pod_name> --follow -n gitlab-managed-apps
```
-
-## GitLab Managed Apps
-
-GitLab provides [GitLab Managed Apps](../user/clusters/applications.md), a one-click
-install for various applications which can be added directly to your configured cluster.
-
-**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For an overview of how to add a new GitLab-managed app, see
-[How to add GitLab-managed-apps to Kubernetes integration](https://youtu.be/mKm-jkranEk).**
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index 31816354040..99097f54360 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -256,7 +256,7 @@ VlhHNXRhVmszTkV0SVEzcEpNMWRyZEVoRU4ydHINCmRIRnFRVTlCVUVVM1pV
SlRORE4xUjFaYVJGb3JlWGM5UFZ4dUlpd2lhWFlpt2lKV00yRnNVbk5RTjJk
Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
max_historical_user_count: 10,
- active_users: 6
+ billable_users_count: 6
}
</code></pre>
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 631180f696c..264673a587e 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -308,6 +308,5 @@ and customized to fit your workflow. Here are some helpful resources for further
1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters)
1. [Incremental rollout to production](customize.md#incremental-rollout-to-production) **(PREMIUM)**
1. [Disable jobs you don't need with CI/CD variables](customize.md#cicd-variables)
-1. [Use a static IP for your cluster](../../user/clusters/applications.md#using-a-static-ip)
1. [Use your own buildpacks to build your application](customize.md#custom-buildpacks)
1. [Prometheus monitoring](../../user/project/integrations/prometheus.md)
diff --git a/doc/topics/autodevops/requirements.md b/doc/topics/autodevops/requirements.md
index adde17d9709..fe378122cb3 100644
--- a/doc/topics/autodevops/requirements.md
+++ b/doc/topics/autodevops/requirements.md
@@ -26,21 +26,20 @@ To make full use of Auto DevOps with Kubernetes, you need:
[new cluster using the GitLab UI](../../user/project/clusters/add_remove_clusters.md#create-new-cluster).
For Kubernetes 1.16+ clusters, you must perform additional configuration for
[Auto Deploy for Kubernetes 1.16+](stages.md#kubernetes-116).
- 1. NGINX Ingress. You can deploy it to your Kubernetes cluster by installing
- the [GitLab-managed app for Ingress](../../user/clusters/applications.md#ingress),
- after configuring the GitLab integration with Kubernetes in the previous step.
-
- Alternatively, you can use the
- [`nginx-ingress`](https://github.com/helm/charts/tree/master/stable/nginx-ingress)
- Helm chart to install Ingress manually.
+ 1. For external HTTP traffic, an Ingress controller is required. For regular
+ deployments, any Ingress controller should work, but as of GitLab 14.0,
+ [canary deployments](../../user/project/canary_deployments.md) require
+ NGINX Ingress. You can deploy the NGINX Ingress controller to your
+ Kubernetes cluster by installing the
+ [`ingress-nginx`](https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx)
+ Helm chart.
NOTE:
- If you use your own Ingress instead of the one provided by GitLab Managed
- Apps, ensure you're running at least version 0.9.0 of NGINX Ingress and
- [enable Prometheus metrics](https://github.com/helm/charts/tree/master/stable/nginx-ingress#prometheus-metrics)
- for the response metrics to appear. You must also
+ For metrics to appear when using the [Prometheus cluster integration](../../user/clusters/integrations.md#prometheus-cluster-integration), you must [enable Prometheus metrics](https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx#prometheus-metrics).
+
+ When deploying [using custom charts](customize.md#custom-helm-chart), you must also
[annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
- the NGINX Ingress deployment to be scraped by Prometheus using
+ the Ingress manifest to be scraped by Prometheus using
`prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
NOTE:
@@ -65,10 +64,6 @@ To make full use of Auto DevOps with Kubernetes, you need:
You can configure Docker-based runners to autoscale as well, using
[Docker Machine](https://docs.gitlab.com/runner/install/autoscaling.html).
- If you've configured the GitLab integration with Kubernetes in the first step, you
- can deploy it to your cluster by installing the
- [GitLab-managed app for GitLab Runner](../../user/clusters/applications.md#gitlab-runner).
-
Runners should be registered as [shared runners](../../ci/runners/README.md#shared-runners)
for the entire GitLab instance, or [specific runners](../../ci/runners/README.md#specific-runners)
that are assigned to specific projects (the default if you've installed the
@@ -78,9 +73,9 @@ To make full use of Auto DevOps with Kubernetes, you need:
To enable Auto Monitoring, you need Prometheus installed either inside or
outside your cluster, and configured to scrape your Kubernetes cluster.
- If you've configured the GitLab integration with Kubernetes, you can deploy it to
- your cluster by installing the
- [GitLab-managed app for Prometheus](../../user/clusters/applications.md#prometheus).
+ If you've configured the GitLab integration with Kubernetes, you can
+ instruct GitLab to query an in-cluster Prometheus by enabling
+ the [Prometheus cluster integration](../../user/clusters/integrations.md#prometheus-cluster-integration).
The [Prometheus service](../../user/project/integrations/prometheus.md)
integration must be enabled for the project, or enabled as a
@@ -92,15 +87,13 @@ To make full use of Auto DevOps with Kubernetes, you need:
- **cert-manager** (optional, for TLS/HTTPS)
- To enable HTTPS endpoints for your application, you must install cert-manager,
+ To enable HTTPS endpoints for your application, you can [install cert-manager](https://cert-manager.io/docs/installation/kubernetes/),
a native Kubernetes certificate management controller that helps with issuing
certificates. Installing cert-manager on your cluster issues a
[Let's Encrypt](https://letsencrypt.org/) certificate and ensures the
- certificates are valid and up-to-date. If you've configured the GitLab integration
- with Kubernetes, you can deploy it to your cluster by installing the
- [GitLab-managed app for cert-manager](../../user/clusters/applications.md#cert-manager).
+ certificates are valid and up-to-date.
-If you don't have Kubernetes or Prometheus installed, then
+If you don't have Kubernetes or Prometheus configured, then
[Auto Review Apps](stages.md#auto-review-apps),
[Auto Deploy](stages.md#auto-deploy), and [Auto Monitoring](stages.md#auto-monitoring)
are skipped.
@@ -128,9 +121,6 @@ When you trigger a pipeline, if you have Auto DevOps enabled and if you have cor
[entered AWS credentials as variables](../../ci/cloud_deployment/index.md#deploy-your-application-to-the-aws-elastic-container-service-ecs),
your application is deployed to AWS ECS.
-[GitLab Managed Apps](../../user/clusters/applications.md) are not available when deploying to AWS ECS.
-You must manually configure your application (such as Ingress or Help) on AWS ECS.
-
If you have both a valid `AUTO_DEVOPS_PLATFORM_TARGET` variable and a Kubernetes cluster tied to your project,
only the deployment to Kubernetes runs.
@@ -166,5 +156,5 @@ same kind of access to external consumers.
The docs linked above explain the issue and present possible solutions, for example:
-- Through [MetalLB](https://github.com/metallb/metallb).
+- Through [MetalLB](https://github.com/metallb/metallb).
- Through [PorterLB](https://github.com/kubesphere/porterlb).
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 3b1282dfb9b..d003af81003 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -687,11 +687,6 @@ The metrics include:
- **Response Metrics:** latency, throughput, error rate
- **System Metrics:** CPU utilization, memory utilization
-GitLab provides some initial alerts for you after you install Prometheus:
-
-- Ingress status code `500` > 0.1%
-- NGINX status code `500` > 0.1%
-
To use Auto Monitoring:
1. [Install and configure the Auto DevOps requirements](requirements.md).
diff --git a/doc/user/clusters/cost_management.md b/doc/user/clusters/cost_management.md
index d1df5642514..b1bd44c91a5 100644
--- a/doc/user/clusters/cost_management.md
+++ b/doc/user/clusters/cost_management.md
@@ -33,9 +33,9 @@ permissions in a project or group.
1. Connect GitLab with Prometheus, depending on your configuration:
- *If Prometheus is already configured,* navigate to **Settings > Integrations > Prometheus**
to provide the API endpoint of your Prometheus server.
- - *For GitLab-managed Prometheus,* navigate to your cluster's **Details** page,
- select the **Applications** tab, and install Prometheus. The integration is
- auto-configured for you.
+ - *To use the Prometheus cluster integration,* navigate to your cluster's **Details** page,
+ select the **Integrations** tab, and follow the instructions to enable the Prometheus
+ cluster integration.
1. Set up the Prometheus integration on the cloned example project.
1. Add the Kubecost `cost-model` to your cluster:
- *For non-managed clusters*, deploy it with GitLab CI/CD.
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 87b259df9d8..4de464822f7 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -17,12 +17,11 @@ your group, enabling you to use the same cluster across multiple projects.
To view your group level Kubernetes clusters, navigate to your project and select
**Kubernetes** from the left-hand menu.
-## Installing applications
+## Cluster management project
-GitLab can install and manage some applications in your group-level
-cluster. For more information on installing, upgrading, uninstalling,
-and troubleshooting applications for your group cluster, see
-[GitLab Managed Apps](../../clusters/applications.md).
+Attach a [cluster management project](../../clusters/management_project.md)
+to your cluster to manage shared resources requiring `cluster-admin` privileges for
+installation, such as an Ingress controller.
## RBAC compatibility
@@ -72,9 +71,6 @@ for deployments with a cluster not managed by GitLab, you must ensure:
(this is [not automatic](https://gitlab.com/gitlab-org/gitlab/-/issues/31519)). Editing
`KUBE_NAMESPACE` directly is discouraged.
-If you [install applications](#installing-applications) on your cluster, GitLab creates
-the resources required to run them, even if you choose to manage your own cluster.
-
### Clearing the cluster cache
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31759) in GitLab 12.6.
@@ -100,7 +96,7 @@ per [multiple Kubernetes clusters](#multiple-kubernetes-clusters) When specifyin
this is automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
the [Auto DevOps](../../../topics/autodevops/index.md) stages.
-The domain should have a wildcard DNS configured to the Ingress IP address.
+The domain should have a wildcard DNS configured to the Ingress IP address. [More details](../../project/clusters/index.md#base-domain).
## Environment scopes **(PREMIUM)**
diff --git a/doc/user/project/canary_deployments.md b/doc/user/project/canary_deployments.md
index f7394093a3a..c67c465b680 100644
--- a/doc/user/project/canary_deployments.md
+++ b/doc/user/project/canary_deployments.md
@@ -91,7 +91,7 @@ Here's an example setup flow from scratch:
1. Prepare an [Auto DevOps-enabled](../../topics/autodevops/index.md) project.
1. Set up a [Kubernetes Cluster](../../user/project/clusters/index.md) in your project.
-1. Install [Ingress](../../user/clusters/applications.md#ingress) as a GitLab Managed App.
+1. Install [NGINX Ingress](https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx) in your cluster.
1. Set up [the base domain](../../user/project/clusters/index.md#base-domain) based on the Ingress
Endpoint assigned above.
1. Check if [`v2.0.0+` of `auto-deploy-image` is used in your Auto DevOps pipelines](../../topics/autodevops/upgrading_auto_deploy_dependencies.md#verify-dependency-versions).
diff --git a/doc/user/project/clusters/add_eks_clusters.md b/doc/user/project/clusters/add_eks_clusters.md
index eb43c85ef8a..79c77a03d5a 100644
--- a/doc/user/project/clusters/add_eks_clusters.md
+++ b/doc/user/project/clusters/add_eks_clusters.md
@@ -184,8 +184,7 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
See the [Managed clusters section](index.md#gitlab-managed-clusters) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
-After about 10 minutes, your cluster is ready to go. You can now proceed
-to install some [pre-defined applications](index.md#installing-applications).
+After about 10 minutes, your cluster is ready to go.
NOTE:
If you have [installed and configured](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html#get-started-kubectl) `kubectl` and you would like to manage your cluster with it, you must add your AWS external ID in the AWS configuration. For more information on how to configure AWS CLI, see [using an IAM role in the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-xaccount).
@@ -290,7 +289,7 @@ you've assigned the role the correct permissions.
### Key Pairs are not loaded
-GitLab loads the key pairs from the **Cluster Region** specified. Ensure that key pair exists in that region.
+GitLab loads the key pairs from the **Cluster Region** specified. Ensure that key pair exists in that region.
#### `ROLLBACK_FAILED` during cluster creation
diff --git a/doc/user/project/clusters/add_gke_clusters.md b/doc/user/project/clusters/add_gke_clusters.md
index 8a41a4d18e5..d9966bf7d38 100644
--- a/doc/user/project/clusters/add_gke_clusters.md
+++ b/doc/user/project/clusters/add_gke_clusters.md
@@ -70,8 +70,7 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
See the [Managed clusters section](index.md#gitlab-managed-clusters) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
-After a couple of minutes, your cluster is ready. You can now proceed
-to install some [pre-defined applications](index.md#installing-applications).
+After a couple of minutes, your cluster is ready.
### Cloud Run for Anthos
@@ -79,8 +78,8 @@ to install some [pre-defined applications](index.md#installing-applications).
You can choose to use Cloud Run for Anthos in place of installing Knative and Istio
separately after the cluster has been created. This means that Cloud Run
-(Knative), Istio, and HTTP Load Balancing are enabled on the cluster at
-create time and cannot be [installed or uninstalled](../../clusters/applications.md) separately.
+(Knative), Istio, and HTTP Load Balancing are enabled on the cluster
+from the start, and cannot be installed or uninstalled.
## Existing GKE cluster
diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md
index ba3bd4d1f63..be0887e6762 100644
--- a/doc/user/project/clusters/add_remove_clusters.md
+++ b/doc/user/project/clusters/add_remove_clusters.md
@@ -61,16 +61,10 @@ When creating a cluster in GitLab, you are asked if you would like to create eit
cluster, which is the GitLab default and recommended option.
- An [Attribute-based access control (ABAC)](https://kubernetes.io/docs/reference/access-authn-authz/abac/) cluster.
-GitLab creates the necessary service accounts and privileges to install and run
-[GitLab managed applications](index.md#installing-applications). When GitLab creates the cluster,
+When GitLab creates the cluster,
a `gitlab` service account with `cluster-admin` privileges is created in the `default` namespace
to manage the newly created cluster.
-The first time you install an application into your cluster, the `tiller` service
-account is created with `cluster-admin` privileges in the
-`gitlab-managed-apps` namespace. This service account is used by Helm to
-install and run [GitLab managed applications](index.md#installing-applications).
-
Helm also creates additional service accounts and other resources for each
installed application. Consult the documentation of the Helm charts for each application
for details.
@@ -141,11 +135,8 @@ If you don't want to use a runner in privileged mode, either:
- Use shared runners on GitLab.com. They don't have this security issue.
- Set up your own runners using the configuration described at
- [shared runners](../../gitlab_com/index.md#shared-runners). This involves:
- 1. Making sure that you don't have it installed via
- [the applications](index.md#installing-applications).
- 1. Installing a runner
- [using `docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
+ [shared runners](../../gitlab_com/index.md#shared-runners) using
+ [`docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
## Create new cluster
@@ -162,20 +153,20 @@ Amazon Elastic Kubernetes Service (EKS) at the project, group, or instance level
- [Amazon EKS](add_eks_clusters.md#new-eks-cluster).
- [Google GKE](add_gke_clusters.md#creating-the-cluster-on-gke).
-After creating a cluster, you can install runners for it as described in
-[GitLab Managed Apps](../../clusters/applications.md).
+After creating a cluster, you can [install runners](https://docs.gitlab.com/runner/install/kubernetes.html),
+add a [cluster management project](../../clusters/management_project.md),
+configure [Auto DevOps](../../../topics/autodevops/index.md),
+or start [deploying right away](index.md#deploying-to-a-kubernetes-cluster).
## Add existing cluster
If you have an existing Kubernetes cluster, you can add it to a project, group,
-or instance.
-
-Kubernetes integration isn't supported for arm64 clusters. See the issue
-[Helm Tiller fails to install on arm64 cluster](https://gitlab.com/gitlab-org/gitlab/-/issues/29838)
-for details.
+or instance, and [install runners](https://docs.gitlab.com/runner/install/kubernetes.html)
+on it (the cluster does not need to be added to GitLab first).
-After adding an existing cluster, you can install runners for it as described in
-[GitLab Managed Apps](../../clusters/applications.md).
+After adding a cluster, you can add a [cluster management project](../../clusters/management_project.md),
+configure [Auto DevOps](../../../topics/autodevops/index.md),
+or start [deploying right away](index.md#deploying-to-a-kubernetes-cluster).
### Existing Kubernetes cluster
@@ -325,8 +316,7 @@ To add a Kubernetes cluster to your project, group, or instance:
1. Finally, click the **Create Kubernetes cluster** button.
-After a couple of minutes, your cluster is ready. You can now proceed
-to install some [pre-defined applications](index.md#installing-applications).
+After a couple of minutes, your cluster is ready.
#### Disable Role-Based Access Control (RBAC) (optional)
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index faa394baacb..50dd5e03388 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -172,14 +172,9 @@ for your deployment jobs to use. Otherwise, a namespace is created for you.
#### Important notes
-Note the following with GitLab and clusters:
-
-- If you [install applications](#installing-applications) on your cluster, GitLab will
- create the resources required to run these even if you have chosen to manage your own
- cluster.
-- Be aware that manually managing resources that have been created by GitLab, like
- namespaces and service accounts, can cause unexpected errors. If this occurs, try
- [clearing the cluster cache](#clearing-the-cluster-cache).
+Be aware that manually managing resources that have been created by GitLab, like
+namespaces and service accounts, can cause unexpected errors. If this occurs, try
+[clearing the cluster cache](#clearing-the-cluster-cache).
#### Clearing the cluster cache
@@ -200,19 +195,15 @@ To clear the cache:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24580) in GitLab 11.8.
-You do not need to specify a base domain on cluster settings when using GitLab Serverless. The domain in that case
-is specified as part of the Knative installation. See [Installing Applications](#installing-applications).
-
Specifying a base domain automatically sets `KUBE_INGRESS_BASE_DOMAIN` as an deployment variable.
If you are using [Auto DevOps](../../../topics/autodevops/index.md), this domain is used for the different
stages. For example, Auto Review Apps and Auto Deploy.
The domain should have a wildcard DNS configured to the Ingress IP address.
-After Ingress has been installed (see [Installing Applications](#installing-applications)),
-you can either:
+You can either:
- Create an `A` record that points to the Ingress IP address with your domain provider.
-- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
+- Enter a wildcard DNS address using a service such as `nip.io` or `xip.io`. For example, `192.168.1.1.xip.io`.
To determine the external Ingress IP address, or external Ingress hostname:
@@ -262,13 +253,11 @@ This list provides a generic solution, and some GitLab-specific approaches:
If you see a trailing `%` on some Kubernetes versions, do not include it.
-## Installing applications
+## Cluster management project
-GitLab can install and manage some applications like Helm, GitLab Runner, Ingress,
-Prometheus, and so on, in your project-level cluster. For more information on
-installing, upgrading, uninstalling, and troubleshooting applications for
-your project cluster, see
-[GitLab Managed Apps](../../clusters/applications.md).
+Attach a [Cluster management project](../../clusters/management_project.md)
+to your cluster to manage shared resources requiring `cluster-admin` privileges for
+installation, such as an Ingress controller.
## Auto DevOps
@@ -457,6 +446,6 @@ Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4701) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/208224) to GitLab Free in 13.2.
-When [Prometheus is deployed](#installing-applications), GitLab monitors the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
+When [the Prometheus cluster integration is enabled](../../clusters/integrations.md#prometheus-cluster-integration), GitLab monitors the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
![Cluster Monitoring](img/k8s_cluster_monitoring.png)
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index bafb7d472c6..0ad32adffb1 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -81,7 +81,7 @@ Support for historical data is coming
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/197879) in GitLab 12.8.
-When you enable [Elastic Stack](../../clusters/applications.md#elastic-stack)
+When you enable [Elastic Stack](../../clusters/integrations.md#elastic-stack-cluster-integration)
on your cluster, you can filter logs displayed in the **Log Explorer** by date.
Click **Show last** in the **Log Explorer** to see the available options.
@@ -90,7 +90,7 @@ Click **Show last** in the **Log Explorer** to see the available options.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21656) in GitLab 12.7.
-When you enable [Elastic Stack](../../clusters/applications.md#elastic-stack) on your cluster,
+When you enable [Elastic Stack](../../clusters/integrations.md#elastic-stack-cluster-integration) on your cluster,
you can search the content of your logs through a search bar. The search is passed
to Elasticsearch using the
[simple_query_string](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html)
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index e0b8c074fcf..b2054c4befb 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -63,18 +63,93 @@ information.
Follow this step-by-step guide to configure an executable runbook in GitLab using
the components outlined above and the pre-loaded demo runbook.
-1. Add a Kubernetes cluster to your project by following the steps outlined in
- [Create new cluster](../add_remove_clusters.md#create-new-cluster).
-
-1. Click the **Install** button next to the **Ingress** application to install Ingress.
-
- ![install ingress](img/ingress-install.png)
-
-1. After Ingress has been installed successfully, click the **Install** button next
- to the **JupyterHub** application. You need the **Jupyter Hostname** provided
- here in the next step.
-
- ![install JupyterHub](img/jupyterhub-install.png)
+1. Create an [OAuth Application for JupyterHub](../../../../integration/oauth_provider.md#gitlab-as-oauth2-authentication-service-provider).
+1. When [installing JupyterHub with Helm](https://zero-to-jupyterhub.readthedocs.io/en/latest/jupyterhub/installation.html), use the following values
+
+ ```yaml
+ #-----------------------------------------------------------------------------
+ # The gitlab and ingress sections must be customized!
+ #-----------------------------------------------------------------------------
+
+ gitlab:
+ clientId: <Your OAuth Application ID>
+ clientSecret: <Your OAuth Application Secret>
+ callbackUrl: http://<Jupyter Hostname>/hub/oauth_callback,
+ # Limit access to members of specific projects or groups:
+ # allowedGitlabGroups: [ "my-group-1", "my-group-2" ]
+ # allowedProjectIds: [ 12345, 6789 ]
+
+ # ingress is required for OAuth to work
+ ingress:
+ enabled: true
+ host: <JupyterHostname>
+ # tls:
+ # - hosts:
+ # - <JupyterHostanme>
+ # secretName: jupyter-cert
+ # annotations:
+ # kubernetes.io/ingress.class: "nginx"
+ # kubernetes.io/tls-acme: "true"
+
+ #-----------------------------------------------------------------------------
+ # NO MODIFICATIONS REQUIRED BEYOND THIS POINT
+ #-----------------------------------------------------------------------------
+
+ hub:
+ extraEnv:
+ JUPYTER_ENABLE_LAB: 1
+ extraConfig: |
+ c.KubeSpawner.cmd = ['jupyter-labhub']
+ c.GitLabOAuthenticator.scope = ['api read_repository write_repository']
+
+ async def add_auth_env(spawner):
+ '''
+ We set user's id, login and access token on single user image to
+ enable repository integration for JupyterHub.
+ See: https://gitlab.com/gitlab-org/gitlab-foss/issues/47138#note_154294790
+ '''
+ auth_state = await spawner.user.get_auth_state()
+
+ if not auth_state:
+ spawner.log.warning("No auth state for %s", spawner.user)
+ return
+
+ spawner.environment['GITLAB_ACCESS_TOKEN'] = auth_state['access_token']
+ spawner.environment['GITLAB_USER_LOGIN'] = auth_state['gitlab_user']['username']
+ spawner.environment['GITLAB_USER_ID'] = str(auth_state['gitlab_user']['id'])
+ spawner.environment['GITLAB_USER_EMAIL'] = auth_state['gitlab_user']['email']
+ spawner.environment['GITLAB_USER_NAME'] = auth_state['gitlab_user']['name']
+
+ c.KubeSpawner.pre_spawn_hook = add_auth_env
+
+ auth:
+ type: gitlab
+ state:
+ enabled: true
+
+ singleuser:
+ defaultUrl: "/lab"
+ image:
+ name: registry.gitlab.com/gitlab-org/jupyterhub-user-image
+ tag: latest
+ lifecycleHooks:
+ postStart:
+ exec:
+ command:
+ - "sh"
+ - "-c"
+ - >
+ git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true;
+ echo "https://oauth2:${GITLAB_ACCESS_TOKEN}@${GITLAB_HOST}" > ~/.git-credentials;
+ git config --global credential.helper store;
+ git config --global user.email "${GITLAB_USER_EMAIL}";
+ git config --global user.name "${GITLAB_USER_NAME}";
+ jupyter serverextension enable --py jupyterlab_git
+
+ proxy:
+ service:
+ type: ClusterIP
+ ```
1. After JupyterHub has been installed successfully, open the **Jupyter Hostname**
in your browser. Click the **Sign in with GitLab** button to log in to
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index a8e320bc635..9a0ca17a9d2 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -53,7 +53,7 @@ To run Knative on GitLab, you need:
The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
1. **GitLab Runner:** A runner is required to run the CI jobs that deploy serverless
applications or functions onto your cluster. You can install GitLab Runner
- onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
+ onto the [existing Kubernetes cluster](https://docs.gitlab.com/runner/install/kubernetes.html).
1. **Domain Name:** Knative provides its own load balancer using Istio, and an
external IP address or hostname for all the applications served by Knative. Enter a
wildcard domain to serve your applications. Configure your DNS server to use the
@@ -68,54 +68,18 @@ To run Knative on GitLab, you need:
`Dockerfile` in order to build your applications. It should be included at the root of your
project's repository and expose port `8080`. `Dockerfile` is not require if you plan to build serverless functions
using our [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes).
-1. **Prometheus** (optional): Installing Prometheus allows you to monitor the scale and traffic of your serverless function/application.
- See [Installing Applications](../index.md#installing-applications) for more information.
+1. **Prometheus** (optional): The [Prometheus cluster integration](../../../clusters/integrations.md#prometheus-cluster-integration)
+ allows you to monitor the scale and traffic of your serverless function/application.
1. **Logging** (optional): Configuring logging allows you to view and search request logs for your serverless function/application.
See [Configuring logging](#configuring-logging) for more information.
-## Installing Knative via the GitLab Kubernetes integration
-
-The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB
-memory. **RBAC must be enabled.**
-
-1. [Add a Kubernetes cluster](../add_remove_clusters.md).
-1. Select the **Applications** tab and scroll down to the Knative app section. Enter the domain to be used with
- your application/functions (e.g. `example.com`) and click **Install**.
-
- ![install-knative](img/install-knative.png)
-
-1. After the Knative installation has finished, you can wait for the IP address or hostname to be displayed in the
- **Knative Endpoint** field or [retrieve the Istio Ingress Endpoint manually](../../../clusters/applications.md#determining-the-external-endpoint-manually).
-
- NOTE:
- Running `kubectl` commands on your cluster requires setting up access to the cluster first.
- For clusters created on GKE, see [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl),
- for other platforms [Install kubectl](https://kubernetes.io/docs/tasks/tools/).
-
-1. The Ingress is now available at this address and routes incoming requests to the proper service based on the DNS
- name in the request. To support this, a wildcard DNS record should be created for the desired domain name. For example,
- if your Knative base domain is `knative.info` then you need to create an A record or CNAME record with domain `*.knative.info`
- pointing the IP address or hostname of the Ingress.
-
- ![DNS entry](img/dns-entry.png)
-
-You can deploy either [functions](#deploying-functions) or [serverless applications](#deploying-serverless-applications)
-on a given project, but not both. The current implementation makes use of a
-`serverless.yml` file to signal a FaaS project.
-
-## Using an existing installation of Knative
+## Configuring Knative
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/58941) in GitLab 12.0.
-The _invocations_ monitoring feature of GitLab serverless is unavailable when
-adding an existing installation of Knative.
-
-It's also possible to use GitLab Serverless with an existing Kubernetes cluster
-which already has Knative installed. You must do the following:
-
1. Follow the steps to
- [add an existing Kubernetes
- cluster](../add_remove_clusters.md#add-existing-cluster).
+ [add a Kubernetes
+ cluster](../add_remove_clusters.md).
1. Ensure GitLab can manage Knative:
- For a non-GitLab managed cluster, ensure that the service account for the token
@@ -164,13 +128,17 @@ which already has Knative installed. You must do the following:
kubectl apply -f knative-serving-only-role.yaml
```
- If you would rather grant permissions on a per service account basis, you can do this
- using a `Role` and `RoleBinding` specific to the service account and namespace.
+ Alternatively, permissions can be granted on a per-service account basis
+ using `Role`s and `RoleBinding`s (see the [Kubernetes RBAC
+ documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
+ for more information).
1. Follow the steps to deploy [functions](#deploying-functions)
or [serverless applications](#deploying-serverless-applications) onto your
cluster.
+1. **Optional:** For invocation metrics to show in GitLab, additional Istio metrics need to be configured in your cluster. For example, with Knative v0.9.0, you can use [this manifest](https://gitlab.com/gitlab-org/charts/knative/-/raw/v0.10.0/vendor/istio-metrics.yml).
+
## Supported runtimes
Serverless functions for GitLab can be run using:
@@ -183,7 +151,7 @@ If a runtime is not available for the required programming language, consider de
### GitLab-managed runtimes
-Currently the following GitLab-managed [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes)
+The following GitLab-managed [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes)
are available:
- `go` (proof of concept)
@@ -499,10 +467,10 @@ rows to bring up the function details page.
The pod count gives you the number of pods running the serverless function instances on a given cluster.
-For the Knative function invocations to appear,
-[Prometheus must be installed](../index.md#installing-applications).
+For the Knative function invocations to appear, the
+[Prometheus cluster integration must be enabled](../../../clusters/integrations.md#prometheus-cluster-integration).
-Once Prometheus is installed, a message may appear indicating that the metrics data _is
+Once Prometheus is enabled, a message may appear indicating that the metrics data _is
loading or is not available at this time._ It appears upon the first access of the
page, but should go away after a few seconds. If the message does not disappear, then it
is possible that GitLab is unable to connect to the Prometheus instance running on the
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 4922c8d9b62..2b4c6c6ca87 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -73,10 +73,6 @@ CPU and Memory consumption is monitored, but requires
to determine the environment. If you are using
[Auto DevOps](../../../topics/autodevops/index.md), this is handled automatically.
-The [NGINX Ingress](../clusters/index.md#installing-applications) that is deployed
-by GitLab to clusters, is automatically annotated for monitoring providing key
-response metrics: latency, throughput, and error rates.
-
##### Example of Kubernetes service annotations and labels
As an example, to activate Prometheus monitoring of a service:
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 8846aadd420..d1fe58390fe 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -27,28 +27,6 @@ NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics
## Configuring NGINX Ingress monitoring
-If you have deployed NGINX Ingress using the GitLab [Kubernetes cluster integration](../../clusters/index.md#installing-applications), Prometheus [automatically monitors it](#about-managed-nginx-ingress-deployments).
-
-For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-
-- NGINX Ingress should be version 0.16.0 or above, with metrics enabled.
-- NGINX Ingress should be annotated for Prometheus monitoring.
-- Prometheus should be configured to monitor annotated pods.
-
-### About managed NGINX Ingress deployments
-
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/helm/charts/tree/master/stable/nginx-ingress). NGINX Ingress is [externally reachable via the Load Balancer's Endpoint](../../../clusters/applications.md#ingress).
-
-NGINX is configured for Prometheus monitoring, by setting:
-
-- `enable-vts-status: "true"`, to export Prometheus metrics.
-- `prometheus.io/scrape: "true"`, to enable automatic discovery.
-- `prometheus.io/port: "10254"`, to specify the metrics port.
-
-When used in conjunction with the GitLab deployed Prometheus service, response metrics are automatically collected.
-
-### Manually setting up NGINX Ingress for Prometheus monitoring
-
Version 0.9.0 and above of [NGINX Ingress](https://github.com/kubernetes/ingress-nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint starts running on port 10254.
Next, the Ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
index 4752fec976c..6bdd2c64dcf 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -27,28 +27,6 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
## Configuring NGINX Ingress monitoring
-If you have deployed NGINX Ingress using the GitLab [Kubernetes cluster integration](../../clusters/index.md#installing-applications), Prometheus [automatically monitors](#about-managed-nginx-ingress-deployments) it.
-
-For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-
-- NGINX Ingress should be version 0.9.0 or above, with metrics enabled.
-- NGINX Ingress should be annotated for Prometheus monitoring.
-- Prometheus should be configured to monitor annotated pods.
-
-### About managed NGINX Ingress deployments
-
-NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/helm/charts/tree/master/stable/nginx-ingress). NGINX Ingress is [externally reachable via the Load Balancer's Endpoint](../../../clusters/applications.md#ingress).
-
-NGINX is configured for Prometheus monitoring, by setting:
-
-- `enable-vts-status: "true"`, to export Prometheus metrics.
-- `prometheus.io/scrape: "true"`, to enable automatic discovery.
-- `prometheus.io/port: "10254"`, to specify the metrics port.
-
-When used in conjunction with the GitLab deployed Prometheus service, response metrics are automatically collected.
-
-### Manually setting up NGINX Ingress for Prometheus monitoring
-
Version 0.9.0 and above of [NGINX Ingress](https://github.com/kubernetes/ingress-nginx) has built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint begins running on port 10254.
Next, the Ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
diff --git a/doc/user/project/merge_requests/approvals/rules.md b/doc/user/project/merge_requests/approvals/rules.md
index 79b48ebb6d5..1e4b0f659ee 100644
--- a/doc/user/project/merge_requests/approvals/rules.md
+++ b/doc/user/project/merge_requests/approvals/rules.md
@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, concepts
---
-# Merge request approval rules
+# Merge request approval rules **(FREE)**
Approval rules define how many [approvals](index.md) a merge request must receive before it can
be merged, and which users should do the approving. You can define approval rules:
diff --git a/doc/user/project/merge_requests/approvals/settings.md b/doc/user/project/merge_requests/approvals/settings.md
index 8769f6a7470..97e4b7da396 100644
--- a/doc/user/project/merge_requests/approvals/settings.md
+++ b/doc/user/project/merge_requests/approvals/settings.md
@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, concepts
---
-# Merge request approval settings
+# Merge request approval settings **(FREE)**
You can configure the settings for [merge request approvals](index.md) to
ensure the approval rules meet your use case. You can also configure
diff --git a/doc/user/project/merge_requests/changes.md b/doc/user/project/merge_requests/changes.md
index 07901392769..5ef4c16ae2a 100644
--- a/doc/user/project/merge_requests/changes.md
+++ b/doc/user/project/merge_requests/changes.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: index, reference
---
-# Changes tab in merge requests
+# Changes tab in merge requests **(FREE)**
The **Changes** tab on a [merge request](index.md), below the main merge request details and next to the discussion tab,
shows the changes to files between branches or commits. This view of changes to a
diff --git a/doc/user/project/repository/jupyter_notebooks/index.md b/doc/user/project/repository/jupyter_notebooks/index.md
index 4b649bab4d9..2ad1504aac3 100644
--- a/doc/user/project/repository/jupyter_notebooks/index.md
+++ b/doc/user/project/repository/jupyter_notebooks/index.md
@@ -20,10 +20,9 @@ rendered to HTML when viewed:
Interactive features, including JavaScript plots, don't work when viewed in
GitLab.
-## Jupyter Hub as a GitLab Managed App
-
-You can deploy [Jupyter Hub as a GitLab managed app](../../../clusters/applications.md#jupyterhub).
-
## Jupyter Git integration
-Find out how to [leverage JupyterLab's Git extension on your Kubernetes cluster](../../../clusters/applications.md#jupyter-git-integration).
+Jupyter can be configured as an OAuth application with repository access, acting
+on behalf of the authenticated user. See the
+[Runbooks documentation](../../../project/clusters/runbooks/index.md) for an
+example configuration.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 5c794445882..b398f97b20c 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -52,8 +52,6 @@ module API
api_endpoint = env['api.endpoint']
feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
- header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
-
Gitlab::ApplicationContext.push(
user: -> { @current_user },
project: -> { @project },
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2361e198603..6ce04be373f 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -487,9 +487,8 @@ module API
def handle_api_exception(exception)
if report_exception?(exception)
define_params_for_grape_middleware
- Gitlab::ApplicationContext.with_context(user: current_user) do
- Gitlab::ErrorTracking.track_exception(exception)
- end
+ Gitlab::ApplicationContext.push(user: current_user)
+ Gitlab::ErrorTracking.track_exception(exception)
end
# This is used with GrapeLogging::Loggers::ExceptionLogger
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index e16149185c9..96d3674077d 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -10,8 +10,6 @@ module API
api_endpoint = env['api.endpoint']
feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s
- header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category
-
Gitlab::ApplicationContext.push(
user: -> { actor&.user },
project: -> { project },
diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb
index 601f2175cfc..760f1352256 100644
--- a/lib/gitlab/application_context.rb
+++ b/lib/gitlab/application_context.rb
@@ -44,6 +44,10 @@ module Gitlab
current.include?(Labkit::Context.log_key(attribute_name))
end
+ def self.current_context_attribute(attribute_name)
+ Labkit::Context.current&.get_attribute(attribute_name)
+ end
+
def initialize(**args)
unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name)
raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any?
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 8c916375a98..d5bf0cffb1e 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -67,10 +67,11 @@ module Gitlab
add_instrument_for_cache_hit(status_code, route, request)
+ Gitlab::ApplicationContext.push(feature_category: route.feature_category)
+
new_headers = {
'ETag' => etag,
- 'X-Gitlab-From-Cache' => 'true',
- ::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER => route.feature_category
+ 'X-Gitlab-From-Cache' => 'true'
}
[status_code, new_headers, []]
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index 19a835b9fc4..bf2af57759a 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -15,7 +15,6 @@ module Gitlab
HEALTH_ENDPOINT = /^\/-\/(liveness|readiness|health|metrics)\/?$/.freeze
- FEATURE_CATEGORY_HEADER = 'X-Gitlab-Feature-Category'
FEATURE_CATEGORY_DEFAULT = 'unknown'
# These were the top 5 categories at a point in time, chosen as a
@@ -70,13 +69,11 @@ module Gitlab
started = Time.now.to_f
health_endpoint = health_endpoint?(env['PATH_INFO'])
status = 'undefined'
- feature_category = nil
begin
status, headers, body = @app.call(env)
elapsed = Time.now.to_f - started
- feature_category = headers&.fetch(FEATURE_CATEGORY_HEADER, nil)
unless health_endpoint
RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method }, elapsed)
@@ -104,6 +101,10 @@ module Gitlab
HEALTH_ENDPOINT.match?(CGI.unescape(path))
end
+
+ def feature_category
+ ::Gitlab::ApplicationContext.current_context_attribute(:feature_category)
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 38e2252754f..6a8d42a5090 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19817,6 +19817,9 @@ msgstr ""
msgid "Link Sentry to GitLab to discover and view the errors your application generates."
msgstr ""
+msgid "Link URL"
+msgstr ""
+
msgid "Link an external wiki from the project's sidebar. %{docs_link}"
msgstr ""
@@ -26396,21 +26399,18 @@ msgstr ""
msgid "PrometheusService|Active"
msgstr ""
-msgid "PrometheusService|Auto configuration"
-msgstr ""
-
msgid "PrometheusService|Auto configuration settings are used unless you override their values here."
msgstr ""
-msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments."
-msgstr ""
-
msgid "PrometheusService|Common metrics"
msgstr ""
msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters."
msgstr ""
+msgid "PrometheusService|Configure GitLab to query a Prometheus installed in one of your clusters."
+msgstr ""
+
msgid "PrometheusService|Custom metrics"
msgstr ""
@@ -26426,18 +26426,9 @@ msgstr ""
msgid "PrometheusService|Finding custom metrics..."
msgstr ""
-msgid "PrometheusService|GitLab is managing Prometheus on your clusters."
-msgstr ""
-
-msgid "PrometheusService|GitLab manages Prometheus on your clusters."
-msgstr ""
-
msgid "PrometheusService|IAP_CLIENT_ID.apps.googleusercontent.com"
msgstr ""
-msgid "PrometheusService|Install Prometheus on clusters"
-msgstr ""
-
msgid "PrometheusService|Manage clusters"
msgstr ""
@@ -26453,9 +26444,6 @@ msgstr ""
msgid "PrometheusService|Monitor application health with Prometheus metrics and dashboards"
msgstr ""
-msgid "PrometheusService|Monitor your project’s environments by deploying and configuring Prometheus on your clusters."
-msgstr ""
-
msgid "PrometheusService|More information"
msgstr ""
@@ -26468,6 +26456,9 @@ msgstr ""
msgid "PrometheusService|No custom metrics have been created. Create one using the button above"
msgstr ""
+msgid "PrometheusService|Prometheus cluster integration"
+msgstr ""
+
msgid "PrometheusService|PrometheusService|The ID of the IAP-secured resource."
msgstr ""
@@ -26483,7 +26474,7 @@ msgstr ""
msgid "PrometheusService|These metrics will only be monitored after your first deployment to an environment"
msgstr ""
-msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration."
+msgid "PrometheusService|To use a Prometheus installed on a cluster, deactivate the manual configuration."
msgstr ""
msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
@@ -26492,6 +26483,9 @@ msgstr ""
msgid "PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page has been deprecated."
msgstr ""
+msgid "PrometheusService|You have a cluster with the Prometheus integration enabled."
+msgstr ""
+
msgid "PrometheusService|https://prometheus.example.com/"
msgstr ""
@@ -27372,6 +27366,9 @@ msgstr ""
msgid "Remove limit"
msgstr ""
+msgid "Remove link"
+msgstr ""
+
msgid "Remove list"
msgstr ""
@@ -29633,10 +29630,10 @@ msgstr ""
msgid "Serverless platform"
msgstr ""
-msgid "ServerlessDetails|Function invocation metrics require Prometheus to be installed first."
+msgid "ServerlessDetails|Configure cluster."
msgstr ""
-msgid "ServerlessDetails|Install Prometheus"
+msgid "ServerlessDetails|Function invocation metrics require the Prometheus cluster integration."
msgstr ""
msgid "ServerlessDetails|Invocation metrics loading or not available at this time."
@@ -29678,9 +29675,6 @@ msgstr ""
msgid "Serverless|In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. %{linkStart}More information%{linkEnd}"
msgstr ""
-msgid "Serverless|Install Knative"
-msgstr ""
-
msgid "Serverless|Learn more about Serverless"
msgstr ""
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
index fefe9535610..3a9d316c321 100644
--- a/qa/qa/page/component/issuable/sidebar.rb
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -65,13 +65,13 @@ module QA
def has_assignee?(username)
within_element(:assignee_block) do
- has_text?(username, wait: 120)
+ has_text?(username, wait: 1)
end
end
def has_no_assignee?(username)
within_element(:assignee_block) do
- has_no_text?(username, wait: 120)
+ has_no_text?(username, wait: 1)
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
index 2654531bc2c..44a361df34d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
@@ -25,6 +25,7 @@ module QA
after do
Runtime::Feature.disable('real_time_issue_sidebar', project: project)
Runtime::Feature.disable('broadcast_issue_updates', project: project)
+ Runtime::Feature.disable(:invite_members_group_modal, project: project)
end
it 'update without refresh', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1048' do
@@ -37,7 +38,9 @@ module QA
Page::Project::Issue::Show.perform do |show|
expect(show).to have_assignee(user1.name)
-
+ # We need to wait 1 second for the page to connect to the websocket to subscribe to updates
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/293699#note_583959786
+ sleep 1
issue.set_issue_assignees(assignee_ids: [user2.id])
expect(show).to have_assignee(user2.name)
diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb
index 40cd54c1e33..8aaf07279e0 100644
--- a/spec/features/groups/members/manage_groups_spec.rb
+++ b/spec/features/groups/members/manage_groups_spec.rb
@@ -143,6 +143,58 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
end
end
+ describe 'group search results' do
+ let_it_be(:group, refind: true) { create(:group) }
+ let_it_be(:group_within_hierarchy) { create(:group, parent: group) }
+ let_it_be(:group_outside_hierarchy) { create(:group) }
+
+ before_all do
+ group.add_owner(user)
+ group_within_hierarchy.add_owner(user)
+ group_outside_hierarchy.add_owner(user)
+ end
+
+ context 'when sharing with groups outside the hierarchy is enabled' do
+ context 'when the invite members group modal is disabled' do
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
+ it 'shows groups within and outside the hierarchy in search results' do
+ visit group_group_members_path(group)
+
+ click_on 'Invite group'
+ click_on 'Search for a group'
+
+ expect(page).to have_text group_within_hierarchy.name
+ expect(page).to have_text group_outside_hierarchy.name
+ end
+ end
+ end
+
+ context 'when sharing with groups outside the hierarchy is disabled' do
+ before do
+ group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
+ end
+
+ context 'when the invite members group modal is disabled' do
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
+ it 'shows only groups within the hierarchy in search results' do
+ visit group_group_members_path(group)
+
+ click_on 'Invite group'
+ click_on 'Search for a group'
+
+ expect(page).to have_text group_within_hierarchy.name
+ expect(page).not_to have_text group_outside_hierarchy.name
+ end
+ end
+ end
+ end
+
def add_group(id, role)
page.click_link 'Invite group'
page.within ".invite-group-form" do
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
index a0b06d7e2a1..db8c2a24f2f 100644
--- a/spec/features/projects/serverless/functions_spec.rb
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -25,7 +25,6 @@ RSpec.describe 'Functions', :js do
end
it 'sees an empty state require Knative installation' do
- expect(page).to have_link('Install Knative')
expect(page).to have_selector('.empty-state')
end
end
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
new file mode 100644
index 00000000000..4fae9ff932c
--- /dev/null
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`content_editor/components/toolbar_link_button renders dropdown component 1`] = `
+"<div class=\\"dropdown b-dropdown gl-new-dropdown btn-group\\">
+ <!----><button aria-haspopup=\\"true\\" aria-expanded=\\"false\\" type=\\"button\\" class=\\"btn dropdown-toggle btn-default btn-sm gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only\\">
+ <!----> <svg data-testid=\\"link-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"dropdown-icon gl-icon s16\\">
+ <use href=\\"#link\\"></use>
+ </svg> <span class=\\"gl-new-dropdown-button-text\\"></span> <svg data-testid=\\"chevron-down-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"gl-button-icon dropdown-chevron gl-icon s16\\">
+ <use href=\\"#chevron-down\\"></use>
+ </svg></button>
+ <ul role=\\"menu\\" tabindex=\\"-1\\" class=\\"dropdown-menu\\">
+ <div class=\\"gl-new-dropdown-inner\\">
+ <!---->
+ <div class=\\"gl-new-dropdown-contents\\">
+ <li role=\\"presentation\\" class=\\"gl-px-3!\\">
+ <form tabindex=\\"-1\\" class=\\"b-dropdown-form gl-p-0\\">
+ <div placeholder=\\"Link URL\\">
+ <div role=\\"group\\" class=\\"input-group\\">
+ <!---->
+ <!----> <input type=\\"text\\" placeholder=\\"Link URL\\" class=\\"gl-form-input form-control\\">
+ <div class=\\"input-group-append\\"><button type=\\"button\\" class=\\"btn btn-confirm btn-md gl-button\\">
+ <!---->
+ <!----> <span class=\\"gl-button-text\\">Apply</span></button></div>
+ <!---->
+ </div>
+ </div>
+ </form>
+ </li>
+ <!---->
+ <!---->
+ </div>
+ <!---->
+ </div>
+ </ul>
+</div>"
+`;
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
new file mode 100644
index 00000000000..812e769c891
--- /dev/null
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -0,0 +1,151 @@
+import { GlDropdown, GlDropdownDivider, GlFormInputGroup, GlButton } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import ToolbarLinkButton from '~/content_editor/components/toolbar_link_button.vue';
+import { tiptapExtension as Link } from '~/content_editor/extensions/link';
+import { hasSelection } from '~/content_editor/services/utils';
+import { createTestEditor, mockChainedCommands } from '../test_utils';
+
+jest.mock('~/content_editor/services/utils');
+
+describe('content_editor/components/toolbar_link_button', () => {
+ let wrapper;
+ let editor;
+
+ const buildWrapper = () => {
+ wrapper = mountExtended(ToolbarLinkButton, {
+ propsData: {
+ tiptapEditor: editor,
+ },
+ stubs: {
+ GlFormInputGroup,
+ },
+ });
+ };
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownDivider = () => wrapper.findComponent(GlDropdownDivider);
+ const findLinkURLInput = () => wrapper.findComponent(GlFormInputGroup).find('input[type="text"]');
+ const findApplyLinkButton = () => wrapper.findComponent(GlButton);
+ const findRemoveLinkButton = () => wrapper.findByText('Remove link');
+
+ beforeEach(() => {
+ editor = createTestEditor({
+ extensions: [Link],
+ });
+ });
+
+ afterEach(() => {
+ editor.destroy();
+ wrapper.destroy();
+ });
+
+ it('renders dropdown component', () => {
+ buildWrapper();
+
+ expect(findDropdown().html()).toMatchSnapshot();
+ });
+
+ describe('when there is an active link', () => {
+ beforeEach(() => {
+ jest.spyOn(editor, 'isActive');
+ editor.isActive.mockReturnValueOnce(true);
+ buildWrapper();
+ });
+
+ it('sets dropdown as active when link extension is active', () => {
+ expect(findDropdown().props('toggleClass')).toEqual({ active: true });
+ });
+
+ it('displays a remove link dropdown option', () => {
+ expect(findDropdownDivider().exists()).toBe(true);
+ expect(wrapper.findByText('Remove link').exists()).toBe(true);
+ });
+
+ it('executes removeLink command when the remove link option is clicked', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'run']);
+
+ await findRemoveLinkButton().trigger('click');
+
+ expect(commands.unsetLink).toHaveBeenCalled();
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.run).toHaveBeenCalled();
+ });
+
+ it('updates the link with a new link when "Apply" button is clicked', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'setLink', 'run']);
+
+ await findLinkURLInput().setValue('https://example');
+ await findApplyLinkButton().trigger('click');
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.unsetLink).toHaveBeenCalled();
+ expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
+ expect(commands.run).toHaveBeenCalled();
+ });
+ });
+
+ describe('when there is not an active link', () => {
+ beforeEach(() => {
+ jest.spyOn(editor, 'isActive');
+ editor.isActive.mockReturnValueOnce(false);
+ buildWrapper();
+ });
+
+ it('does not set dropdown as active', () => {
+ expect(findDropdown().props('toggleClass')).toEqual({ active: false });
+ });
+
+ it('does not display a remove link dropdown option', () => {
+ expect(findDropdownDivider().exists()).toBe(false);
+ expect(wrapper.findByText('Remove link').exists()).toBe(false);
+ });
+
+ it('sets the link to the value in the URL input when "Apply" button is clicked', async () => {
+ const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'setLink', 'run']);
+
+ await findLinkURLInput().setValue('https://example');
+ await findApplyLinkButton().trigger('click');
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
+ expect(commands.run).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the user displays the dropdown', () => {
+ let commands;
+
+ beforeEach(() => {
+ commands = mockChainedCommands(editor, ['focus', 'extendMarkRange', 'run']);
+ });
+
+ describe('given the user has not selected text', () => {
+ beforeEach(() => {
+ hasSelection.mockReturnValueOnce(false);
+ });
+
+ it('the editor selection is extended to the current mark extent', () => {
+ buildWrapper();
+
+ findDropdown().vm.$emit('show');
+ expect(commands.extendMarkRange).toHaveBeenCalledWith(Link.name);
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.run).toHaveBeenCalled();
+ });
+ });
+
+ describe('given the user has selected text', () => {
+ beforeEach(() => {
+ hasSelection.mockReturnValueOnce(true);
+ });
+
+ it('the editor does not modify the current selection', () => {
+ buildWrapper();
+
+ findDropdown().vm.$emit('show');
+ expect(commands.extendMarkRange).not.toHaveBeenCalled();
+ expect(commands.focus).not.toHaveBeenCalled();
+ expect(commands.run).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js
index 3c8474d9f12..8e73aef678b 100644
--- a/spec/frontend/content_editor/test_utils.js
+++ b/spec/frontend/content_editor/test_utils.js
@@ -21,6 +21,24 @@ export const createTestEditor = ({ extensions = [] }) => {
});
};
+export const mockChainedCommands = (editor, commandNames = []) => {
+ const commandMocks = commandNames.reduce(
+ (accum, commandName) => ({
+ ...accum,
+ [commandName]: jest.fn(),
+ }),
+ {},
+ );
+
+ Object.keys(commandMocks).forEach((commandName) => {
+ commandMocks[commandName].mockReturnValue(commandMocks);
+ });
+
+ jest.spyOn(editor, 'chain').mockImplementation(() => commandMocks);
+
+ return commandMocks;
+};
+
/**
* Creates a Content Editor extension for testing
* purposes.
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 0f0f7342ff4..05a771c19f1 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -54,6 +54,7 @@ import {
} from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types';
import * as utils from '~/diffs/store/utils';
+import * as workerUtils from '~/diffs/utils/workers';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
@@ -252,7 +253,10 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs },
{ type: types.SET_DIFF_METADATA, payload: noFilesData },
// Workers are synchronous in Jest environment (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58805)
- { type: types.SET_TREE_DATA, payload: utils.generateTreeList(diffMetadata.diff_files) },
+ {
+ type: types.SET_TREE_DATA,
+ payload: workerUtils.generateTreeList(diffMetadata.diff_files),
+ },
],
[],
() => {
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 6af38590610..45cf3a32565 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -685,141 +685,6 @@ describe('DiffsStoreUtils', () => {
});
});
- describe('generateTreeList', () => {
- let files;
-
- beforeAll(() => {
- files = [
- {
- new_path: 'app/index.js',
- deleted_file: false,
- new_file: false,
- removed_lines: 10,
- added_lines: 0,
- file_hash: 'test',
- },
- {
- new_path: 'app/test/index.js',
- deleted_file: false,
- new_file: true,
- removed_lines: 0,
- added_lines: 0,
- file_hash: 'test',
- },
- {
- new_path: 'app/test/filepathneedstruncating.js',
- deleted_file: false,
- new_file: true,
- removed_lines: 0,
- added_lines: 0,
- file_hash: 'test',
- },
- {
- new_path: 'package.json',
- deleted_file: true,
- new_file: false,
- removed_lines: 0,
- added_lines: 0,
- file_hash: 'test',
- },
- ];
- });
-
- it('creates a tree of files', () => {
- const { tree } = utils.generateTreeList(files);
-
- expect(tree).toEqual([
- {
- key: 'app',
- path: 'app',
- name: 'app',
- type: 'tree',
- tree: [
- {
- addedLines: 0,
- changed: true,
- deleted: false,
- fileHash: 'test',
- key: 'app/index.js',
- name: 'index.js',
- parentPath: 'app/',
- path: 'app/index.js',
- removedLines: 10,
- tempFile: false,
- type: 'blob',
- tree: [],
- },
- {
- key: 'app/test',
- path: 'app/test',
- name: 'test',
- type: 'tree',
- opened: true,
- tree: [
- {
- addedLines: 0,
- changed: true,
- deleted: false,
- fileHash: 'test',
- key: 'app/test/index.js',
- name: 'index.js',
- parentPath: 'app/test/',
- path: 'app/test/index.js',
- removedLines: 0,
- tempFile: true,
- type: 'blob',
- tree: [],
- },
- {
- addedLines: 0,
- changed: true,
- deleted: false,
- fileHash: 'test',
- key: 'app/test/filepathneedstruncating.js',
- name: 'filepathneedstruncating.js',
- parentPath: 'app/test/',
- path: 'app/test/filepathneedstruncating.js',
- removedLines: 0,
- tempFile: true,
- type: 'blob',
- tree: [],
- },
- ],
- },
- ],
- opened: true,
- },
- {
- key: 'package.json',
- parentPath: '/',
- path: 'package.json',
- name: 'package.json',
- type: 'blob',
- changed: true,
- tempFile: false,
- deleted: true,
- fileHash: 'test',
- addedLines: 0,
- removedLines: 0,
- tree: [],
- },
- ]);
- });
-
- it('creates flat list of blobs & folders', () => {
- const { treeEntries } = utils.generateTreeList(files);
-
- expect(Object.keys(treeEntries)).toEqual([
- 'app',
- 'app/index.js',
- 'app/test',
- 'app/test/index.js',
- 'app/test/filepathneedstruncating.js',
- 'package.json',
- ]);
- });
- });
-
describe('getDiffMode', () => {
it('returns mode when matched in file', () => {
expect(
@@ -842,177 +707,6 @@ describe('DiffsStoreUtils', () => {
});
});
- describe('getLowestSingleFolder', () => {
- it('returns path and tree of lowest single folder tree', () => {
- const folder = {
- name: 'app',
- type: 'tree',
- tree: [
- {
- name: 'javascripts',
- type: 'tree',
- tree: [
- {
- type: 'blob',
- name: 'index.js',
- },
- ],
- },
- ],
- };
- const { path, treeAcc } = utils.getLowestSingleFolder(folder);
-
- expect(path).toEqual('app/javascripts');
- expect(treeAcc).toEqual([
- {
- type: 'blob',
- name: 'index.js',
- },
- ]);
- });
-
- it('returns passed in folders path & tree when more than tree exists', () => {
- const folder = {
- name: 'app',
- type: 'tree',
- tree: [
- {
- name: 'spec',
- type: 'blob',
- tree: [],
- },
- ],
- };
- const { path, treeAcc } = utils.getLowestSingleFolder(folder);
-
- expect(path).toEqual('app');
- expect(treeAcc).toBeNull();
- });
- });
-
- describe('flattenTree', () => {
- it('returns flattened directory structure', () => {
- const tree = [
- {
- type: 'tree',
- name: 'app',
- tree: [
- {
- type: 'tree',
- name: 'javascripts',
- tree: [
- {
- type: 'blob',
- name: 'index.js',
- tree: [],
- },
- ],
- },
- ],
- },
- {
- type: 'tree',
- name: 'ee',
- tree: [
- {
- type: 'tree',
- name: 'lib',
- tree: [
- {
- type: 'tree',
- name: 'ee',
- tree: [
- {
- type: 'tree',
- name: 'gitlab',
- tree: [
- {
- type: 'tree',
- name: 'checks',
- tree: [
- {
- type: 'tree',
- name: 'longtreenametomakepath',
- tree: [
- {
- type: 'blob',
- name: 'diff_check.rb',
- tree: [],
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- },
- {
- type: 'tree',
- name: 'spec',
- tree: [
- {
- type: 'tree',
- name: 'javascripts',
- tree: [],
- },
- {
- type: 'blob',
- name: 'index_spec.js',
- tree: [],
- },
- ],
- },
- ];
- const flattened = utils.flattenTree(tree);
-
- expect(flattened).toEqual([
- {
- type: 'tree',
- name: 'app/javascripts',
- tree: [
- {
- type: 'blob',
- name: 'index.js',
- tree: [],
- },
- ],
- },
- {
- type: 'tree',
- name: 'ee/lib/…/…/…/longtreenametomakepath',
- tree: [
- {
- name: 'diff_check.rb',
- tree: [],
- type: 'blob',
- },
- ],
- },
- {
- type: 'tree',
- name: 'spec',
- tree: [
- {
- type: 'tree',
- name: 'javascripts',
- tree: [],
- },
- {
- type: 'blob',
- name: 'index_spec.js',
- tree: [],
- },
- ],
- },
- ]);
- });
- });
-
describe('convertExpandLines', () => {
it('converts expanded lines to normal lines', () => {
const diffLines = [
diff --git a/spec/frontend/diffs/utils/workers_spec.js b/spec/frontend/diffs/utils/workers_spec.js
new file mode 100644
index 00000000000..25d8183b777
--- /dev/null
+++ b/spec/frontend/diffs/utils/workers_spec.js
@@ -0,0 +1,309 @@
+import { generateTreeList, getLowestSingleFolder, flattenTree } from '~/diffs/utils/workers';
+
+describe('~/diffs/utils/workers', () => {
+ describe('generateTreeList', () => {
+ let files;
+
+ beforeAll(() => {
+ files = [
+ {
+ new_path: 'app/index.js',
+ deleted_file: false,
+ new_file: false,
+ removed_lines: 10,
+ added_lines: 0,
+ file_hash: 'test',
+ },
+ {
+ new_path: 'app/test/index.js',
+ deleted_file: false,
+ new_file: true,
+ removed_lines: 0,
+ added_lines: 0,
+ file_hash: 'test',
+ },
+ {
+ new_path: 'app/test/filepathneedstruncating.js',
+ deleted_file: false,
+ new_file: true,
+ removed_lines: 0,
+ added_lines: 0,
+ file_hash: 'test',
+ },
+ {
+ new_path: 'package.json',
+ deleted_file: true,
+ new_file: false,
+ removed_lines: 0,
+ added_lines: 0,
+ file_hash: 'test',
+ },
+ ];
+ });
+
+ it('creates a tree of files', () => {
+ const { tree } = generateTreeList(files);
+
+ expect(tree).toEqual([
+ {
+ key: 'app',
+ path: 'app',
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/index.js',
+ name: 'index.js',
+ parentPath: 'app/',
+ path: 'app/index.js',
+ removedLines: 10,
+ tempFile: false,
+ type: 'blob',
+ tree: [],
+ },
+ {
+ key: 'app/test',
+ path: 'app/test',
+ name: 'test',
+ type: 'tree',
+ opened: true,
+ tree: [
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/test/index.js',
+ name: 'index.js',
+ parentPath: 'app/test/',
+ path: 'app/test/index.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ tree: [],
+ },
+ {
+ addedLines: 0,
+ changed: true,
+ deleted: false,
+ fileHash: 'test',
+ key: 'app/test/filepathneedstruncating.js',
+ name: 'filepathneedstruncating.js',
+ parentPath: 'app/test/',
+ path: 'app/test/filepathneedstruncating.js',
+ removedLines: 0,
+ tempFile: true,
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ opened: true,
+ },
+ {
+ key: 'package.json',
+ parentPath: '/',
+ path: 'package.json',
+ name: 'package.json',
+ type: 'blob',
+ changed: true,
+ tempFile: false,
+ deleted: true,
+ fileHash: 'test',
+ addedLines: 0,
+ removedLines: 0,
+ tree: [],
+ },
+ ]);
+ });
+
+ it('creates flat list of blobs & folders', () => {
+ const { treeEntries } = generateTreeList(files);
+
+ expect(Object.keys(treeEntries)).toEqual([
+ 'app',
+ 'app/index.js',
+ 'app/test',
+ 'app/test/index.js',
+ 'app/test/filepathneedstruncating.js',
+ 'package.json',
+ ]);
+ });
+ });
+
+ describe('getLowestSingleFolder', () => {
+ it('returns path and tree of lowest single folder tree', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'javascripts',
+ type: 'tree',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ],
+ },
+ ],
+ };
+ const { path, treeAcc } = getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app/javascripts');
+ expect(treeAcc).toEqual([
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ]);
+ });
+
+ it('returns passed in folders path & tree when more than tree exists', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'spec',
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ };
+ const { path, treeAcc } = getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app');
+ expect(treeAcc).toBeNull();
+ });
+ });
+
+ describe('flattenTree', () => {
+ it('returns flattened directory structure', () => {
+ const tree = [
+ {
+ type: 'tree',
+ name: 'app',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'ee',
+ tree: [
+ {
+ type: 'tree',
+ name: 'lib',
+ tree: [
+ {
+ type: 'tree',
+ name: 'ee',
+ tree: [
+ {
+ type: 'tree',
+ name: 'gitlab',
+ tree: [
+ {
+ type: 'tree',
+ name: 'checks',
+ tree: [
+ {
+ type: 'tree',
+ name: 'longtreenametomakepath',
+ tree: [
+ {
+ type: 'blob',
+ name: 'diff_check.rb',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ];
+ const flattened = flattenTree(tree);
+
+ expect(flattened).toEqual([
+ {
+ type: 'tree',
+ name: 'app/javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'ee/lib/…/…/…/longtreenametomakepath',
+ tree: [
+ {
+ name: 'diff_check.rb',
+ tree: [],
+ type: 'blob',
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
index 33df3a66fcd..36f6746b754 100644
--- a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
+++ b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
@@ -11,7 +11,7 @@ exports[`EmptyStateComponent should render content 1`] = `
<p>In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub>
</p>
<div class=\\"gl-display-flex gl-flex-wrap gl-justify-content-center\\">
- <gl-button-stub category=\\"primary\\" variant=\\"confirm\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" href=\\"/clusters\\" class=\\"gl-mb-3 gl-mx-2\\">Install Knative</gl-button-stub>
+ <!---->
<!---->
</div>
</div>
diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js
index d5b187452c6..1b93fd784e1 100644
--- a/spec/frontend/serverless/components/missing_prometheus_spec.js
+++ b/spec/frontend/serverless/components/missing_prometheus_spec.js
@@ -21,7 +21,7 @@ describe('missingPrometheusComponent', () => {
const { vm } = wrapper;
expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
- 'Function invocation metrics require Prometheus to be installed first.',
+ 'Function invocation metrics require the Prometheus cluster integration.',
);
expect(wrapper.find(GlButton).attributes('variant')).toBe('success');
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 51e0d0fcd64..96869fcc777 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -192,7 +192,7 @@ RSpec.describe EnvironmentsHelper do
"environment_name": environment.name,
"environments_path": api_v4_projects_environments_path(id: project.id),
"environment_id": environment.id,
- "cluster_applications_documentation_path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack'),
+ "cluster_applications_documentation_path" => help_page_path('user/clusters/integrations.md', anchor: 'elastic-stack-cluster-integration'),
"clusters_path": project_clusters_path(project, format: :json)
}
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index c4fe2ebaba9..ecd68caba79 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -68,6 +68,24 @@ RSpec.describe Gitlab::ApplicationContext do
end
end
+ describe '.current_context_attribute' do
+ it 'returns the raw attribute value' do
+ described_class.with_context(caller_id: "Hello") do
+ expect(described_class.current_context_attribute(:caller_id)).to be('Hello')
+ end
+ end
+
+ it 'returns the attribute value with meta prefix' do
+ described_class.with_context(feature_category: "secure") do
+ expect(described_class.current_context_attribute('meta.feature_category')).to be('secure')
+ end
+ end
+
+ it 'returns nil if the key was not present in the current context' do
+ expect(described_class.current_context_attribute(:caller_id)).to be(nil)
+ end
+ end
+
describe '#to_lazy_hash' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 3122a3b1c07..c4da89e5f5c 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -33,7 +33,6 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
expect(headers['ETag']).to be_nil
expect(headers['X-Gitlab-From-Cache']).to be_nil
- expect(headers[::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER]).to be_nil
end
it 'passes status code from app' do
@@ -41,6 +40,12 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
expect(status).to eq app_status_code
end
+
+ it 'does not set feature category attribute' do
+ expect(Gitlab::ApplicationContext).not_to receive(:push)
+
+ _, _, _ = middleware.call(build_request(path, if_none_match))
+ end
end
context 'when there is no ETag in store for given resource' do
@@ -164,8 +169,15 @@ RSpec.describe Gitlab::EtagCaching::Middleware, :clean_gitlab_redis_shared_state
it 'sets correct headers' do
_, headers, _ = middleware.call(build_request(path, if_none_match))
- expect(headers).to include('X-Gitlab-From-Cache' => 'true',
- ::Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER => 'issue_tracking')
+ expect(headers).to include('X-Gitlab-From-Cache' => 'true')
+ end
+
+ it "pushes route's feature category to the context" do
+ expect(Gitlab::ApplicationContext).to receive(:push).with(
+ feature_category: 'issue_tracking'
+ )
+
+ _, _, _ = middleware.call(build_request(path, if_none_match))
end
it_behaves_like 'sends a process_action.action_controller notification', 304
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 1f7daaa308d..8c64019a0a1 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -7,8 +7,16 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
subject { described_class.new(app) }
+ around do |example|
+ # Simulate application context middleware
+ # In fact, this middleware cleans up the contexts after a request lifecycle
+ ::Gitlab::ApplicationContext.with_context({}) do
+ example.run
+ end
+ end
+
describe '#call' do
- let(:status) { 100 }
+ let(:status) { 200 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] }
@@ -91,9 +99,9 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
context 'feature category header' do
- context 'when a feature category header is present' do
+ context 'when a feature category context is present' do
before do
- allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => 'issue_tracking' }, nil])
+ ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
end
it 'adds the feature category to the labels for http_requests_total' do
@@ -113,11 +121,20 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
end
end
- context 'when the feature category header is an empty string' do
+ context 'when application raises an exception when the feature category context is present' do
before do
- allow(app).to receive(:call).and_return([200, { described_class::FEATURE_CATEGORY_HEADER => '' }, nil])
+ ::Gitlab::ApplicationContext.push(feature_category: 'issue_tracking')
+ allow(app).to receive(:call).and_raise(StandardError)
+ end
+
+ it 'adds the feature category to the labels for http_requests_total' do
+ expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'issue_tracking')
+
+ expect { subject.call(env) }.to raise_error(StandardError)
end
+ end
+ context 'when the feature category context is not available' do
it 'sets the feature category to unknown' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).not_to receive(:http_health_requests_total)
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index c834e183e5e..fd75c8411d5 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -585,7 +585,7 @@ RSpec.describe ProjectPresenter do
"can_push_code" => "true",
"original_branch" => "master",
"path" => "/#{project.full_path}/-/create/master",
- "project_path" => project.path,
+ "project_path" => project.full_path,
"target_branch" => "master"
}
)
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 5cf3e6d849c..43c064e1a2b 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe 'projects/settings/operations/show' do
expect(rendered).to have_content _('Prometheus')
expect(rendered).to have_content _('Link Prometheus monitoring to GitLab.')
- expect(rendered).to have_content _('To enable the installation of Prometheus on your clusters, deactivate the manual configuration.')
+ expect(rendered).to have_content _('To use a Prometheus installed on a cluster, deactivate the manual configuration.')
end
end