summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-04 12:08:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-04 12:08:25 +0000
commit7928b47c8e06c1ac3f63d321a73dd527aea4e4c3 (patch)
treedff8cfaaae2fa981b6d95a866ebd1f7b6b86838c /app
parent0d8bcdf77d609b3624541de767a0129aa0b7e8d2 (diff)
downloadgitlab-ce-7928b47c8e06c1ac3f63d321a73dd527aea4e4c3.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/access_tokens/components/access_token_table_app.vue17
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue48
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js3
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue4
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js35
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue165
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue2
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue4
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js10
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql50
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/app.vue5
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue37
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js12
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/incidents.scss73
-rw-r--r--app/assets/stylesheets/page_bundles/prometheus.scss (renamed from app/assets/stylesheets/pages/prometheus.scss)2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss100
-rw-r--r--app/helpers/ci/pipeline_editor_helper.rb12
-rw-r--r--app/helpers/ci/pipelines_helper.rb3
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/users/phone_number_validation.rb41
-rw-r--r--app/models/wiki.rb10
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/uploaders/object_storage/cdn/google_cdn.rb2
-rw-r--r--app/views/clusters/clusters/_health.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/branch_rules/_show.html.haml2
-rw-r--r--app/views/projects/environments/metrics.html.haml2
-rw-r--r--app/views/projects/incidents/show.html.haml1
-rw-r--r--app/views/projects/issues/show.html.haml1
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml10
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/settings/branch_rules/index.html.haml4
34 files changed, 498 insertions, 178 deletions
diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
index 76709296c89..cd64579a360 100644
--- a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
+++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
@@ -55,7 +55,22 @@ export default {
},
computed: {
filteredFields() {
- return this.showRole ? FIELDS : FIELDS.filter((field) => field.key !== 'role');
+ const ignoredFields = [];
+
+ // Show 'action' column only when there are no active tokens or when some of them have a revokePath
+ const showAction =
+ this.activeAccessTokens.length === 0 ||
+ this.activeAccessTokens.some((token) => token.revokePath);
+
+ if (!showAction) {
+ ignoredFields.push('action');
+ }
+
+ if (!this.showRole) {
+ ignoredFields.push('role');
+ }
+
+ return FIELDS.filter(({ key }) => !ignoredFields.includes(key));
},
header() {
return sprintf(this.$options.i18n.header, {
diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
index 3e87088e77e..7d2b9cd3d42 100644
--- a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
+++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
@@ -15,11 +15,20 @@ export default {
'Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started.',
),
btnText: __('Configure pipeline'),
+ externalCiNote: __("This project's pipeline configuration is located outside this repository"),
+ externalCiInstructions: __(
+ 'To edit the pipeline configuration, you must go to the project or external site that hosts the file.',
+ ),
},
inject: {
emptyStateIllustrationPath: {
default: '',
},
+ usesExternalConfig: {
+ default: false,
+ type: Boolean,
+ required: false,
+ },
},
methods: {
createEmptyConfigFile() {
@@ -33,22 +42,31 @@ export default {
<pipeline-editor-file-nav v-on="$listeners" />
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
<img :src="emptyStateIllustrationPath" />
- <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
- <p class="gl-mt-3">
- <gl-sprintf :message="$options.i18n.body">
- <template #code="{ content }">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </p>
- <gl-button
- variant="confirm"
- class="gl-mt-3"
- data-qa-selector="create_new_ci_button"
- @click="createEmptyConfigFile"
+ <div
+ v-if="usesExternalConfig"
+ class="gl-display-flex gl-flex-direction-column gl-align-items-center"
>
- {{ $options.i18n.btnText }}
- </gl-button>
+ <h1 class="gl-font-size-h1">{{ $options.i18n.externalCiNote }}</h1>
+ <p class="gl-mt-3">{{ $options.i18n.externalCiInstructions }}</p>
+ </div>
+ <div v-else class="gl-display-flex gl-flex-direction-column gl-align-items-center">
+ <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
+ <p class="gl-mt-3">
+ <gl-sprintf :message="$options.i18n.body">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-button
+ variant="confirm"
+ class="gl-mt-3"
+ data-qa-selector="create_new_ci_button"
+ @click="createEmptyConfigFile"
+ >
+ {{ $options.i18n.btnText }}
+ </gl-button>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 13dad0b2459..6d91c339833 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { parseBoolean } from '~/lib/utils/common_utils';
import { EDITOR_APP_STATUS_LOADING } from './constants';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
@@ -42,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectNamespace,
simulatePipelineHelpPagePath,
totalBranches,
+ usesExternalConfig,
validateTabIllustrationPath,
ymlHelpPagePath,
} = el.dataset;
@@ -133,6 +135,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectNamespace,
simulatePipelineHelpPagePath,
totalBranches: parseInt(totalBranches, 10),
+ usesExternalConfig: parseBoolean(usesExternalConfig),
validateTabIllustrationPath,
ymlHelpPagePath,
},
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 548769eb214..ff848a973e3 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -40,7 +40,7 @@ export default {
PipelineEditorHome,
PipelineEditorMessages,
},
- inject: ['ciConfigPath', 'newMergeRequestPath', 'projectFullPath'],
+ inject: ['ciConfigPath', 'newMergeRequestPath', 'projectFullPath', 'usesExternalConfig'],
data() {
return {
ciConfigData: {},
@@ -397,7 +397,7 @@ export default {
<div class="gl-mt-4 gl-relative" data-qa-selector="pipeline_editor_app">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<pipeline-editor-empty-state
- v-else-if="showStartScreen"
+ v-else-if="showStartScreen || usesExternalConfig"
@createEmptyConfigFile="setNewEmptyCiConfigFile"
@refetchContent="refetchContent"
/>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
new file mode 100644
index 00000000000..1fd87b9897c
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
@@ -0,0 +1,35 @@
+import { s__ } from '~/locale';
+
+export const I18N = {
+ manageProtectionsLinkTitle: s__('BranchRules|Manage in Protected Branches'),
+ targetBranch: s__('BranchRules|Target Branch'),
+ branchNameOrPattern: s__('BranchRules|Branch name or pattern'),
+ branch: s__('BranchRules|Target Branch'),
+ allBranches: s__('BranchRules|All branches'),
+ protectBranchTitle: s__('BranchRules|Protect branch'),
+ protectBranchDescription: s__(
+ 'BranchRules|Keep stable branches secure and force developers to use merge requests. %{linkStart}What are protected branches?%{linkEnd}',
+ ),
+ wildcardsHelpText: s__(
+ 'BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported',
+ ),
+ forcePushTitle: s__('BranchRules|Force push'),
+ allowForcePushDescription: s__(
+ 'BranchRules|All users with push access are allowed to force push.',
+ ),
+ disallowForcePushDescription: s__('BranchRules|Force push is not allowed.'),
+ approvalsTitle: s__('BranchRules|Approvals'),
+ statusChecksTitle: s__('BranchRules|Status checks'),
+ allowedToPushHeader: s__('BranchRules|Allowed to push (%{total})'),
+ allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'),
+ noData: s__('BranchRules|No data to display'),
+};
+
+export const BRANCH_PARAM_NAME = 'branch';
+
+export const ALL_BRANCHES_WILDCARD = '*';
+
+export const WILDCARDS_HELP_PATH =
+ 'user/project/protected_branches#configure-multiple-protected-branches-by-using-a-wildcard';
+
+export const PROTECTED_BRANCHES_HELP_PATH = 'user/project/protected_branches';
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 58938b6bf96..6534ff883a6 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -1,33 +1,172 @@
<script>
-import { s__ } from '~/locale';
+import { GlSprintf, GlLink, GlLoadingIcon } from '@gitlab/ui';
+import { sprintf } from '~/locale';
import { getParameterByName } from '~/lib/utils/url_utility';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import branchRulesQuery from '../../queries/branch_rules_details.query.graphql';
+import Protection from './protection.vue';
+import {
+ I18N,
+ ALL_BRANCHES_WILDCARD,
+ BRANCH_PARAM_NAME,
+ WILDCARDS_HELP_PATH,
+ PROTECTED_BRANCHES_HELP_PATH,
+} from './constants';
+
+const wildcardsHelpDocLink = helpPagePath(WILDCARDS_HELP_PATH);
+const protectedBranchesHelpDocLink = helpPagePath(PROTECTED_BRANCHES_HELP_PATH);
export default {
name: 'RuleView',
- i18n: { branch: s__('BranchRules|Target Branch') },
- components: {},
- props: {
+ i18n: I18N,
+ wildcardsHelpDocLink,
+ protectedBranchesHelpDocLink,
+ components: { Protection, GlSprintf, GlLink, GlLoadingIcon },
+ inject: {
projectPath: {
- type: String,
- required: true,
+ default: '',
+ },
+ protectedBranchesPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ project: {
+ query: branchRulesQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update({ project: { branchRules } }) {
+ this.branchProtection = branchRules.nodes.find(
+ (rule) => rule.name === this.branch,
+ )?.branchProtection;
+ },
},
},
data() {
return {
- branch: getParameterByName('branch'),
- protected: getParameterByName('protected'),
+ branch: getParameterByName(BRANCH_PARAM_NAME),
+ branchProtection: {},
};
},
+ computed: {
+ forcePushDescription() {
+ return this.branchProtection?.allowForcePush
+ ? this.$options.i18n.allowForcePushDescription
+ : this.$options.i18n.disallowForcePushDescription;
+ },
+ mergeAccessLevels() {
+ const { mergeAccessLevels } = this.branchProtection || {};
+ return this.getAccessLevels(mergeAccessLevels);
+ },
+ pushAccessLevels() {
+ const { pushAccessLevels } = this.branchProtection || {};
+ return this.getAccessLevels(pushAccessLevels);
+ },
+ allowedToMergeHeader() {
+ return sprintf(this.$options.i18n.allowedToMergeHeader, {
+ total: this.mergeAccessLevels.total,
+ });
+ },
+ allowedToPushHeader() {
+ return sprintf(this.$options.i18n.allowedToPushHeader, {
+ total: this.pushAccessLevels.total,
+ });
+ },
+ allBranches() {
+ return this.branch === ALL_BRANCHES_WILDCARD;
+ },
+ allBranchesLabel() {
+ return this.$options.i18n.allBranches;
+ },
+ branchTitle() {
+ return this.allBranches
+ ? this.$options.i18n.targetBranch
+ : this.$options.i18n.branchNameOrPattern;
+ },
+ },
+ methods: {
+ getAccessLevels(accessLevels = {}) {
+ const total = accessLevels.edges?.length;
+ const accessLevelTypes = { total, users: [], groups: [], roles: [] };
+
+ accessLevels.edges?.forEach(({ node }) => {
+ if (node.user) {
+ const src = node.user.avatarUrl;
+ accessLevelTypes.users.push({ src, ...node.user });
+ } else if (node.group) {
+ accessLevelTypes.groups.push(node);
+ } else {
+ accessLevelTypes.roles.push(node);
+ }
+ });
+
+ return accessLevelTypes;
+ },
+ },
};
</script>
<template>
- <div>
- <div class="gl-display-flex gl-flex-direction-column gl-pt-3">
- <strong>{{ $options.i18n.branch }}</strong>
- <span class="gl-font-monospace gl-mt-2" data-testid="branch">{{ branch }}</span>
+ <gl-loading-icon v-if="$apollo.loading" />
+ <div v-else-if="!branchProtection">{{ $options.i18n.noData }}</div>
+ <div v-else>
+ <strong data-testid="branch-title">{{ branchTitle }}</strong>
+ <p v-if="!allBranches" class="gl-mb-3 gl-text-gray-400">
+ <gl-sprintf :message="$options.i18n.wildcardsHelpText">
+ <template #link="{ content }">
+ <gl-link :href="$options.wildcardsHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <div v-if="allBranches" class="gl-mt-2" data-testid="branch">
+ {{ allBranchesLabel }}
</div>
+ <code v-else class="gl-mt-2" data-testid="branch">{{ branch }}</code>
+
+ <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.protectBranchTitle }}</h4>
+ <gl-sprintf :message="$options.i18n.protectBranchDescription">
+ <template #link="{ content }">
+ <gl-link :href="$options.protectedBranchesHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+
+ <!-- Allowed to push -->
+ <protection
+ class="gl-mt-3"
+ :header="allowedToPushHeader"
+ :header-link-title="$options.i18n.manageProtectionsLinkTitle"
+ :header-link-href="protectedBranchesPath"
+ :roles="pushAccessLevels.roles"
+ :users="pushAccessLevels.users"
+ :groups="pushAccessLevels.groups"
+ />
+
+ <!-- Force push -->
+ <strong>{{ $options.i18n.forcePushTitle }}</strong>
+ <p>{{ forcePushDescription }}</p>
+
+ <!-- Allowed to merge -->
+ <protection
+ :header="allowedToMergeHeader"
+ :header-link-title="$options.i18n.manageProtectionsLinkTitle"
+ :header-link-href="protectedBranchesPath"
+ :roles="mergeAccessLevels.roles"
+ :users="mergeAccessLevels.users"
+ :groups="mergeAccessLevels.groups"
+ />
+
+ <!-- Approvals -->
+ <!-- Follow-up: add approval section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
- <!-- TODO: List branch rule details (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
+ <!-- Status checks -->
+ <!-- Follow-up: add status checks section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
index ec70ab88870..8434b7bfce5 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
@@ -58,7 +58,7 @@ export default {
<template #header>
<div class="gl-display-flex gl-justify-content-space-between">
<strong>{{ header }}</strong>
- <gl-link :href="headerLinkHref" target="_blank">{{ headerLinkTitle }}</gl-link>
+ <gl-link :href="headerLinkHref">{{ headerLinkTitle }}</gl-link>
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
index 56be0198574..2509c2538b2 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
@@ -45,6 +45,9 @@ export default {
this.users.length - this.$options.MAX_VISIBLE_AVATARS,
);
},
+ commaSeparateList() {
+ return this.accessLevels.length > 1;
+ },
},
};
</script>
@@ -80,6 +83,7 @@ export default {
</gl-avatars-inline>
<div v-for="(item, index) in accessLevels" :key="index" data-testid="access-level">
+ <span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span>
{{ item.accessLevelDescription }}
</div>
</div>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
index 10de5f12757..39164063d05 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
+++ b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import RuleEdit from './components/edit/index.vue';
+import View from './components/view/index.vue';
export default function mountBranchRules(el) {
if (!el) {
@@ -14,13 +14,17 @@ export default function mountBranchRules(el) {
defaultClient: createDefaultClient(),
});
- const { projectPath } = el.dataset;
+ const { projectPath, protectedBranchesPath } = el.dataset;
return new Vue({
el,
apolloProvider,
+ provide: {
+ projectPath,
+ protectedBranchesPath,
+ },
render(h) {
- return h(RuleEdit, { props: { projectPath } });
+ return h(View);
},
});
}
diff --git a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
new file mode 100644
index 00000000000..3ac165498a1
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
@@ -0,0 +1,50 @@
+query getBranchRulesDetails($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ id
+ branchRules {
+ nodes {
+ name
+ branchProtection {
+ allowForcePush
+ codeOwnerApprovalRequired
+ mergeAccessLevels {
+ edges {
+ node {
+ accessLevel
+ accessLevelDescription
+ group {
+ id
+ avatarUrl
+ }
+ user {
+ id
+ name
+ avatarUrl
+ webUrl
+ }
+ }
+ }
+ }
+ pushAccessLevels {
+ edges {
+ node {
+ accessLevel
+ accessLevelDescription
+ group {
+ id
+ avatarUrl
+ }
+ user {
+ id
+ name
+ avatarUrl
+ webUrl
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
index 65e0c87e3f3..94793a535cc 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -35,10 +35,9 @@ export default {
},
},
},
- props: {
+ inject: {
projectPath: {
- type: String,
- required: true,
+ default: '',
},
},
data() {
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index 68750318029..2b88f8561d7 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -1,10 +1,11 @@
<script>
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export const i18n = {
defaultLabel: s__('BranchRules|default'),
protectedLabel: s__('BranchRules|protected'),
+ detailsButtonLabel: s__('BranchRules|Details'),
};
export default {
@@ -12,6 +13,12 @@ export default {
i18n,
components: {
GlBadge,
+ GlButton,
+ },
+ inject: {
+ branchRulesPath: {
+ default: '',
+ },
},
props: {
name: {
@@ -38,24 +45,30 @@ export default {
hasApprovalDetails() {
return this.approvalDetails && this.approvalDetails.length;
},
+ detailsPath() {
+ return `${this.branchRulesPath}?branch=${this.name}`;
+ },
},
};
</script>
<template>
- <div class="gl-border-b gl-pt-5 gl-pb-5">
- <strong class="gl-font-monospace">{{ name }}</strong>
+ <div class="gl-border-b gl-pt-5 gl-pb-5 gl-display-flex gl-justify-content-space-between">
+ <div>
+ <strong class="gl-font-monospace">{{ name }}</strong>
- <gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
- $options.i18n.defaultLabel
- }}</gl-badge>
+ <gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
+ $options.i18n.defaultLabel
+ }}</gl-badge>
- <gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
- $options.i18n.protectedLabel
- }}</gl-badge>
+ <gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
+ $options.i18n.protectedLabel
+ }}</gl-badge>
- <ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
- <li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
- </ul>
+ <ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
+ <li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
+ </ul>
+ </div>
+ <gl-button :href="detailsPath"> {{ $options.i18n.detailsButtonLabel }}</gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
index 35322e2e466..042be089e09 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
@@ -12,17 +12,17 @@ const apolloProvider = new VueApollo({
export default function mountBranchRules(el) {
if (!el) return null;
- const { projectPath } = el.dataset;
+ const { projectPath, branchRulesPath } = el.dataset;
return new Vue({
el,
apolloProvider,
+ provide: {
+ projectPath,
+ branchRulesPath,
+ },
render(createElement) {
- return createElement(BranchRulesApp, {
- props: {
- projectPath,
- },
- });
+ return createElement(BranchRulesApp);
},
});
}
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index 147d7a92d57..68fe701c7e1 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -17,7 +17,6 @@
@import './pages/pipelines';
@import './pages/profile';
@import './pages/projects';
-@import './pages/prometheus';
@import './pages/registry';
@import './pages/search';
@import './pages/service_desk';
diff --git a/app/assets/stylesheets/page_bundles/incidents.scss b/app/assets/stylesheets/page_bundles/incidents.scss
new file mode 100644
index 00000000000..de246fa14b9
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/incidents.scss
@@ -0,0 +1,73 @@
+@import 'mixins_and_variables_and_functions';
+
+.issuable-discussion.incident-timeline-events {
+ .main-notes-list::before {
+ content: none;
+ }
+
+ .timeline-event-note {
+ p {
+ margin-bottom: 0;
+ font-size: 0.875rem;
+ }
+ }
+}
+
+/**
+ * We have a very specific design proposal where we cannot
+ * use `vertical-line` mixin as it is and have to use
+ * custom styles, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81284#note_904867444
+ */
+.timeline-entry-vertical-line {
+ &::before,
+ &::after {
+ content: '';
+ border-left: 2px solid $gray-50;
+ position: absolute;
+ left: 20px;
+ height: calc(100% + #{$gl-spacing-scale-5});
+ top: -#{$gl-spacing-scale-5};
+ }
+
+ &:first-child::before {
+ content: none;
+ }
+
+ &:first-child {
+ &::after {
+ top: $gl-spacing-scale-5;
+ height: calc(100% + #{$gl-spacing-scale-5});
+ }
+ }
+
+ &:last-child,
+ &.create-timeline-event {
+ &::before {
+ top: - #{$gl-spacing-scale-5} !important; // Override default positioning
+ @include gl-h-8;
+ }
+
+ &::after {
+ content: none;
+ }
+ }
+}
+
+.timeline-entry:not(:last-child) {
+ .timeline-event-border {
+ @include gl-pb-5;
+ @include gl-border-gray-50;
+ @include gl-border-1;
+ @include gl-border-b-solid;
+ }
+}
+
+.timeline-group:last-child {
+ .timeline-entry:last-child,
+ .create-timeline-event {
+ .timeline-event-bottom-border {
+ @include gl-border-b;
+ @include gl-pt-5;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/page_bundles/prometheus.scss
index 71cbd7d9613..055036d757d 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/page_bundles/prometheus.scss
@@ -1,3 +1,5 @@
+@import 'mixins_and_variables_and_functions';
+
.prometheus-graphs {
.dropdown-buttons {
> div {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e77e30c532e..6070311dcb6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -856,24 +856,6 @@
}
}
-.issuable-todo-btn {
- .gl-spinner {
- display: none;
- }
-
- &.is-loading {
- .gl-spinner {
- display: inline-block;
- }
-
- &.sidebar-collapsed-icon {
- .issuable-todo-inner {
- display: none;
- }
- }
- }
-}
-
/*
* Following overrides are done to prevent
* legacy dropdown styles from influencing
@@ -932,85 +914,3 @@
}
}
}
-
-.icon-overlap-and-shadow {
- filter:
- drop-shadow(0 1px 0.5px #fff)
- drop-shadow(1px 0 0.5px #fff)
- drop-shadow(0 -1px 0.5px #fff)
- drop-shadow(-1px 0 0.5px #fff);
- margin-right: -7px;
- z-index: 1;
-}
-
-.issuable-discussion.incident-timeline-events {
- .main-notes-list::before {
- content: none;
- }
-
- .timeline-event-note {
- p {
- margin-bottom: 0;
- font-size: 0.875rem;
- }
- }
-}
-
-/**
- * We have a very specific design proposal where we cannot
- * use `vertical-line` mixin as it is and have to use
- * custom styles, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81284#note_904867444
- */
-.timeline-entry-vertical-line {
- &::before,
- &::after {
- content: '';
- border-left: 2px solid $gray-50;
- position: absolute;
- left: 20px;
- height: calc(100% + #{$gl-spacing-scale-5});
- top: -#{$gl-spacing-scale-5};
- }
-
- &:first-child::before {
- content: none;
- }
-
- &:first-child {
- &::after {
- top: $gl-spacing-scale-5;
- height: calc(100% + #{$gl-spacing-scale-5});
- }
- }
-
- &:last-child,
- &.create-timeline-event {
- &::before {
- top: - #{$gl-spacing-scale-5} !important; // Override default positioning
- @include gl-h-8;
- }
-
- &::after {
- content: none;
- }
- }
-}
-
-.timeline-entry:not(:last-child) {
- .timeline-event-border {
- @include gl-pb-5;
- @include gl-border-gray-50;
- @include gl-border-1;
- @include gl-border-b-solid;
- }
-}
-
-.timeline-group:last-child {
- .timeline-entry:last-child,
- .create-timeline-event {
- .timeline-event-bottom-border {
- @include gl-border-b;
- @include gl-pt-5;
- }
- }
-}
diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb
index d00301678dd..99a92ba9b59 100644
--- a/app/helpers/ci/pipeline_editor_helper.rb
+++ b/app/helpers/ci/pipeline_editor_helper.rb
@@ -11,7 +11,6 @@ module Ci
def js_pipeline_editor_data(project)
initial_branch = params[:branch_name]
latest_commit = project.repository.commit(initial_branch) || project.commit
- commit_sha = latest_commit ? latest_commit.sha : ''
total_branches = project.repository_exists? ? project.repository.branch_count : 0
{
@@ -27,17 +26,26 @@ module Ci
"lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'),
"needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
"new-merge-request-path" => namespace_project_new_merge_request_path,
- "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
+ "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(latest_commit.sha) : '',
"pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path,
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
"simulate-pipeline-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'simulate-a-cicd-pipeline'),
"total-branches" => total_branches,
+ "uses-external-config" => uses_external_config?(project) ? 'true' : 'false',
"validate-tab-illustration-path" => image_path('illustrations/project-run-CICD-pipelines-sm.svg'),
"yml-help-page-path" => help_page_path('ci/yaml/index')
}
end
+
+ private
+
+ def uses_external_config?(project)
+ ci_config_source = Gitlab::Ci::ProjectConfig.new(project: project, sha: nil).source
+
+ [:external_project_source, :remote_source].include?(ci_config_source)
+ end
end
end
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index a67771116b9..c93c8dd8d76 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -69,7 +69,8 @@ module Ci
end
def has_pipeline_badges?(pipeline)
- pipeline.child? ||
+ pipeline.schedule? ||
+ pipeline.child? ||
pipeline.latest? ||
pipeline.merge_train_pipeline? ||
pipeline.has_yaml_errors? ||
diff --git a/app/models/project.rb b/app/models/project.rb
index aca61f645da..3d026351b4f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -477,6 +477,7 @@ class Project < ApplicationRecord
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :forward_deployment_enabled, :forward_deployment_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
+ delegate :inbound_job_token_scope_enabled, :inbound_job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :opt_in_jwt, :opt_in_jwt=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :allow_fork_pipelines_to_run_in_parent_project, :allow_fork_pipelines_to_run_in_parent_project=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
@@ -2720,6 +2721,7 @@ class Project < ApplicationRecord
ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci]
end
+ # DO NOT USE. This method will be deprecated soon
def uses_external_project_ci_config?
!!(ci_config_path =~ %r{@.+/.+})
end
@@ -2844,6 +2846,7 @@ class Project < ApplicationRecord
repository.gitlab_ci_yml_for(sha, ci_config_path_or_default)
end
+ # DO NOT USE. This method will be deprecated soon
def ci_config_external_project
Project.find_by_full_path(ci_config_path.split('@', 2).last)
end
@@ -2892,6 +2895,12 @@ class Project < ApplicationRecord
ci_cd_settings.job_token_scope_enabled?
end
+ def ci_inbound_job_token_scope_enabled?
+ return false unless ci_cd_settings
+
+ ci_cd_settings.inbound_job_token_scope_enabled?
+ end
+
def restrict_user_defined_variables?
return false unless ci_cd_settings
diff --git a/app/models/user.rb b/app/models/user.rb
index 9fa9419cd37..d64a52ff7b9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -235,6 +235,7 @@ class User < ApplicationRecord
has_one :user_highest_role
has_one :user_canonical_email
has_one :credit_card_validation, class_name: '::Users::CreditCardValidation'
+ has_one :phone_number_validation, class_name: '::Users::PhoneNumberValidation'
has_one :atlassian_identity, class_name: 'Atlassian::Identity'
has_one :banned_user, class_name: '::Users::BannedUser'
diff --git a/app/models/users/phone_number_validation.rb b/app/models/users/phone_number_validation.rb
new file mode 100644
index 00000000000..f6123c01fd0
--- /dev/null
+++ b/app/models/users/phone_number_validation.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Users
+ class PhoneNumberValidation < ApplicationRecord
+ self.primary_key = :user_id
+ self.table_name = 'user_phone_number_validations'
+
+ belongs_to :user, foreign_key: :user_id
+ belongs_to :banned_user, class_name: '::Users::BannedUser', foreign_key: :user_id
+
+ validates :country,
+ presence: true,
+ length: { maximum: 3 }
+
+ validates :international_dial_code,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 1,
+ less_than_or_equal_to: 999
+ }
+
+ validates :phone_number,
+ presence: true,
+ format: {
+ with: /\A\d+\Z/,
+ message: -> (object, data) { _('can contain only digits') }
+ },
+ length: { maximum: 12 }
+
+ validates :telesign_reference_xid,
+ length: { maximum: 255 }
+
+ def self.related_to_banned_user?(international_dial_code, phone_number)
+ joins(:banned_user).where(
+ international_dial_code: international_dial_code,
+ phone_number: phone_number
+ ).exists?
+ end
+ end
+end
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
index fac79a8194a..afdac86d9c7 100644
--- a/app/models/wiki.rb
+++ b/app/models/wiki.rb
@@ -229,7 +229,7 @@ class Wiki
find_page(SIDEBAR, version)
end
- def find_file(name, version = 'HEAD', load_content: true)
+ def find_file(name, version = default_branch, load_content: true)
data_limit = load_content ? -1 : 0
blobs = repository.blobs_at([[version, name]], blob_size_limit: data_limit)
@@ -423,7 +423,7 @@ class Wiki
escaped_title = Regexp.escape(sluggified_title(title))
regex = Regexp.new("^#{escaped_title}\.#{ALLOWED_EXTENSIONS_REGEX}$", 'i')
- repository.ls_files('HEAD').any? { |s| s =~ regex }
+ repository.ls_files(default_branch).any? { |s| s =~ regex }
end
def raise_duplicate_page_error!
@@ -473,11 +473,11 @@ class Wiki
end
def check_page_historical(path, commit)
- repository.last_commit_for_path('HEAD', path).id != commit.id
+ repository.last_commit_for_path(default_branch, path)&.id != commit&.id
end
def find_page_with_repository_rpcs(title, version, load_content: true)
- version = version.presence || 'HEAD'
+ version = version.presence || default_branch
path = find_matched_file(title, version)
return if path.blank?
@@ -493,7 +493,7 @@ class Wiki
path: sluggified_title(path),
raw_data: blob.data,
name: canonicalize_filename(path),
- historical: version == 'HEAD' ? false : check_page_historical(path, commit),
+ historical: version == default_branch ? false : check_page_historical(path, commit),
version: Gitlab::Git::WikiPageVersion.new(commit, format)
)
WikiPage.new(self, page)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 63c60f5a89e..5cdd1fdadcb 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -138,7 +138,7 @@ class WikiPage
default_per_page = Kaminari.config.default_per_page
offset = [options[:page].to_i - 1, 0].max * options.fetch(:per_page, default_per_page)
- wiki.repository.commits('HEAD',
+ wiki.repository.commits(wiki.default_branch,
path: page.path,
limit: options.fetch(:limit, default_per_page),
offset: offset)
@@ -147,7 +147,7 @@ class WikiPage
def count_versions
return [] unless persisted?
- wiki.wiki.count_page_versions(page.path)
+ wiki.repository.count_commits(ref: wiki.default_branch, path: page.path)
end
def last_version
diff --git a/app/uploaders/object_storage/cdn/google_cdn.rb b/app/uploaders/object_storage/cdn/google_cdn.rb
index ea7683f131c..d34ed8ac897 100644
--- a/app/uploaders/object_storage/cdn/google_cdn.rb
+++ b/app/uploaders/object_storage/cdn/google_cdn.rb
@@ -41,7 +41,7 @@ module ObjectStorage
private
def config_valid?
- [key_name, decoded_key, cdn_url].all?(&:present?)
+ [key_name, decoded_key, cdn_url].all?(&:present?) && cdn_url.start_with?('https://')
end
def key_name
diff --git a/app/views/clusters/clusters/_health.html.haml b/app/views/clusters/clusters/_health.html.haml
index 75609465eb3..50facdf91a2 100644
--- a/app/views/clusters/clusters/_health.html.haml
+++ b/app/views/clusters/clusters/_health.html.haml
@@ -1,3 +1,5 @@
+- add_page_specific_style 'page_bundles/prometheus'
+
%section.settings.no-animate.expanded.cluster-health-graphs.qa-cluster-health-section#cluster-health
- if @cluster&.integration_prometheus_available?
#prometheus-graphs{ data: @cluster.health_data(clusterable) }
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 4139be053f8..9fd542e0cfb 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -10,7 +10,7 @@
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
- = render_if_exists 'projects/blob/owners', blob: blob
+ #js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref } }
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
diff --git a/app/views/projects/branch_rules/_show.html.haml b/app/views/projects/branch_rules/_show.html.haml
index 46665fdb450..27525b441ab 100644
--- a/app/views/projects/branch_rules/_show.html.haml
+++ b/app/views/projects/branch_rules/_show.html.haml
@@ -9,4 +9,4 @@
= _('Define rules for who can push, merge, and the required approvals for each branch.')
.settings-content.gl-pr-0
- #js-branch-rules{ data: { project_path: @project.full_path } }
+ #js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project) } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index cd7339edd1a..31041d124e4 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -1,3 +1,5 @@
+- add_page_specific_style 'page_bundles/prometheus'
+
- page_title _("Metrics Dashboard"), @environment.name
.prometheus-container
diff --git a/app/views/projects/incidents/show.html.haml b/app/views/projects/incidents/show.html.haml
index 5043f94bd5c..7a1e7f503f8 100644
--- a/app/views/projects/incidents/show.html.haml
+++ b/app/views/projects/incidents/show.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Incidents"), project_incidents_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Incidents")
+- add_page_specific_style 'page_bundles/incidents'
- add_page_specific_style 'page_bundles/issues_show'
= render 'projects/issuable/show', issuable: @issue
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 06c422fc4d6..76b725d140c 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
+- add_page_specific_style 'page_bundles/incidents'
- add_page_specific_style 'page_bundles/issues_show'
- add_page_specific_style 'page_bundles/work_items'
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 28b433b2514..4b549aaf1cd 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -3,12 +3,10 @@
.form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
- .select-wrapper
- = f.select :auth_method,
- options_for_select(auth_options, mirror.auth_method),
- {}, { class: "form-control gl-form-select select-control js-mirror-auth-type qa-authentication-method" }
- = sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
- = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
+ = f.select :auth_method,
+ options_for_select(auth_options, mirror.auth_method),
+ {}, { class: "custom-select gl-form-select js-mirror-auth-type qa-authentication-method" }
+ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group
.well-password-auth.collapse.js-well-password-auth
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 5db898067db..2e403358e2e 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -22,6 +22,8 @@
.well-segment.qa-pipeline-badges
.icon-container
= sprite_icon('flag', css_class: 'gl-top-0!')
+ - if @pipeline.schedule?
+ = gl_badge_tag _('Scheduled'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-scheduled', title: _('This pipeline was triggered by a schedule.') }
- if @pipeline.child?
- text = sprintf(s_('Pipelines|Child pipeline (%{link_start}parent%{link_end})'), { link_start: "<a href='#{pipeline_path(@pipeline.triggered_by_pipeline)}' class='text-underline'>", link_end: "</a>"}).html_safe
= gl_badge_tag text, { variant: :info, size: :sm }, { class: 'js-pipeline-child has-tooltip', title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
diff --git a/app/views/projects/settings/branch_rules/index.html.haml b/app/views/projects/settings/branch_rules/index.html.haml
index 84c060701bb..ab692a23e44 100644
--- a/app/views/projects/settings/branch_rules/index.html.haml
+++ b/app/views/projects/settings/branch_rules/index.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs _('Repository Settings'), project_settings_repository_path(@project)
- page_title s_('BranchRules|Branch rules details')
-%h3= s_('BranchRules|Branch rules details')
+%h3.gl-mb-5= s_('BranchRules|Branch rules details')
-#js-branch-rules{ data: { project_path: @project.full_path } }
+#js-branch-rules{ data: { project_path: @project.full_path, protected_branches_path: project_settings_repository_path(@project, anchor: 'js-protected-branches-settings') } }