summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-02 15:07:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-02 15:07:36 +0000
commite61f798b74e8e18fca7239fd01802182479bfcfc (patch)
tree4a49b062d8df2ffe3e6e8a07d2aadc034acb9092
parentafbfbfc87abfa006f1d369fdf9c740eb1c826808 (diff)
downloadgitlab-ce-e61f798b74e8e18fca7239fd01802182479bfcfc.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js2
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue48
-rw-r--r--app/assets/javascripts/runner/graphql/get_runner.query.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/runner_details.fragment.graphql13
-rw-r--r--app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql12
-rw-r--r--app/assets/javascripts/runner/graphql/runner_update.mutation.graphql2
-rw-r--r--app/assets/javascripts/runner/runner_details/runner_update_form_utils.js38
-rw-r--r--app/assets/javascripts/token_access/components/token_access.vue207
-rw-r--r--app/assets/javascripts/token_access/components/token_projects_table.vue81
-rw-r--r--app/assets/javascripts/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql5
-rw-r--r--app/assets/javascripts/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql5
-rw-r--r--app/assets/javascripts/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql8
-rw-r--r--app/assets/javascripts/token_access/graphql/queries/get_ci_job_token_scope.query.graphql7
-rw-r--r--app/assets/javascripts/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql12
-rw-r--r--app/assets/javascripts/token_access/index.js31
-rw-r--r--app/assets/stylesheets/components/avatar.scss13
-rw-r--r--app/assets/stylesheets/framework/variables.scss12
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss20
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss20
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb1
-rw-r--r--app/views/admin/application_settings/_gitaly.html.haml18
-rw-r--r--app/views/admin/application_settings/_usage.html.haml2
-rw-r--r--app/views/admin/application_settings/preferences.html.haml4
-rw-r--r--app/views/ci/token_access/_index.html.haml1
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml13
-rw-r--r--config/feature_flags/development/api_caching_rate_limit_repository_compare.yml8
-rw-r--r--doc/api/index.md62
-rw-r--r--doc/development/changelog.md1
-rw-r--r--doc/development/documentation/structure.md214
-rw-r--r--doc/development/feature_flags/index.md4
-rw-r--r--doc/development/index.md7
-rw-r--r--doc/development/multi_version_compatibility.md11
-rw-r--r--doc/development/sql.md12
-rw-r--r--doc/user/admin_area/analytics/dev_ops_report.md13
-rw-r--r--doc/user/admin_area/settings/gitaly_timeouts.md19
-rw-r--r--doc/user/group/devops_adoption/index.md11
-rw-r--r--doc/user/markdown.md3
-rw-r--r--lib/api/repositories.rb34
-rw-r--r--locale/gitlab.pot86
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb2
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js12
-rw-r--r--spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js96
-rw-r--r--spec/frontend/token_access/mock_data.js84
-rw-r--r--spec/frontend/token_access/token_access_spec.js213
-rw-r--r--spec/frontend/token_access/token_projects_table_spec.js51
-rw-r--r--spec/requests/api/repositories_spec.rb11
47 files changed, 1253 insertions, 280 deletions
diff --git a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js
index fa781ff7f0e..bab3cce39ac 100644
--- a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js
+++ b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/usage_statistics.js
@@ -5,7 +5,7 @@ export const HELPER_TEXT_USAGE_PING_DISABLED = __(
);
export const HELPER_TEXT_USAGE_PING_ENABLED = __(
- 'You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in future, you will also need to register with GitLab via a new cloud licensing service.',
+ 'You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in the future, you will also need to register with GitLab via a new cloud licensing service.',
);
function setHelperText(usagePingCheckbox) {
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index db7b3bad6ed..e88dbf20e1b 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -8,6 +8,7 @@ import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deploy
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import initSettingsPanels from '~/settings_panels';
+import { initTokenAccess } from '~/token_access';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
@@ -40,4 +41,5 @@ document.addEventListener('DOMContentLoaded', () => {
initSharedRunnersToggle();
initInstallRunner();
initRunnerAwsDeployments();
+ initTokenAccess();
});
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index 284ff57870d..85d14547efd 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -7,36 +7,16 @@ import {
GlFormInputGroup,
GlTooltipDirective,
} from '@gitlab/ui';
+import {
+ modelToUpdateMutationVariables,
+ runnerToModel,
+} from 'ee_else_ce/runner/runner_details/runner_update_form_utils';
import createFlash, { FLASH_TYPES } from '~/flash';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
import runnerUpdateMutation from '../graphql/runner_update.mutation.graphql';
-const runnerToModel = (runner) => {
- const {
- id,
- description,
- maximumTimeout,
- accessLevel,
- active,
- locked,
- runUntagged,
- tagList = [],
- } = runner || {};
-
- return {
- id,
- description,
- maximumTimeout,
- accessLevel,
- active,
- locked,
- runUntagged,
- tagList: tagList.join(', '),
- };
-};
-
export default {
name: 'RunnerUpdateForm',
components: {
@@ -45,6 +25,8 @@ export default {
GlFormCheckbox,
GlFormGroup,
GlFormInputGroup,
+ RunnerUpdateCostFactorFields: () =>
+ import('ee_component/runner/components/runner_update_cost_factor_fields.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -69,18 +51,6 @@ export default {
readonlyIpAddress() {
return this.runner?.ipAddress;
},
- updateMutationInput() {
- const { maximumTimeout, tagList } = this.model;
-
- return {
- ...this.model,
- maximumTimeout: maximumTimeout !== '' ? maximumTimeout : null,
- tagList: tagList
- .split(',')
- .map((tag) => tag.trim())
- .filter((tag) => Boolean(tag)),
- };
- },
},
watch: {
runner(newVal, oldVal) {
@@ -100,9 +70,7 @@ export default {
},
} = await this.$apollo.mutate({
mutation: runnerUpdateMutation,
- variables: {
- input: this.updateMutationInput,
- },
+ variables: modelToUpdateMutationVariables(this.model),
});
if (errors?.length) {
@@ -218,6 +186,8 @@ export default {
<gl-form-input-group v-model="model.tagList" />
</gl-form-group>
+ <runner-update-cost-factor-fields v-model="model" />
+
<div class="form-actions">
<gl-button
type="submit"
diff --git a/app/assets/javascripts/runner/graphql/get_runner.query.graphql b/app/assets/javascripts/runner/graphql/get_runner.query.graphql
index 84e0d6cc95c..c294cb9bf22 100644
--- a/app/assets/javascripts/runner/graphql/get_runner.query.graphql
+++ b/app/assets/javascripts/runner/graphql/get_runner.query.graphql
@@ -1,4 +1,4 @@
-#import "~/runner/graphql/runner_details.fragment.graphql"
+#import "ee_else_ce/runner/graphql/runner_details.fragment.graphql"
query getRunner($id: CiRunnerID!) {
runner(id: $id) {
diff --git a/app/assets/javascripts/runner/graphql/runner_details.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_details.fragment.graphql
index 6d7dc1e2798..2449ee0fc0f 100644
--- a/app/assets/javascripts/runner/graphql/runner_details.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_details.fragment.graphql
@@ -1,12 +1,5 @@
+#import "./runner_details_shared.fragment.graphql"
+
fragment RunnerDetails on CiRunner {
- id
- runnerType
- active
- accessLevel
- runUntagged
- locked
- ipAddress
- description
- maximumTimeout
- tagList
+ ...RunnerDetailsShared
}
diff --git a/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
new file mode 100644
index 00000000000..8c50cba7de3
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
@@ -0,0 +1,12 @@
+fragment RunnerDetailsShared on CiRunner {
+ id
+ runnerType
+ active
+ accessLevel
+ runUntagged
+ locked
+ ipAddress
+ description
+ maximumTimeout
+ tagList
+}
diff --git a/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql b/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
index d50c1880d77..dcc7fdf24f1 100644
--- a/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_update.mutation.graphql
@@ -1,4 +1,4 @@
-#import "~/runner/graphql/runner_details.fragment.graphql"
+#import "ee_else_ce/runner/graphql/runner_details.fragment.graphql"
mutation runnerUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
diff --git a/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js b/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js
new file mode 100644
index 00000000000..3b519fa7d71
--- /dev/null
+++ b/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js
@@ -0,0 +1,38 @@
+export const runnerToModel = (runner) => {
+ const {
+ id,
+ description,
+ maximumTimeout,
+ accessLevel,
+ active,
+ locked,
+ runUntagged,
+ tagList = [],
+ } = runner || {};
+
+ return {
+ id,
+ description,
+ maximumTimeout,
+ accessLevel,
+ active,
+ locked,
+ runUntagged,
+ tagList: tagList.join(', '),
+ };
+};
+
+export const modelToUpdateMutationVariables = (model) => {
+ const { maximumTimeout, tagList } = model;
+
+ return {
+ input: {
+ ...model,
+ maximumTimeout: maximumTimeout !== '' ? maximumTimeout : null,
+ tagList: tagList
+ ?.split(',')
+ .map((tag) => tag.trim())
+ .filter((tag) => Boolean(tag)),
+ },
+ };
+};
diff --git a/app/assets/javascripts/token_access/components/token_access.vue b/app/assets/javascripts/token_access/components/token_access.vue
new file mode 100644
index 00000000000..271c90921f0
--- /dev/null
+++ b/app/assets/javascripts/token_access/components/token_access.vue
@@ -0,0 +1,207 @@
+<script>
+import { GlButton, GlCard, GlFormGroup, GlFormInput, GlLoadingIcon, GlToggle } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { __, s__ } from '~/locale';
+import addProjectCIJobTokenScopeMutation from '../graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
+import removeProjectCIJobTokenScopeMutation from '../graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql';
+import updateCIJobTokenScopeMutation from '../graphql/mutations/update_ci_job_token_scope.mutation.graphql';
+import getCIJobTokenScopeQuery from '../graphql/queries/get_ci_job_token_scope.query.graphql';
+import getProjectsWithCIJobTokenScopeQuery from '../graphql/queries/get_projects_with_ci_job_token_scope.query.graphql';
+import TokenProjectsTable from './token_projects_table.vue';
+
+export default {
+ i18n: {
+ toggleLabelTitle: s__('CICD|Limit CI_JOB_TOKEN access'),
+ toggleHelpText: s__(
+ `CICD|Manage which projects can use this project's CI_JOB_TOKEN CI/CD variable for API access`,
+ ),
+ cardHeaderTitle: s__('CICD|Add an existing project to the scope'),
+ formGroupLabel: __('Search for project'),
+ addProject: __('Add project'),
+ cancel: __('Cancel'),
+ addProjectPlaceholder: __('Paste project path (i.e. gitlab-org/gitlab)'),
+ projectsFetchError: __('There was a problem fetching the projects'),
+ scopeFetchError: __('There was a problem fetching the job token scope value'),
+ },
+ components: {
+ GlButton,
+ GlCard,
+ GlFormGroup,
+ GlFormInput,
+ GlLoadingIcon,
+ GlToggle,
+ TokenProjectsTable,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ jobTokenScopeEnabled: {
+ query: getCIJobTokenScopeQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ };
+ },
+ update(data) {
+ return data.project.ciCdSettings.jobTokenScopeEnabled;
+ },
+ error() {
+ createFlash({ message: this.$options.i18n.scopeFetchError });
+ },
+ },
+ projects: {
+ query: getProjectsWithCIJobTokenScopeQuery,
+ variables() {
+ return {
+ fullPath: this.fullPath,
+ };
+ },
+ update(data) {
+ return data.project?.ciJobTokenScope?.projects?.nodes ?? [];
+ },
+ error() {
+ createFlash({ message: this.$options.i18n.projectsFetchError });
+ },
+ },
+ },
+ data() {
+ return {
+ jobTokenScopeEnabled: null,
+ targetProjectPath: '',
+ projects: [],
+ };
+ },
+ computed: {
+ isProjectPathEmpty() {
+ return this.targetProjectPath === '';
+ },
+ },
+ methods: {
+ async updateCIJobTokenScope() {
+ try {
+ const {
+ data: {
+ ciCdSettingsUpdate: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: updateCIJobTokenScopeMutation,
+ variables: {
+ input: {
+ fullPath: this.fullPath,
+ jobTokenScopeEnabled: this.jobTokenScopeEnabled,
+ },
+ },
+ });
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ createFlash({ message: error });
+ }
+ },
+ async addProject() {
+ try {
+ const {
+ data: {
+ ciJobTokenScopeAddProject: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: addProjectCIJobTokenScopeMutation,
+ variables: {
+ input: {
+ projectPath: this.fullPath,
+ targetProjectPath: this.targetProjectPath,
+ },
+ },
+ });
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ createFlash({ message: error });
+ } finally {
+ this.clearTargetProjectPath();
+ this.getProjects();
+ }
+ },
+ async removeProject(removeTargetPath) {
+ try {
+ const {
+ data: {
+ ciJobTokenScopeRemoveProject: { errors },
+ },
+ } = await this.$apollo.mutate({
+ mutation: removeProjectCIJobTokenScopeMutation,
+ variables: {
+ input: {
+ projectPath: this.fullPath,
+ targetProjectPath: removeTargetPath,
+ },
+ },
+ });
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ createFlash({ message: error });
+ } finally {
+ this.getProjects();
+ }
+ },
+ clearTargetProjectPath() {
+ this.targetProjectPath = '';
+ },
+ getProjects() {
+ this.$apollo.queries.projects.refetch();
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-loading-icon v-if="$apollo.loading" size="md" class="gl-mt-5" />
+ <template v-else>
+ <gl-toggle
+ v-model="jobTokenScopeEnabled"
+ :label="$options.i18n.toggleLabelTitle"
+ :help="$options.i18n.toggleHelpText"
+ @change="updateCIJobTokenScope"
+ />
+ <div v-if="jobTokenScopeEnabled" data-testid="token-section">
+ <gl-card class="gl-mt-5">
+ <template #header>
+ <h5 class="gl-my-0">{{ $options.i18n.cardHeaderTitle }}</h5>
+ </template>
+ <template #default>
+ <gl-form-group :label="$options.i18n.formGroupLabel" label-for="token-project-search">
+ <gl-form-input
+ id="token-project-search"
+ v-model="targetProjectPath"
+ :placeholder="$options.i18n.addProjectPlaceholder"
+ />
+ </gl-form-group>
+ </template>
+ <template #footer>
+ <gl-button
+ variant="confirm"
+ :disabled="isProjectPathEmpty"
+ data-testid="add-project-button"
+ @click="addProject"
+ >
+ {{ $options.i18n.addProject }}
+ </gl-button>
+ <gl-button @click="clearTargetProjectPath">{{ $options.i18n.cancel }}</gl-button>
+ </template>
+ </gl-card>
+
+ <token-projects-table :projects="projects" @removeProject="removeProject" />
+ </div>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/token_access/components/token_projects_table.vue b/app/assets/javascripts/token_access/components/token_projects_table.vue
new file mode 100644
index 00000000000..c89ac79abb9
--- /dev/null
+++ b/app/assets/javascripts/token_access/components/token_projects_table.vue
@@ -0,0 +1,81 @@
+<script>
+import { GlButton, GlTable } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+const defaultTableClasses = {
+ thClass: 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!',
+};
+
+export default {
+ i18n: {
+ emptyText: s__('CI/CD|No projects have been added to the scope'),
+ },
+ fields: [
+ {
+ key: 'project',
+ label: __('Projects with access'),
+ tdClass: 'gl-p-5!',
+ ...defaultTableClasses,
+ columnClass: 'gl-w-85p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'gl-p-5! gl-text-right',
+ ...defaultTableClasses,
+ columnClass: 'gl-w-15p',
+ },
+ ],
+ components: {
+ GlButton,
+ GlTable,
+ },
+ inject: {
+ fullPath: {
+ default: '',
+ },
+ },
+ props: {
+ projects: {
+ type: Array,
+ required: true,
+ },
+ },
+ methods: {
+ removeProject(project) {
+ this.$emit('removeProject', project);
+ },
+ },
+};
+</script>
+<template>
+ <gl-table
+ :items="projects"
+ :fields="$options.fields"
+ :tbody-tr-attr="{ 'data-testid': 'projects-token-table-row' }"
+ :empty-text="$options.i18n.emptyText"
+ show-empty
+ stacked="sm"
+ fixed
+ >
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(project)="{ item }">
+ {{ item.name }}
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <gl-button
+ v-if="item.fullPath !== fullPath"
+ category="primary"
+ variant="danger"
+ icon="remove"
+ :aria-label="__('Remove access')"
+ data-testid="remove-project-button"
+ @click="removeProject(item.fullPath)"
+ />
+ </template>
+ </gl-table>
+</template>
diff --git a/app/assets/javascripts/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql
new file mode 100644
index 00000000000..0a7c76dd580
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql
@@ -0,0 +1,5 @@
+mutation addProjectCIJobTokenScope($input: CiJobTokenScopeAddProjectInput!) {
+ ciJobTokenScopeAddProject(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql
new file mode 100644
index 00000000000..5107ea30cd1
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql
@@ -0,0 +1,5 @@
+mutation removeProjectCIJobTokenScope($input: CiJobTokenScopeRemoveProjectInput!) {
+ ciJobTokenScopeRemoveProject(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql b/app/assets/javascripts/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql
new file mode 100644
index 00000000000..d99f2e3597d
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql
@@ -0,0 +1,8 @@
+mutation updateCIJobTokenScope($input: CiCdSettingsUpdateInput!) {
+ ciCdSettingsUpdate(input: $input) {
+ ciCdSettings {
+ jobTokenScopeEnabled
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/token_access/graphql/queries/get_ci_job_token_scope.query.graphql b/app/assets/javascripts/token_access/graphql/queries/get_ci_job_token_scope.query.graphql
new file mode 100644
index 00000000000..d4f559c3701
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/queries/get_ci_job_token_scope.query.graphql
@@ -0,0 +1,7 @@
+query getCIJobTokenScope($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ ciCdSettings {
+ jobTokenScopeEnabled
+ }
+ }
+}
diff --git a/app/assets/javascripts/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql b/app/assets/javascripts/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql
new file mode 100644
index 00000000000..bec0710a1dd
--- /dev/null
+++ b/app/assets/javascripts/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql
@@ -0,0 +1,12 @@
+query getProjectsWithCIJobTokenScope($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ ciJobTokenScope {
+ projects {
+ nodes {
+ name
+ fullPath
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/token_access/index.js b/app/assets/javascripts/token_access/index.js
new file mode 100644
index 00000000000..6a29883290a
--- /dev/null
+++ b/app/assets/javascripts/token_access/index.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import TokenAccess from './components/token_access.vue';
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+export const initTokenAccess = (containerId = 'js-ci-token-access-app') => {
+ const containerEl = document.getElementById(containerId);
+
+ if (!containerEl) {
+ return false;
+ }
+
+ const { fullPath } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ apolloProvider,
+ provide: {
+ fullPath,
+ },
+ render(createElement) {
+ return createElement(TokenAccess);
+ },
+ });
+};
diff --git a/app/assets/stylesheets/components/avatar.scss b/app/assets/stylesheets/components/avatar.scss
index c8f69bfdbaf..3885134e276 100644
--- a/app/assets/stylesheets/components/avatar.scss
+++ b/app/assets/stylesheets/components/avatar.scss
@@ -67,15 +67,12 @@ $avatar-sizes: (
)
);
-$identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal,
- $identicon-orange, $identicon-gray;
-
.avatar,
.avatar-container {
float: left;
margin-right: $gl-padding;
border-radius: $avatar-radius;
- border: 1px solid $gray-normal;
+ border: 1px solid $t-gray-a-08;
@each $size, $size-config in $avatar-sizes {
&.s#{$size} {
@@ -125,8 +122,8 @@ $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $i
.identicon {
text-align: center;
vertical-align: top;
- color: $identicon-text-color;
- background-color: $identicon-gray;
+ color: $gray-900;
+ background-color: $gray-50;
// Sizes
@each $size, $size-config in $avatar-sizes {
@@ -143,9 +140,9 @@ $identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $i
}
// Background colors
- @for $i from 1 through length($identicon-backgrounds) {
+ @for $i from 1 through length($gl-avatar-identicon-bgs) {
&.bg#{$i} {
- background-color: nth($identicon-backgrounds, $i);
+ background-color: nth($gl-avatar-identicon-bgs, $i);
}
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index d3976cfa8c7..726f8e28efe 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -634,18 +634,6 @@ $note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3;
/*
-* Identicon
-*/
-$identicon-text-color: #525252 !default;
-$identicon-red: #ffebee !default;
-$identicon-purple: #f3e5f5 !default;
-$identicon-indigo: #e8eaf6 !default;
-$identicon-blue: #e3f2fd !default;
-$identicon-teal: #e0f2f1 !default;
-$identicon-orange: #fbe9e7 !default;
-$identicon-gray: #eee !default;
-
-/*
* Calendar
*/
$calendar-hover-bg: #ecf3fe;
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index 766f185ced2..f4a76601913 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -2066,7 +2066,7 @@ svg.s16 {
float: left;
margin-right: 16px;
border-radius: 50%;
- border: 1px solid #333;
+ border: 1px solid rgba(255, 255, 255, 0.08);
}
.avatar.s16,
.avatar-container.s16 {
@@ -2108,8 +2108,8 @@ svg.s16 {
.identicon {
text-align: center;
vertical-align: top;
- color: #525252;
- background-color: #eee;
+ color: #fafafa;
+ background-color: #303030;
}
.identicon.s16 {
font-size: 10px;
@@ -2124,25 +2124,25 @@ svg.s16 {
line-height: 38px;
}
.identicon.bg1 {
- background-color: #ffebee;
+ background-color: #660e00;
}
.identicon.bg2 {
- background-color: #f3e5f5;
+ background-color: #f4f0ff;
}
.identicon.bg3 {
- background-color: #e8eaf6;
+ background-color: #f1f1ff;
}
.identicon.bg4 {
- background-color: #e3f2fd;
+ background-color: #033464;
}
.identicon.bg5 {
- background-color: #e0f2f1;
+ background-color: #0a4020;
}
.identicon.bg6 {
- background-color: #fbe9e7;
+ background-color: #5c2900;
}
.identicon.bg7 {
- background-color: #eee;
+ background-color: #303030;
}
.avatar-container {
overflow: hidden;
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index 47d8b44230e..08175c2e99b 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -1934,7 +1934,7 @@ svg.s16 {
float: left;
margin-right: 16px;
border-radius: 50%;
- border: 1px solid #f5f5f5;
+ border: 1px solid rgba(0, 0, 0, 0.08);
}
.avatar.s16,
.avatar-container.s16 {
@@ -1976,8 +1976,8 @@ svg.s16 {
.identicon {
text-align: center;
vertical-align: top;
- color: #525252;
- background-color: #eee;
+ color: #303030;
+ background-color: #f0f0f0;
}
.identicon.s16 {
font-size: 10px;
@@ -1992,25 +1992,25 @@ svg.s16 {
line-height: 38px;
}
.identicon.bg1 {
- background-color: #ffebee;
+ background-color: #fcf1ef;
}
.identicon.bg2 {
- background-color: #f3e5f5;
+ background-color: #f4f0ff;
}
.identicon.bg3 {
- background-color: #e8eaf6;
+ background-color: #f1f1ff;
}
.identicon.bg4 {
- background-color: #e3f2fd;
+ background-color: #e9f3fc;
}
.identicon.bg5 {
- background-color: #e0f2f1;
+ background-color: #ecf4ee;
}
.identicon.bg6 {
- background-color: #fbe9e7;
+ background-color: #fdf1dd;
}
.identicon.bg7 {
- background-color: #eee;
+ background-color: #f0f0f0;
}
.avatar-container {
overflow: hidden;
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 3254d4129d3..960c0beb244 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -12,6 +12,7 @@ module Projects
before_action :define_variables
before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @project)
+ push_frontend_feature_flag(:ci_scoped_job_token, @project, default_enabled: :yaml)
end
helper_method :highlight_badge
diff --git a/app/views/admin/application_settings/_gitaly.html.haml b/app/views/admin/application_settings/_gitaly.html.haml
index b28a53d8bf6..ade6dac606a 100644
--- a/app/views/admin/application_settings/_gitaly.html.haml
+++ b/app/views/admin/application_settings/_gitaly.html.haml
@@ -3,25 +3,19 @@
%fieldset
.form-group
- = f.label :gitaly_timeout_default, _('Default Timeout Period'), class: 'label-bold'
+ = f.label :gitaly_timeout_default, _('Default timeout'), class: 'label-bold'
= f.number_field :gitaly_timeout_default, class: 'form-control gl-form-input'
.form-text.text-muted
- Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
- for git fetch/push operations or Sidekiq jobs.
- This timeout should be less than the worker timeout. If a Gitaly call timeout would exceed the
- worker timeout, the remaining time from the worker timeout would be used to avoid having to terminate
- the worker.
+ = _('Timeout for most Gitaly operations (in seconds).')
.form-group
- = f.label :gitaly_timeout_fast, _('Fast Timeout Period'), class: 'label-bold'
+ = f.label :gitaly_timeout_fast, _('Fast timeout'), class: 'label-bold'
= f.number_field :gitaly_timeout_fast, class: 'form-control gl-form-input'
.form-text.text-muted
- Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
- If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
- can help maintain the stability of the GitLab instance.
+ = _('Timeout for the fastest Gitaly operations (in seconds).')
.form-group
- = f.label :gitaly_timeout_medium, _('Medium Timeout Period'), class: 'label-bold'
+ = f.label :gitaly_timeout_medium, _('Medium timeout'), class: 'label-bold'
= f.number_field :gitaly_timeout_medium, class: 'form-control gl-form-input'
.form-text.text-muted
- Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
+ = _('Timeout for moderately fast Gitaly operations (in seconds). Provide a value between Default timeout and Fast timeout.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index e307fc3a33a..fee2a98bf44 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -44,7 +44,7 @@
= link_to sprite_icon('question-o'), help_page_path('development/usage_ping/index.md', anchor: 'registration-features-program')
.form-text.text-muted
- if usage_ping_enabled
- %p.gl-mb-3.text-muted{ id: 'usage_ping_features_helper_text' }= _('You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in future, you will also need to register with GitLab via a new cloud licensing service.')
+ %p.gl-mb-3.text-muted{ id: 'usage_ping_features_helper_text' }= _('You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in the future, you will also need to register with GitLab via a new cloud licensing service.')
- else
%p.gl-mb-3.text-muted{ id: 'usage_ping_features_helper_text' }= _('To enable Registration Features, make sure "Enable service ping" is checked.')
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index 17bf9ba84a2..bb584818f25 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -60,11 +60,13 @@
%section.settings.as-gitaly.no-animate#js-gitaly-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
- = _('Gitaly')
+ = _('Gitaly timeouts')
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Configure Gitaly timeouts.')
+ %span
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/gitaly_timeouts.md'), target: '_blank'
.settings-content
= render 'gitaly'
diff --git a/app/views/ci/token_access/_index.html.haml b/app/views/ci/token_access/_index.html.haml
new file mode 100644
index 00000000000..e6f21fc4ea4
--- /dev/null
+++ b/app/views/ci/token_access/_index.html.haml
@@ -0,0 +1 @@
+#js-ci-token-access-app{ data: { full_path: @project.full_path } }
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index b9a8bddda91..15752287fc3 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -95,3 +95,16 @@
.settings-content
= render 'ci/deploy_freeze/index'
+
+- if Feature.enabled?(:ci_scoped_job_token, @project, default_enabled: :yaml)
+ %section.settings.no-animate#js-token-access{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
+ = _("Token Access")
+ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _("Control which projects can use the CI_JOB_TOKEN CI/CD variable for API access to this project. It is a security risk to disable this feature, because unauthorized projects may attempt to retrieve an active token and access the API.")
+ = link_to _('Learn more'), help_page_path('api/index', anchor: 'gitlab-cicd-job-token-scope'), target: '_blank', rel: 'noopener noreferrer'
+ .settings-content
+ = render 'ci/token_access/index'
diff --git a/config/feature_flags/development/api_caching_rate_limit_repository_compare.yml b/config/feature_flags/development/api_caching_rate_limit_repository_compare.yml
new file mode 100644
index 00000000000..66597f95e8c
--- /dev/null
+++ b/config/feature_flags/development/api_caching_rate_limit_repository_compare.yml
@@ -0,0 +1,8 @@
+---
+name: api_caching_rate_limit_repository_compare
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64407
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334264
+milestone: '14.1'
+type: development
+group: group::source code
+default_enabled: false
diff --git a/doc/api/index.md b/doc/api/index.md
index e4b7cc56aab..76d31c35b96 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -245,6 +245,68 @@ your [runners](../ci/runners/README.md) to be secure. Avoid:
If you have an insecure GitLab Runner configuration, you increase the risk that someone
tries to steal tokens from other jobs.
+#### GitLab CI/CD job token scope
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328553) in GitLab 14.1.
+
+- [Deployed behind a feature flag](../user/feature_flags.md), disabled by default.
+- Disabled on GitLab.com.
+- Not recommended for production use.
+- To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-ci-job-token-scope). **(FREE SELF)**
+
+This in-development feature might not be available for your use. There can be
+[risks when enabling features still in development](../user/feature_flags.md#risks-when-enabling-features-still-in-development).
+Refer to this feature's version history for more details.
+
+CI job token can access only projects that are defined in its scope.
+You can configure the scope via project settings.
+
+The CI job token scope consists in a allowlist of projects that are authorized by maintainers to be
+accessible via a CI job token. By default a scope only contains the same project where the token
+comes from. Other projects can be added and removed by maintainers.
+
+You can configure the scope via project settings.
+
+Since GitLab 14.1 this setting is enabled by default for new projects. Existing projects are
+recommended to enable this feature and configure which projects are authorized to be accessed
+by a job token.
+
+The CI job token scope limits the risks that a leaked token is used to access private data that
+the user associated to the job can access to.
+
+When the job token scope feature is enabled in the project settings, only the projects in scope
+will be allowed to be accessed by a job token. If the job token scope feature is disabled, any
+projects can be accessed, as long as the user associated to the job has permissions.
+
+For example. If a project `A` has a running job with a `CI_JOB_TOKEN`, its scope is defined by
+project `A`. If the job wants to use the `CI_JOB_TOKEN` to access data from project `B` or
+trigger some actions in that project, then project `B` must be in the job token scope for `A`.
+
+A job token might give extra permissions that aren't necessary to access specific resources.
+There is [a proposal](https://gitlab.com/groups/gitlab-org/-/epics/3559) to redesign the feature
+for more strategic control of the access permissions.
+
+<!-- Add this at the end of the file -->
+
+#### Enable or disable CI Job Token Scope **(FREE SELF)**
+
+This is under development and not ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
+can enable it.
+
+To enable it:
+
+```ruby
+Feature.enable(:ci_scoped_job_token)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:ci_scoped_job_token)
+```
+
### Impersonation tokens
Impersonation tokens are a type of [personal access token](../user/profile/personal_access_tokens.md).
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index c9d2d820c54..c96fe2c18c1 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -98,6 +98,7 @@ EE: true
database records created during Cycle Analytics model spec."
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
+- [Removing](feature_flags/#changelog) a feature flag, when the new code is retained.
## Writing good changelog entries
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 9449fd52839..4fe3458b909 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -7,37 +7,40 @@ description: What to include in GitLab documentation pages.
# Documentation topic types
-At GitLab, we have not traditionally used topic types. However, we are starting to
-move in this direction, and we now use four topic types:
+At GitLab, we have not traditionally used types for our content. However, we are starting to
+move in this direction, and we now use four primary topic types:
- [Concept](#concept)
- [Task](#task)
- [Reference](#reference)
- [Troubleshooting](#troubleshooting)
-Each page contains multiple topic types. For example,
-a page with the title `Pipelines`, which is generated from a file called `index.md`,
-can include a concept and multiple task and reference topics.
+In general, each page in our docset contains multiple topics. (Each heading indicates a new topic.)
+Each topic on a page should be a specific topic type. For example,
+a page with the title `Pipelines` can include topics that are concepts and tasks.
+
+A page might also contain only one type of information. These pages are generally one of our
+[other content types](#other-types-of-content).
## Concept
-A concept topic introduces a single feature or concept.
+A concept introduces a single feature or concept.
A concept should answer the questions:
- What is this?
- Why would I use it?
-Think of everything someone might want to know if they've never heard of this topic before.
+Think of everything someone might want to know if they've never heard of this concept before.
Don't tell them **how** to do this thing. Tell them **what it is**.
-If you start describing another topic, start a new concept and link to it.
+If you start describing another concept, start a new concept and link to it.
-Also, do not use "Overview" or "Introduction" for the topic title. Instead,
+Also, do not use **Overview** or **Introduction** for the title. Instead,
use a noun or phrase that someone would search for.
-Concept topics should be in this format:
+Concepts should be in this format:
```markdown
# Title (a noun, like "Widgets")
@@ -47,14 +50,14 @@ A paragraph that explains what this thing is.
Another paragraph that explains what this thing is.
Remember, if you start to describe about another concept, stop yourself.
-Each concept topic should be about one concept only.
+Each concept should be about one concept only.
```
## Task
-A task topic gives instructions for how to complete a procedure.
+A task gives instructions for how to complete a procedure.
-Task topics should be in this format:
+Tasks should be in this format:
```markdown
# Title (starts with an active verb, like "Create a widget" or "Delete a widget")
@@ -89,20 +92,21 @@ Prerequisites:
To create an issue:
-1. Go to **Issues > List**.
-1. In the top right, select **New issue**.
-1. Complete the fields. (If you have a reference topic that lists each field, link to it here.)
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Issues > List**.
+1. In the top right corner, select **New issue**.
+1. Complete the fields. (If you have reference content that lists each field, link to it here.)
1. Select **Create issue**.
The issue is created. You can view it by going to **Issues > List**.
```
-If you have several tasks on a page that share prerequisites, you can make a
-reference topic with the title **Prerequisites**, and link to it.
+If you have several tasks on a page that share prerequisites, you can use the title
+**Prerequisites**, and link to it.
## Reference
-A reference topic provides information in an easily-scannable format,
+Reference information should be in an easily-scannable format,
like a table or list. It's similar to a dictionary or encyclopedia entry.
```markdown
@@ -115,18 +119,18 @@ Introductory sentence.
| **Name** | Descriptive sentence about the setting. |
```
-If a feature or concept has its own prerequisites, you can use the reference
-topic type to create a **Prerequisites** header for the information.
+If a feature or concept has its own prerequisites, you can use reference
+content to create a **Prerequisites** header for the information.
## Troubleshooting
-Troubleshooting topics can be one of two categories:
+Troubleshooting can be one of two categories:
-- **Troubleshooting task.** This topic is written the same as a [standard task topic](#task).
+- **Troubleshooting task.** This information is written the same way as a [standard task](#task).
For example, "Run debug tools" or "Verify syntax."
-- **Troubleshooting reference.** This topic has a specific format.
+- **Troubleshooting reference.** This information has a specific format.
-Troubleshooting reference topics should be in this format:
+Troubleshooting reference information should be in this format:
```markdown
# Title (the error message or a description of it)
@@ -138,104 +142,24 @@ This issue occurs when...
The workaround is...
```
-For the topic title:
+For the heading:
-- Consider including at least a partial error message in the title.
+- Consider including at least a partial error message.
- Use fewer than 70 characters.
-Remember to include the complete error message in the topics content if it is
-not complete in the title.
-
-## Other information on a topic
-
-Topics include other information.
-
-For example:
-
-- Each topic must have a [tier badge](styleguide/index.md#product-tier-badges).
-- New topics must have information about the
- [GitLab version where the feature was introduced](styleguide/index.md#where-to-put-version-text).
-
-### Help and feedback section
-
-This section ([introduced](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/319) in GitLab 11.4)
-is displayed at the end of each document and can be omitted by adding a key into
-the front matter:
-
-```yaml
----
-feedback: false
----
-```
-
-The default is to leave it there. If you want to omit it from a document, you
-must check with a technical writer before doing so.
-
-#### Disqus
-
-We also have integrated the docs site with Disqus (introduced by
-[!151](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/151)),
-allowing our users to post comments.
-
-To omit only the comments from the feedback section, use the following key in
-the front matter:
-
-```yaml
----
-comments: false
----
-```
-
-We're hiding comments only in main index pages, such as [the main documentation index](../../index.md),
-since its content is too broad to comment on. Before omitting Disqus, you must
-check with a technical writer.
-
-Note that after adding `feedback: false` to the front matter, it will omit
-Disqus, therefore, don't add both keys to the same document.
-
-The click events in the feedback section are tracked with Google Tag Manager.
-The conversions can be viewed on Google Analytics by navigating to
-**Behavior > Events > Top events > docs**.
-
-### Guidelines for good practices
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36576/) in GitLab 13.2 as GitLab Development documentation.
-
-*Good practice* examples demonstrate encouraged ways of writing code while
-comparing with examples of practices to avoid. These examples are labeled as
-*Bad* or *Good*. In GitLab development guidelines, when presenting the cases,
-it's recommended to follow a *first-bad-then-good* strategy. First demonstrate
-the *Bad* practice (how things *could* be done, which is often still working
-code), and then how things *should* be done better, using a *Good* example. This
-is typically an improved example of the same code.
-
-Consider the following guidelines when offering examples:
-
-- First, offer the *Bad* example, and then the *Good* one.
-- When only one bad case and one good case is given, use the same code block.
-- When more than one bad case or one good case is offered, use separated code
- blocks for each. With many examples being presented, a clear separation helps
- the reader to go directly to the good part. Consider offering an explanation
- (for example, a comment, or a link to a resource) on why something is bad
- practice.
-- Better and best cases can be considered part of the good case(s) code block.
- In the same code block, precede each with comments: `# Better` and `# Best`.
-
-Although the bad-then-good approach is acceptable for the GitLab development
-guidelines, do not use it for user documentation. For user documentation, use
-*Do* and *Don't*. For examples, see the [Pajamas Design System](https://design.gitlab.com/content/punctuation/).
+If you do not put the full error in the title, include it in the body text.
## Other types of content
There are other types of content in the GitLab documentation that don't
-classify as one of the four [topic types](#documentation-topic-types).
+classify as one of the four primary [topic types](#documentation-topic-types).
These include:
- [Tutorials](#tutorials)
- [Get started pages](#get-started)
- [Topics and resources pages](#topics-and-resources-pages)
-In most cases, these content types are on their own standalone page.
+In most cases, these pages are standalone.
### Tutorials
@@ -315,10 +239,78 @@ or you have to create one, use this format:
Brief sentence to describe the feature.
-The following topics and resources can help you understand and work with this feature:
+Refer to these resources for more information about <this feature>:
- Link 1
- Link 2
- Link 3
+```
+
+## Help and feedback section
+
+This section ([introduced](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/319) in GitLab 11.4)
+is displayed at the end of each document and can be omitted by adding a key into
+the front matter:
+
+```yaml
+---
+feedback: false
+---
+```
+
+The default is to leave it there. If you want to omit it from a document, you
+must check with a technical writer before doing so.
+
+### Disqus
+
+We also have integrated the docs site with Disqus (introduced by
+[!151](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/151)),
+allowing our users to post comments.
+
+To omit only the comments from the feedback section, use the following key in
+the front matter:
+```yaml
+---
+comments: false
+---
```
+
+We're hiding comments only in main index pages, such as [the main documentation index](../../index.md),
+since its content is too broad to comment on. Before omitting Disqus, you must
+check with a technical writer.
+
+Note that after adding `feedback: false` to the front matter, it will omit
+Disqus, therefore, don't add both keys to the same document.
+
+The click events in the feedback section are tracked with Google Tag Manager.
+The conversions can be viewed on Google Analytics by navigating to
+**Behavior > Events > Top events > docs**.
+
+## Guidelines for good practices
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36576/) in GitLab 13.2 as GitLab Development documentation.
+
+*Good practice* examples demonstrate encouraged ways of writing code while
+comparing with examples of practices to avoid. These examples are labeled as
+*Bad* or *Good*. In GitLab development guidelines, when presenting the cases,
+it's recommended to follow a *first-bad-then-good* strategy. First demonstrate
+the *Bad* practice (how things *could* be done, which is often still working
+code), and then how things *should* be done better, using a *Good* example. This
+is typically an improved example of the same code.
+
+Consider the following guidelines when offering examples:
+
+- First, offer the *Bad* example, and then the *Good* one.
+- When only one bad case and one good case is given, use the same code block.
+- When more than one bad case or one good case is offered, use separated code
+ blocks for each. With many examples being presented, a clear separation helps
+ the reader to go directly to the good part. Consider offering an explanation
+ (for example, a comment, or a link to a resource) on why something is bad
+ practice.
+- Better and best cases can be considered part of the good case(s) code block.
+ In the same code block, precede each with comments: `# Better` and `# Best`.
+
+Although the bad-then-good approach is acceptable for the GitLab development
+guidelines, do not use it for user documentation. For user documentation, use
+*Do* and *Don't*. For examples, see the [Pajamas Design System](https://design.gitlab.com/content/punctuation/).
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index ac5a7268c54..397153a0abb 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -21,6 +21,10 @@ All newly-introduced feature flags should be [disabled by default](https://about
NOTE:
This document is the subject of continued work as part of an epic to [improve internal usage of Feature Flags](https://gitlab.com/groups/gitlab-org/-/epics/3551). Raise any suggestions as new issues and attach them to the epic.
+## When to use feature flags
+
+Moved to the ["When to use feature flags"](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags) section in the handbook.
+
## Feature flags in GitLab development
The following highlights should be considered when deciding if feature flags
diff --git a/doc/development/index.md b/doc/development/index.md
index bc996fdff21..e26dd6e105c 100644
--- a/doc/development/index.md
+++ b/doc/development/index.md
@@ -143,6 +143,13 @@ In these cases, use the following workflow:
for final content review and merge. The Technical Writer may ask for
additional approvals as previously suggested before merging the MR.
+### Reviewer values
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57293) in GitLab 14.1.
+
+As a reviewer or as a reviewee, make sure to familiarize yourself with
+the [reviewer values](https://about.gitlab.com/handbook/engineering/workflow/reviewer-values/) we strive for at GitLab.
+
## UX and Frontend guides
- [GitLab Design System](https://design.gitlab.com/), for building GitLab with
diff --git a/doc/development/multi_version_compatibility.md b/doc/development/multi_version_compatibility.md
index acdf8194cb1..8e49afda579 100644
--- a/doc/development/multi_version_compatibility.md
+++ b/doc/development/multi_version_compatibility.md
@@ -43,6 +43,10 @@ Is it ok if all GitLab nodes have been updated, but the post-deployment migratio
Is it ok if all nodes have been updated, and then the post-deployment migrations get executed a couple days later, and then the background migrations take a week to finish?
+### When upgrading a dependency like Rails
+
+Is it ok that some nodes have the new Rails version, but some nodes have the old Rails version?
+
## A walkthrough of an update
Backwards compatibility problems during updates are often very subtle. This is why it is worth familiarizing yourself with [update instructions](../update/index.md), [reference architectures](../administration/reference_architectures/index.md), and [GitLab.com's architecture](https://about.gitlab.com/handbook/engineering/infrastructure/production/architecture/). But to illustrate how these problems arise, take a look at this example of a simple update.
@@ -102,6 +106,13 @@ Yes! We have specific instructions for [zero-downtime updates](../update/index.m
## I've identified a potential backwards compatibility problem, what can I do about it?
+### Coordinate
+
+For major or minor version updates of Rails or Puma:
+
+- Engage the Quality team to thoroughly test the MR.
+- Notify the `@gitlab-org/release/managers` on the MR prior to merging.
+
### Feature flags
One way to handle this is to use a feature flag that is disabled by
diff --git a/doc/development/sql.md b/doc/development/sql.md
index a98645cfcae..ddca88cb9bb 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -70,7 +70,7 @@ WHERE title ILIKE '%Draft:%';
Because the value for `ILIKE` starts with a wildcard the database is not able to
use an index as it doesn't know where to start scanning the indexes.
-Luckily, PostgreSQL _does_ provide a solution: trigram GIN indexes. These
+Luckily, PostgreSQL _does_ provide a solution: trigram Generalized Inverted Index (GIN) indexes. These
indexes can be created as follows:
```sql
@@ -261,9 +261,9 @@ from `ActiveRecord::Base`.
## Use UNIONs
-UNIONs aren't very commonly used in most Rails applications but they're very
-powerful and useful. In most applications queries tend to use a lot of JOINs to
-get related data or data based on certain criteria, but JOIN performance can
+`UNION`s aren't very commonly used in most Rails applications but they're very
+powerful and useful. Queries tend to use a lot of `JOIN`s to
+get related data or data based on certain criteria, but `JOIN` performance can
quickly deteriorate as the data involved grows.
For example, if you want to get a list of projects where the name contains a
@@ -279,7 +279,7 @@ OR namespaces.name ILIKE '%gitlab%';
```
Using a large database this query can easily take around 800 milliseconds to
-run. Using a UNION we'd write the following instead:
+run. Using a `UNION` we'd write the following instead:
```sql
SELECT projects.*
@@ -301,7 +301,7 @@ This doesn't mean you should start using UNIONs everywhere, but it's something
to keep in mind when using lots of JOINs in a query and filtering out records
based on the joined data.
-GitLab comes with a `Gitlab::SQL::Union` class that can be used to build a UNION
+GitLab comes with a `Gitlab::SQL::Union` class that can be used to build a `UNION`
of multiple `ActiveRecord::Relation` objects. You can use this class as
follows:
diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md
index cb94bc8d35b..3c5c646f86c 100644
--- a/doc/user/admin_area/analytics/dev_ops_report.md
+++ b/doc/user/admin_area/analytics/dev_ops_report.md
@@ -39,25 +39,28 @@ collected before this feature is available.
## DevOps Adoption **(ULTIMATE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247112) in GitLab 13.7 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
-> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
> - [Deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59267) in GitLab 14.0.
> - Enabled on GitLab.com.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-devops-adoption). **(ULTIMATE SELF)**
+> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
+> - DAST and SAST metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328033) in GitLab 14.1.
DevOps Adoption shows you which groups within your organization are using the most essential features of GitLab:
- Dev
- - Issues
- - Merge Requests
- Approvals
- Code owners
+ - Issues
+ - Merge requests
- Sec
+ - DAST
+ - SAST
- Scans
- Ops
- - Runners
- - Pipelines
- Deployments
+ - Pipelines
+ - Runners
When managing groups in the UI, you can add your groups with the **Add group to table**
button, in the top right hand section the page.
diff --git a/doc/user/admin_area/settings/gitaly_timeouts.md b/doc/user/admin_area/settings/gitaly_timeouts.md
index 6f488efee11..04887906c91 100644
--- a/doc/user/admin_area/settings/gitaly_timeouts.md
+++ b/doc/user/admin_area/settings/gitaly_timeouts.md
@@ -8,23 +8,20 @@ type: reference
# Gitaly timeouts **(FREE SELF)**
[Gitaly](../../../administration/gitaly/index.md) timeouts are configurable. The timeouts can be
-configured to make sure that long running Gitaly calls don't needlessly take up resources.
+configured to make sure that long-running Gitaly calls don't needlessly take up resources.
To access Gitaly timeout settings:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Settings > Preferences**.
-1. Expand the **Gitaly** section.
+1. Expand the **Gitaly timeouts** section.
## Available timeouts
-The following timeouts can be modified:
+The following timeouts are available.
-- **Default Timeout Period**. This timeout is the default for most Gitaly calls. It should be shorter than the
- worker timeout that can be configured for [Puma](https://docs.gitlab.com/omnibus/settings/puma.html#puma-settings).
- Used to make sure that Gitaly calls made within a web request cannot exceed the entire request timeout.
- Defaults to 55 seconds.
-
-- **Fast Timeout Period**. This is the timeout for very short Gitaly calls. Defaults to 10 seconds.
-- **Medium Timeout Period**. This timeout should be between the default and the fast timeout.
- Defaults to 30 seconds.
+| Timeout | Default | Description |
+|:--------|:-----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Default | 55 seconds | Timeout for most Gitaly calls (not enforced for `git` `fetch` and `push` operations, or Sidekiq jobs). For example, checking if a repository exists on disk. Makes sure that Gitaly calls made within a web request cannot exceed the entire request timeout. It should be shorter than the worker timeout that can be configured for [Puma](https://docs.gitlab.com/omnibus/settings/puma.html#puma-settings). If a Gitaly call timeout exceeds the worker timeout, the remaining time from the worker timeout is used to avoid having to terminate the worker. |
+| Fast | 10 seconds | Timeout for fast Gitaly operations used within requests, sometimes multiple times. For example, checking if a repository exists on disk. If fast operations exceed this threshold, there may be a problem with a storage shard. Failing fast can help maintain the stability of the GitLab instance. |
+| Medium | 30 seconds | Timeout for Gitaly operations that should be fast (possibly within requests) but preferably not used multiple times within a request. For example, loading blobs. Timeout that should be set between Default and Fast. |
diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md
index 6056af1cb80..e6c9a492c2c 100644
--- a/doc/user/group/devops_adoption/index.md
+++ b/doc/user/group/devops_adoption/index.md
@@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321083) in GitLab 13.11 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/333556) in GitLab 14.1.
> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
+> - DAST and SAST metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328033) in GitLab 14.1.
Prerequisites:
@@ -19,16 +20,18 @@ To access Group DevOps Adoption, go to your group and select **Analytics > DevOp
Group DevOps Adoption shows you how individual groups and sub-groups within your organization use the following features:
- Dev
- - Issues
- - Merge Requests
- Approvals
- Code owners
+ - Issues
+ - Merge requests
- Sec
+ - DAST
+ - SAST
- Scans
- Ops
- - Runners
- - Pipelines
- Deployments
+ - Pipelines
+ - Runners
When managing groups in the UI, you can add your sub-groups with the **Add sub-group to table**
button, in the top right hand section of your Groups pages.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index db2d6846b7c..809eaf1b8fd 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -507,7 +507,8 @@ This example links to `<wiki_root>/miscellaneous.md`:
GitLab Flavored Markdown renders GitLab-specific references. For example, you can reference
an issue, a commit, a team member, or even an entire project team. GitLab Flavored Markdown turns
-that reference into a link so you can navigate between them.
+that reference into a link so you can navigate between them. All references to namespaces or
+projects must use lower-case characters.
Additionally, GitLab Flavored Markdown recognizes certain cross-project references and also has a shorthand
version to reference other projects from the same namespace.
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index a5234828de3..145863e3158 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -120,24 +120,28 @@ module API
optional :straight, type: Boolean, desc: 'Comparison method, `true` for direct comparison between `from` and `to` (`from`..`to`), `false` to compare using merge base (`from`...`to`)', default: false
end
get ':id/repository/compare' do
- if params[:from_project_id].present?
- target_project = MergeRequestTargetProjectFinder
- .new(current_user: current_user, source_project: user_project, project_feature: :repository)
- .execute(include_routes: true).find_by_id(params[:from_project_id])
-
- if target_project.blank?
- render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
+ ff_enabled = Feature.enabled?(:api_caching_rate_limit_repository_compare, user_project, default_enabled: :yaml)
+
+ cache_action_if(ff_enabled, [user_project, :repository_compare, current_user, declared_params], expires_in: 30.seconds) do
+ if params[:from_project_id].present?
+ target_project = MergeRequestTargetProjectFinder
+ .new(current_user: current_user, source_project: user_project, project_feature: :repository)
+ .execute(include_routes: true).find_by_id(params[:from_project_id])
+
+ if target_project.blank?
+ render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
+ end
+ else
+ target_project = user_project
end
- else
- target_project = user_project
- end
- compare = CompareService.new(user_project, params[:to]).execute(target_project, params[:from], straight: params[:straight])
+ compare = CompareService.new(user_project, params[:to]).execute(target_project, params[:from], straight: params[:straight])
- if compare
- present compare, with: Entities::Compare
- else
- not_found!("Ref")
+ if compare
+ present compare, with: Entities::Compare
+ else
+ not_found!("Ref")
+ end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ddf2a37f450..4ed6f7c057a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2050,6 +2050,9 @@ msgstr ""
msgid "Add previously merged commits"
msgstr ""
+msgid "Add project"
+msgstr ""
+
msgid "Add projects"
msgstr ""
@@ -5762,6 +5765,9 @@ msgstr ""
msgid "CI/CD configuration file"
msgstr ""
+msgid "CI/CD|No projects have been added to the scope"
+msgstr ""
+
msgid "CICDAnalytics|%{percent}%{percentSymbol}"
msgstr ""
@@ -5797,6 +5803,9 @@ msgstr ""
msgid "CICD|Add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} with a domain, or create an AUTO_DEVOPS_PLATFORM_TARGET CI variable."
msgstr ""
+msgid "CICD|Add an existing project to the scope"
+msgstr ""
+
msgid "CICD|Auto DevOps"
msgstr ""
@@ -5821,6 +5830,12 @@ msgstr ""
msgid "CICD|Jobs"
msgstr ""
+msgid "CICD|Limit CI_JOB_TOKEN access"
+msgstr ""
+
+msgid "CICD|Manage which projects can use this project's CI_JOB_TOKEN CI/CD variable for API access"
+msgstr ""
+
msgid "CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file."
msgstr ""
@@ -8784,6 +8799,9 @@ msgstr ""
msgid "Control whether to display third-party offers in GitLab."
msgstr ""
+msgid "Control which projects can use the CI_JOB_TOKEN CI/CD variable for API access to this project. It is a security risk to disable this feature, because unauthorized projects may attempt to retrieve an active token and access the API."
+msgstr ""
+
msgid "Cookie domain"
msgstr ""
@@ -10314,9 +10332,6 @@ msgstr ""
msgid "Default CI/CD configuration file"
msgstr ""
-msgid "Default Timeout Period"
-msgstr ""
-
msgid "Default artifacts expiration"
msgstr ""
@@ -10353,6 +10368,9 @@ msgstr ""
msgid "Default projects limit"
msgstr ""
+msgid "Default timeout"
+msgstr ""
+
msgid "Default: Map a FogBugz account ID to a full name"
msgstr ""
@@ -11150,10 +11168,7 @@ msgstr ""
msgid "DevopsAdoption|Are you sure that you would like to remove %{name} from the table?"
msgstr ""
-msgid "DevopsAdoption|At least one MR opened"
-msgstr ""
-
-msgid "DevopsAdoption|At least one approval on an MR"
+msgid "DevopsAdoption|At least one approval on a merge request"
msgstr ""
msgid "DevopsAdoption|At least one deploy"
@@ -11162,6 +11177,9 @@ msgstr ""
msgid "DevopsAdoption|At least one issue opened"
msgstr ""
+msgid "DevopsAdoption|At least one merge request opened"
+msgstr ""
+
msgid "DevopsAdoption|At least one pipeline successfully run"
msgstr ""
@@ -11177,6 +11195,12 @@ msgstr ""
msgid "DevopsAdoption|Confirm remove Group"
msgstr ""
+msgid "DevopsAdoption|DAST"
+msgstr ""
+
+msgid "DevopsAdoption|DAST enabled for at least one project"
+msgstr ""
+
msgid "DevopsAdoption|Deploys"
msgstr ""
@@ -11231,6 +11255,12 @@ msgstr ""
msgid "DevopsAdoption|Runners"
msgstr ""
+msgid "DevopsAdoption|SAST"
+msgstr ""
+
+msgid "DevopsAdoption|SAST enabled for at least one project"
+msgstr ""
+
msgid "DevopsAdoption|Save changes"
msgstr ""
@@ -13563,7 +13593,7 @@ msgstr ""
msgid "False positive"
msgstr ""
-msgid "Fast Timeout Period"
+msgid "Fast timeout"
msgstr ""
msgid "Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged."
@@ -15025,9 +15055,6 @@ msgstr ""
msgid "GitLabPages|Your pages are served under:"
msgstr ""
-msgid "Gitaly"
-msgstr ""
-
msgid "Gitaly Servers"
msgstr ""
@@ -15037,6 +15064,9 @@ msgstr ""
msgid "Gitaly storage name:"
msgstr ""
+msgid "Gitaly timeouts"
+msgstr ""
+
msgid "Gitaly|Address"
msgstr ""
@@ -20201,7 +20231,7 @@ msgstr ""
msgid "Measured in bytes of code. Excludes generated and vendored code."
msgstr ""
-msgid "Medium Timeout Period"
+msgid "Medium timeout"
msgstr ""
msgid "Medium vulnerabilities present"
@@ -23612,6 +23642,9 @@ msgstr ""
msgid "Paste issue link"
msgstr ""
+msgid "Paste project path (i.e. gitlab-org/gitlab)"
+msgstr ""
+
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity."
msgstr ""
@@ -25958,6 +25991,9 @@ msgstr ""
msgid "Projects will be permanently deleted immediately."
msgstr ""
+msgid "Projects with access"
+msgstr ""
+
msgid "Projects with critical vulnerabilities"
msgstr ""
@@ -27036,6 +27072,9 @@ msgstr ""
msgid "Remove Zoom meeting"
msgstr ""
+msgid "Remove access"
+msgstr ""
+
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch."
msgstr ""
@@ -28492,6 +28531,9 @@ msgstr ""
msgid "Search for a user"
msgstr ""
+msgid "Search for project"
+msgstr ""
+
msgid "Search for projects, issues, etc."
msgstr ""
@@ -32808,9 +32850,15 @@ msgstr ""
msgid "There was a problem fetching project users."
msgstr ""
+msgid "There was a problem fetching the job token scope value"
+msgstr ""
+
msgid "There was a problem fetching the keep latest artifacts setting."
msgstr ""
+msgid "There was a problem fetching the projects"
+msgstr ""
+
msgid "There was a problem fetching users."
msgstr ""
@@ -33894,6 +33942,15 @@ msgstr ""
msgid "Timeout connecting to the Google API. Please try again."
msgstr ""
+msgid "Timeout for moderately fast Gitaly operations (in seconds). Provide a value between Default timeout and Fast timeout."
+msgstr ""
+
+msgid "Timeout for most Gitaly operations (in seconds)."
+msgstr ""
+
+msgid "Timeout for the fastest Gitaly operations (in seconds)."
+msgstr ""
+
msgid "Timezone"
msgstr ""
@@ -34165,6 +34222,9 @@ msgstr ""
msgid "Token"
msgstr ""
+msgid "Token Access"
+msgstr ""
+
msgid "Token name"
msgstr ""
@@ -37179,7 +37239,7 @@ msgstr ""
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
-msgid "You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in future, you will also need to register with GitLab via a new cloud licensing service."
+msgid "You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in the future, you will also need to register with GitLab via a new cloud licensing service."
msgstr ""
msgid "You can enable project access token creation in %{link_start}group settings%{link_end}."
diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
index fd161cdb4a6..734ff160937 100644
--- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
@@ -94,7 +94,7 @@ module QA
end
def run_jenkins_server
- Service::DockerRun::Jenkins.init do |runner|
+ Service::DockerRun::Jenkins.new.tap do |runner|
runner.pull
runner.register!
end
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index dad041072ce..15029d7a911 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -207,13 +207,11 @@ describe('RunnerUpdateForm', () => {
});
it.each`
- value | submitted
- ${''} | ${{ tagList: [] }}
- ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }}
- ${'with spaces'} | ${{ tagList: ['with spaces'] }}
- ${',,,,, commas'} | ${{ tagList: ['commas'] }}
- ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }}
- ${' trimmed , trimmed2 '} | ${{ tagList: ['trimmed', 'trimmed2'] }}
+ value | submitted
+ ${''} | ${{ tagList: [] }}
+ ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }}
+ ${'with spaces'} | ${{ tagList: ['with spaces'] }}
+ ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }}
`('Field updates runner\'s tags for "$value"', async ({ value, submitted }) => {
const runner = { ...mockRunner, tagList: ['tag1'] };
createComponent({ props: { runner } });
diff --git a/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js b/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js
new file mode 100644
index 00000000000..510b4e604ac
--- /dev/null
+++ b/spec/frontend/runner/runner_detail/runner_update_form_utils_spec.js
@@ -0,0 +1,96 @@
+import { ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants';
+import {
+ modelToUpdateMutationVariables,
+ runnerToModel,
+} from '~/runner/runner_details/runner_update_form_utils';
+
+const mockId = 'gid://gitlab/Ci::Runner/1';
+const mockDescription = 'Runner Desc.';
+
+const mockRunner = {
+ id: mockId,
+ description: mockDescription,
+ maximumTimeout: 100,
+ accessLevel: ACCESS_LEVEL_NOT_PROTECTED,
+ active: true,
+ locked: true,
+ runUntagged: true,
+ tagList: ['tag-1', 'tag-2'],
+};
+
+const mockModel = {
+ ...mockRunner,
+ tagList: 'tag-1, tag-2',
+};
+
+describe('~/runner/runner_details/runner_update_form_utils', () => {
+ describe('runnerToModel', () => {
+ it('collects all model data', () => {
+ expect(runnerToModel(mockRunner)).toEqual(mockModel);
+ });
+
+ it('does not collect other data', () => {
+ const model = runnerToModel({
+ ...mockRunner,
+ unrelated: 'unrelatedValue',
+ });
+
+ expect(model.unrelated).toEqual(undefined);
+ });
+
+ it('tag list defaults to an empty string', () => {
+ const model = runnerToModel({
+ ...mockRunner,
+ tagList: undefined,
+ });
+
+ expect(model.tagList).toEqual('');
+ });
+ });
+
+ describe('modelToUpdateMutationVariables', () => {
+ it('collects all model data', () => {
+ expect(modelToUpdateMutationVariables(mockModel)).toEqual({
+ input: {
+ ...mockRunner,
+ },
+ });
+ });
+
+ it('collects a nullable timeout from the model', () => {
+ const variables = modelToUpdateMutationVariables({
+ ...mockModel,
+ maximumTimeout: '',
+ });
+
+ expect(variables).toEqual({
+ input: {
+ ...mockRunner,
+ maximumTimeout: null,
+ },
+ });
+ });
+
+ it.each`
+ tagList | tagListInput
+ ${''} | ${[]}
+ ${'tag1, tag2'} | ${['tag1', 'tag2']}
+ ${'with spaces'} | ${['with spaces']}
+ ${',,,,, commas'} | ${['commas']}
+ ${'more ,,,,, commas'} | ${['more', 'commas']}
+ ${' trimmed , trimmed2 '} | ${['trimmed', 'trimmed2']}
+ `('collect tags separated by commas for "$value"', ({ tagList, tagListInput }) => {
+ const variables = modelToUpdateMutationVariables({
+ ...mockModel,
+ tagList,
+ });
+
+ expect(variables).toEqual({
+ input: {
+ ...mockRunner,
+ tagList: tagListInput,
+ },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/token_access/mock_data.js b/spec/frontend/token_access/mock_data.js
new file mode 100644
index 00000000000..14d7b00cb6d
--- /dev/null
+++ b/spec/frontend/token_access/mock_data.js
@@ -0,0 +1,84 @@
+export const enabledJobTokenScope = {
+ data: {
+ project: {
+ ciCdSettings: {
+ jobTokenScopeEnabled: true,
+ __typename: 'ProjectCiCdSetting',
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const disabledJobTokenScope = {
+ data: {
+ project: {
+ ciCdSettings: {
+ jobTokenScopeEnabled: false,
+ __typename: 'ProjectCiCdSetting',
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const updateJobTokenScope = {
+ data: {
+ ciCdSettingsUpdate: {
+ ciCdSettings: {
+ jobTokenScopeEnabled: true,
+ __typename: 'ProjectCiCdSetting',
+ },
+ errors: [],
+ __typename: 'CiCdSettingsUpdatePayload',
+ },
+ },
+};
+
+export const projectsWithScope = {
+ data: {
+ project: {
+ __typename: 'Project',
+ ciJobTokenScope: {
+ __typename: 'CiJobTokenScopeType',
+ projects: {
+ __typename: 'ProjectConnection',
+ nodes: [
+ {
+ fullPath: 'root/332268-test',
+ name: 'root/332268-test',
+ },
+ ],
+ },
+ },
+ },
+ },
+};
+
+export const addProjectSuccess = {
+ data: {
+ ciJobTokenScopeAddProject: {
+ errors: [],
+ __typename: 'CiJobTokenScopeAddProjectPayload',
+ },
+ },
+};
+
+export const removeProjectSuccess = {
+ data: {
+ ciJobTokenScopeRemoveProject: {
+ errors: [],
+ __typename: 'CiJobTokenScopeRemoveProjectPayload',
+ },
+ },
+};
+
+export const mockProjects = [
+ {
+ name: 'merge-train-stuff',
+ fullPath: 'root/merge-train-stuff',
+ isLocked: false,
+ __typename: 'Project',
+ },
+ { name: 'ci-project', fullPath: 'root/ci-project', isLocked: true, __typename: 'Project' },
+];
diff --git a/spec/frontend/token_access/token_access_spec.js b/spec/frontend/token_access/token_access_spec.js
new file mode 100644
index 00000000000..244a7f00cb9
--- /dev/null
+++ b/spec/frontend/token_access/token_access_spec.js
@@ -0,0 +1,213 @@
+import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
+import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import TokenAccess from '~/token_access/components/token_access.vue';
+import addProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
+import removeProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql';
+import updateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/update_ci_job_token_scope.mutation.graphql';
+import getCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_ci_job_token_scope.query.graphql';
+import getProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/get_projects_with_ci_job_token_scope.query.graphql';
+import {
+ enabledJobTokenScope,
+ disabledJobTokenScope,
+ updateJobTokenScope,
+ projectsWithScope,
+ addProjectSuccess,
+ removeProjectSuccess,
+} from './mock_data';
+
+const projectPath = 'root/my-repo';
+const error = new Error('Error');
+const localVue = createLocalVue();
+
+localVue.use(VueApollo);
+
+jest.mock('~/flash');
+
+describe('TokenAccess component', () => {
+ let wrapper;
+
+ const enabledJobTokenScopeHandler = jest.fn().mockResolvedValue(enabledJobTokenScope);
+ const disabledJobTokenScopeHandler = jest.fn().mockResolvedValue(disabledJobTokenScope);
+ const updateJobTokenScopeHandler = jest.fn().mockResolvedValue(updateJobTokenScope);
+ const getProjectsWithScope = jest.fn().mockResolvedValue(projectsWithScope);
+ const addProjectSuccessHandler = jest.fn().mockResolvedValue(addProjectSuccess);
+ const addProjectFailureHandler = jest.fn().mockRejectedValue(error);
+ const removeProjectSuccessHandler = jest.fn().mockResolvedValue(removeProjectSuccess);
+ const removeProjectFailureHandler = jest.fn().mockRejectedValue(error);
+
+ const findToggle = () => wrapper.findComponent(GlToggle);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findAddProjectBtn = () => wrapper.find('[data-testid="add-project-button"]');
+ const findRemoveProjectBtn = () => wrapper.find('[data-testid="remove-project-button"]');
+ const findTokenSection = () => wrapper.find('[data-testid="token-section"]');
+
+ const createMockApolloProvider = (requestHandlers) => {
+ return createMockApollo(requestHandlers);
+ };
+
+ const createComponent = (requestHandlers, mountFn = shallowMount) => {
+ wrapper = mountFn(TokenAccess, {
+ localVue,
+ provide: {
+ fullPath: projectPath,
+ },
+ apolloProvider: createMockApolloProvider(requestHandlers),
+ data() {
+ return {
+ targetProjectPath: 'root/test',
+ };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('loading state', () => {
+ it('shows loading state while waiting on query to resolve', async () => {
+ createComponent([
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ ]);
+
+ expect(findLoadingIcon().exists()).toBe(true);
+
+ await waitForPromises();
+
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('toggle', () => {
+ it('the toggle should be enabled and the token section should show', async () => {
+ createComponent([
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ ]);
+
+ await waitForPromises();
+
+ expect(findToggle().props('value')).toBe(true);
+ expect(findTokenSection().exists()).toBe(true);
+ });
+
+ it('the toggle should be disabled and the token section should not show', async () => {
+ createComponent([
+ [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ ]);
+
+ await waitForPromises();
+
+ expect(findToggle().props('value')).toBe(false);
+ expect(findTokenSection().exists()).toBe(false);
+ });
+
+ it('switching the toggle calls the mutation', async () => {
+ createComponent([
+ [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler],
+ [updateCIJobTokenScopeMutation, updateJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ ]);
+
+ await waitForPromises();
+
+ findToggle().vm.$emit('change', true);
+
+ expect(updateJobTokenScopeHandler).toHaveBeenCalledWith({
+ input: { fullPath: projectPath, jobTokenScopeEnabled: true },
+ });
+ });
+ });
+
+ describe('add project', () => {
+ it('calls add project mutation', async () => {
+ createComponent(
+ [
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ [addProjectCIJobTokenScopeMutation, addProjectSuccessHandler],
+ ],
+ mount,
+ );
+
+ await waitForPromises();
+
+ findAddProjectBtn().trigger('click');
+
+ expect(addProjectSuccessHandler).toHaveBeenCalledWith({
+ input: {
+ projectPath,
+ targetProjectPath: 'root/test',
+ },
+ });
+ });
+
+ it('add project handles error correctly', async () => {
+ createComponent(
+ [
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ [addProjectCIJobTokenScopeMutation, addProjectFailureHandler],
+ ],
+ mount,
+ );
+
+ await waitForPromises();
+
+ findAddProjectBtn().trigger('click');
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
+ describe('remove project', () => {
+ it('calls remove project mutation', async () => {
+ createComponent(
+ [
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ [removeProjectCIJobTokenScopeMutation, removeProjectSuccessHandler],
+ ],
+ mount,
+ );
+
+ await waitForPromises();
+
+ findRemoveProjectBtn().trigger('click');
+
+ expect(removeProjectSuccessHandler).toHaveBeenCalledWith({
+ input: {
+ projectPath,
+ targetProjectPath: 'root/332268-test',
+ },
+ });
+ });
+
+ it('remove project handles error correctly', async () => {
+ createComponent(
+ [
+ [getCIJobTokenScopeQuery, enabledJobTokenScopeHandler],
+ [getProjectsWithCIJobTokenScopeQuery, getProjectsWithScope],
+ [removeProjectCIJobTokenScopeMutation, removeProjectFailureHandler],
+ ],
+ mount,
+ );
+
+ await waitForPromises();
+
+ findRemoveProjectBtn().trigger('click');
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/token_access/token_projects_table_spec.js b/spec/frontend/token_access/token_projects_table_spec.js
new file mode 100644
index 00000000000..3bda0d0b530
--- /dev/null
+++ b/spec/frontend/token_access/token_projects_table_spec.js
@@ -0,0 +1,51 @@
+import { GlTable, GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import TokenProjectsTable from '~/token_access/components/token_projects_table.vue';
+import { mockProjects } from './mock_data';
+
+describe('Token projects table', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = mount(TokenProjectsTable, {
+ provide: {
+ fullPath: 'root/ci-project',
+ },
+ propsData: {
+ projects: mockProjects,
+ },
+ });
+ };
+
+ const findTable = () => wrapper.findComponent(GlTable);
+ const findAllTableRows = () => wrapper.findAll('[data-testid="projects-token-table-row"]');
+ const findDeleteProjectBtn = () => wrapper.findComponent(GlButton);
+ const findAllDeleteProjectBtn = () => wrapper.findAllComponents(GlButton);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('displays a table', () => {
+ expect(findTable().exists()).toBe(true);
+ });
+
+ it('displays the correct amount of table rows', () => {
+ expect(findAllTableRows()).toHaveLength(mockProjects.length);
+ });
+
+ it('delete project button emits event with correct project to delete', async () => {
+ await findDeleteProjectBtn().trigger('click');
+
+ expect(wrapper.emitted('removeProject')).toEqual([[mockProjects[0].fullPath]]);
+ });
+
+ it('does not show the remove icon if the project is locked', () => {
+ // currently two mock projects with one being a locked project
+ expect(findAllDeleteProjectBtn()).toHaveLength(1);
+ });
+});
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 1b96efeca22..d019e89e0b4 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -477,6 +477,17 @@ RSpec.describe API::Repositories do
let(:request) { get api(route, guest) }
end
end
+
+ context 'api_caching_rate_limit_repository_compare is disabled' do
+ before do
+ stub_feature_flags(api_caching_rate_limit_repository_compare: false)
+ end
+
+ it_behaves_like 'repository compare' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
end
describe 'GET /projects/:id/repository/contributors' do