diff options
Diffstat (limited to 'app/assets/javascripts/projects/settings')
9 files changed, 127 insertions, 35 deletions
diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 335545c802a..dcf7415a444 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -580,7 +580,7 @@ export default class AccessDropdown { return ` <li> <a href="#" class="${isActiveClass} item-${role.type}" data-role-id="${role.id}"> - ${role.text} + ${escape(role.text)} </a> </li> `; 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 index 6da058ebc9c..61c37a2348a 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js @@ -6,6 +6,7 @@ export const I18N = { branchNameOrPattern: s__('BranchRules|Branch name or pattern'), branch: s__('BranchRules|Target Branch'), allBranches: s__('BranchRules|All branches'), + matchingBranchesLinkTitle: s__('BranchRules|%{total} matching %{subject}'), 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}', 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 eb11e17dd1b..626ed67c466 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,9 +1,10 @@ <script> import { GlSprintf, GlLink, GlLoadingIcon } from '@gitlab/ui'; -import { sprintf } from '~/locale'; -import { getParameterByName } from '~/lib/utils/url_utility'; +import { sprintf, n__ } from '~/locale'; +import { getParameterByName, mergeUrlParams } from '~/lib/utils/url_utility'; import { helpPagePath } from '~/helpers/help_page_helper'; import branchRulesQuery from '../../queries/branch_rules_details.query.graphql'; +import { getAccessLevels } from '../../../utils'; import Protection from './protection.vue'; import { I18N, @@ -41,6 +42,9 @@ export default { statusChecksPath: { default: '', }, + branchesPath: { + default: '', + }, }, apollo: { project: { @@ -55,6 +59,7 @@ export default { this.branchProtection = branchRule?.branchProtection; this.approvalRules = branchRule?.approvalRules; this.statusChecks = branchRule?.externalStatusChecks?.nodes || []; + this.matchingBranchesCount = branchRule?.matchingBranchesCount; }, }, }, @@ -64,6 +69,7 @@ export default { branchProtection: {}, approvalRules: {}, statusChecks: [], + matchingBranchesCount: null, }; }, computed: { @@ -115,28 +121,20 @@ export default { ? this.$options.i18n.targetBranch : this.$options.i18n.branchNameOrPattern; }, + matchingBranchesLinkHref() { + return mergeUrlParams({ state: 'all', search: this.branch }, this.branchesPath); + }, + matchingBranchesLinkTitle() { + const total = this.matchingBranchesCount; + const subject = n__('branch', 'branches', total); + return sprintf(this.$options.i18n.matchingBranchesLinkTitle, { total, subject }); + }, approvals() { return this.approvalRules?.nodes || []; }, }, 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; - }, + getAccessLevels, }, }; </script> @@ -161,6 +159,10 @@ export default { </div> <code v-else class="gl-mt-2" data-testid="branch">{{ branch }}</code> + <p v-if="matchingBranchesCount" class="gl-mt-3"> + <gl-link :href="matchingBranchesLinkHref">{{ matchingBranchesLinkTitle }}</gl-link> + </p> + <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.protectBranchTitle }}</h4> <gl-sprintf :message="$options.i18n.protectBranchDescription"> <template #link="{ content }"> 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 89cfb1e1c8e..7639acc1181 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 @@ -14,7 +14,13 @@ export default function mountBranchRules(el) { defaultClient: createDefaultClient(), }); - const { projectPath, protectedBranchesPath, approvalRulesPath, statusChecksPath } = el.dataset; + const { + projectPath, + protectedBranchesPath, + approvalRulesPath, + statusChecksPath, + branchesPath, + } = el.dataset; return new Vue({ el, @@ -24,6 +30,7 @@ export default function mountBranchRules(el) { protectedBranchesPath, approvalRulesPath, statusChecksPath, + branchesPath, }, render(h) { 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 index aa1e4923aa8..a832e59aa67 100644 --- 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 @@ -68,6 +68,7 @@ query getBranchRulesDetails($projectPath: ID!) { externalUrl } } + matchingBranchesCount } } } 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 a9eb2a53fbf..9b669024a8b 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue @@ -1,7 +1,7 @@ <script> import { s__ } from '~/locale'; import { createAlert } from '~/flash'; -import branchRulesQuery from './graphql/queries/branch_rules.query.graphql'; +import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql'; import BranchRule from './components/branch_rule.vue'; export const i18n = { @@ -51,13 +51,14 @@ export default { <template> <div class="settings-content"> <branch-rule - v-for="rule in branchRules" - :key="rule.name" + v-for="(rule, index) in branchRules" + :key="`${rule.name}-${index}`" :name="rule.name" :is-default="rule.isDefault" :branch-protection="rule.branchProtection" - :status-checks-total="rule.externalStatusChecks.nodes.length" - :approval-rules-total="rule.approvalRules.nodes.length" + :status-checks-total="rule.externalStatusChecks ? rule.externalStatusChecks.nodes.length : 0" + :approval-rules-total="rule.approvalRules ? rule.approvalRules.nodes.length : 0" + :matching-branches-count="rule.matchingBranchesCount" /> <span v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</span> 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 78c824c66d1..41947834bdb 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,6 +1,7 @@ <script> import { GlBadge, GlButton } from '@gitlab/ui'; import { s__, sprintf, n__ } from '~/locale'; +import { getAccessLevels } from '../../../utils'; export const i18n = { defaultLabel: s__('BranchRules|default'), @@ -9,6 +10,9 @@ export const i18n = { codeOwnerApprovalRequired: s__('BranchRules|Requires CODEOWNERS approval'), statusChecks: s__('BranchRules|%{total} status %{subject}'), approvalRules: s__('BranchRules|%{total} approval %{subject}'), + matchingBranches: s__('BranchRules|%{total} matching %{subject}'), + pushAccessLevels: s__('BranchRules|Allowed to merge'), + mergeAccessLevels: s__('BranchRules|Allowed to push'), }; export default { @@ -48,8 +52,16 @@ export default { required: false, default: 0, }, + matchingBranchesCount: { + type: Number, + required: false, + default: 0, + }, }, computed: { + isWildcard() { + return this.name.includes('*'); + }, hasApprovalDetails() { return this.approvalDetails.length; }, @@ -68,8 +80,31 @@ export default { subject: n__('rule', 'rules', this.approvalRulesTotal), }); }, + matchingBranchesText() { + return sprintf(this.$options.i18n.matchingBranches, { + total: this.matchingBranchesCount, + subject: n__('branch', 'branches', this.matchingBranchesCount), + }); + }, + mergeAccessLevels() { + const { mergeAccessLevels } = this.branchProtection || {}; + return this.getAccessLevels(mergeAccessLevels); + }, + pushAccessLevels() { + const { pushAccessLevels } = this.branchProtection || {}; + return this.getAccessLevels(pushAccessLevels); + }, + pushAccessLevelsText() { + return this.getAccessLevelsText(this.$options.i18n.pushAccessLevels, this.pushAccessLevels); + }, + mergeAccessLevelsText() { + return this.getAccessLevelsText(this.$options.i18n.mergeAccessLevels, this.mergeAccessLevels); + }, approvalDetails() { const approvalDetails = []; + if (this.isWildcard) { + approvalDetails.push(this.matchingBranchesText); + } if (this.branchProtection.allowForcePush) { approvalDetails.push(this.$options.i18n.allowForcePush); } @@ -82,9 +117,31 @@ export default { if (this.approvalRulesTotal) { approvalDetails.push(this.approvalRulesText); } + if (this.mergeAccessLevels.total > 0) { + approvalDetails.push(this.mergeAccessLevelsText); + } + if (this.pushAccessLevels.total > 0) { + approvalDetails.push(this.pushAccessLevelsText); + } return approvalDetails; }, }, + methods: { + getAccessLevels, + getAccessLevelsText(beginString = '', accessLevels) { + const textParts = []; + if (accessLevels.roles.length) { + textParts.push(n__('1 role', '%d roles', accessLevels.roles.length)); + } + if (accessLevels.groups.length) { + textParts.push(n__('1 group', '%d groups', accessLevels.groups.length)); + } + if (accessLevels.users.length) { + textParts.push(n__('1 user', '%d users', accessLevels.users.length)); + } + return `${beginString}: ${textParts.join(', ')}`; + }, + }, }; </script> diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql b/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql index 49e089e7805..a8cdda5505f 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql @@ -5,18 +5,24 @@ query getBranchRules($projectPath: ID!) { nodes { name isDefault + matchingBranchesCount branchProtection { allowForcePush - codeOwnerApprovalRequired - } - externalStatusChecks { - nodes { - id + mergeAccessLevels { + edges { + node { + accessLevel + accessLevelDescription + } + } } - } - approvalRules { - nodes { - id + pushAccessLevels { + edges { + node { + accessLevel + accessLevelDescription + } + } } } } diff --git a/app/assets/javascripts/projects/settings/utils.js b/app/assets/javascripts/projects/settings/utils.js new file mode 100644 index 00000000000..7bcfde39178 --- /dev/null +++ b/app/assets/javascripts/projects/settings/utils.js @@ -0,0 +1,17 @@ +export const 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; +}; |