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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 16:49:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 16:49:51 +0300
commit71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch)
tree6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /app/assets/javascripts/projects
parenta7253423e3403b8c08f8a161e5937e1488f5f407 (diff)
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/commit/components/branches_dropdown.vue84
-rw-r--r--app/assets/javascripts/projects/commit/components/form_modal.vue13
-rw-r--r--app/assets/javascripts/projects/commit/components/projects_dropdown.vue57
-rw-r--r--app/assets/javascripts/projects/commit/store/getters.js4
-rw-r--r--app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue4
-rw-r--r--app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql4
-rw-r--r--app/assets/javascripts/projects/merge_requests/index.js18
-rw-r--r--app/assets/javascripts/projects/project_name_rules.js29
-rw-r--r--app/assets/javascripts/projects/project_new.js9
-rw-r--r--app/assets/javascripts/projects/project_visibility.js15
-rw-r--r--app/assets/javascripts/projects/prune_objects_button.js23
-rw-r--r--app/assets/javascripts/projects/prune_unreachable_objects_button.vue75
-rw-r--r--app/assets/javascripts/projects/report_abuse/components/report_abuse_dropdown_item.vue (renamed from app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue)17
-rw-r--r--app/assets/javascripts/projects/report_abuse/index.js25
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/edit/protections/push_protections.vue2
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js12
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue105
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js2
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql47
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/app.vue53
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue4
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/constants.js22
-rw-r--r--app/assets/javascripts/projects/settings/utils.js2
23 files changed, 329 insertions, 297 deletions
diff --git a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
index a037e721677..0ed154c47dd 100644
--- a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
+++ b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
@@ -1,12 +1,7 @@
<script>
-import {
- GlDropdown,
- GlSearchBoxByType,
- GlDropdownItem,
- GlDropdownText,
- GlLoadingIcon,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
+import { debounce } from 'lodash';
import {
I18N_NO_RESULTS_MESSAGE,
I18N_BRANCH_HEADER,
@@ -16,11 +11,7 @@ import {
export default {
name: 'BranchesDropdown',
components: {
- GlDropdown,
- GlSearchBoxByType,
- GlDropdownItem,
- GlDropdownText,
- GlLoadingIcon,
+ GlCollapsibleListbox,
},
props: {
value: {
@@ -46,19 +37,17 @@ export default {
},
computed: {
...mapGetters(['joinedBranches']),
- ...mapState(['isFetching', 'branch', 'branches']),
- filteredResults() {
- const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
- return this.joinedBranches.filter((resultString) =>
- resultString.toLowerCase().includes(lowerCasedSearchTerm),
- );
+ ...mapState(['isFetching']),
+ listboxItems() {
+ return this.joinedBranches.map((value) => ({ value, text: value }));
},
},
watch: {
// Parent component can set the branch value (e.g. when the user selects a different project)
// and we need to keep the search term in sync with the selected value
value(val) {
- this.searchTermChanged(val);
+ this.searchTerm = val;
+ this.fetchBranches(this.searchTerm);
},
},
mounted() {
@@ -67,50 +56,29 @@ export default {
methods: {
...mapActions(['fetchBranches']),
selectBranch(branch) {
- this.$emit('selectBranch', branch);
- this.searchTerm = branch; // enables isSelected to work as expected
- },
- isSelected(selectedBranch) {
- return selectedBranch === this.branch;
+ this.$emit('input', branch);
},
+ debouncedSearch: debounce(function debouncedSearch() {
+ this.fetchBranches(this.searchTerm);
+ }, 250),
searchTermChanged(value) {
- this.searchTerm = value;
- this.fetchBranches(value);
+ this.searchTerm = value.trim();
+ this.debouncedSearch(value);
},
},
};
</script>
<template>
- <gl-dropdown :text="value" :header-text="$options.i18n.branchHeaderTitle">
- <gl-search-box-by-type
- :value="searchTerm"
- trim
- autocomplete="off"
- :debounce="250"
- :placeholder="$options.i18n.branchSearchPlaceholder"
- data-testid="dropdown-search-box"
- @input="searchTermChanged"
- />
- <gl-dropdown-item
- v-for="branch in filteredResults"
- v-show="!isFetching"
- :key="branch"
- :name="branch"
- :is-checked="isSelected(branch)"
- is-check-item
- data-testid="dropdown-item"
- @click="selectBranch(branch)"
- >
- {{ branch }}
- </gl-dropdown-item>
- <gl-dropdown-text v-show="isFetching" data-testid="dropdown-text-loading-icon">
- <gl-loading-icon size="sm" class="gl-mx-auto" />
- </gl-dropdown-text>
- <gl-dropdown-text
- v-if="!filteredResults.length && !isFetching"
- data-testid="empty-result-message"
- >
- <span class="gl-text-gray-500">{{ $options.i18n.noResultsMessage }}</span>
- </gl-dropdown-text>
- </gl-dropdown>
+ <gl-collapsible-listbox
+ :header-text="$options.i18n.branchHeaderTitle"
+ :toggle-text="value"
+ :items="listboxItems"
+ searchable
+ :search-placeholder="$options.i18n.branchSearchPlaceholder"
+ :searching="isFetching"
+ :selected="value"
+ :no-results-text="$options.i18n.noResultsMessage"
+ @search="searchTermChanged"
+ @select="selectBranch"
+ />
</template>
diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue
index 1febe8ceaab..f78afef1c17 100644
--- a/app/assets/javascripts/projects/commit/components/form_modal.vue
+++ b/app/assets/javascripts/projects/commit/components/form_modal.vue
@@ -141,11 +141,7 @@ export default {
:value="targetProjectId"
/>
- <projects-dropdown
- class="gl-w-half"
- :value="targetProjectName"
- @selectProject="setSelectedProject"
- />
+ <projects-dropdown :value="targetProjectName" @selectProject="setSelectedProject" />
</gl-form-group>
<gl-form-group
@@ -155,12 +151,7 @@ export default {
>
<input id="start_branch" type="hidden" name="start_branch" :value="branch" />
- <branches-dropdown
- class="gl-w-half"
- :value="branch"
- :blanked="isRevert"
- @selectBranch="setBranch"
- />
+ <branches-dropdown :value="branch" :blanked="isRevert" @input="setBranch" />
</gl-form-group>
<gl-form-checkbox
diff --git a/app/assets/javascripts/projects/commit/components/projects_dropdown.vue b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue
index 6288bcdaad0..d43f5b99e2c 100644
--- a/app/assets/javascripts/projects/commit/components/projects_dropdown.vue
+++ b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue
@@ -1,5 +1,5 @@
<script>
-import { GlDropdown, GlSearchBoxByType, GlDropdownItem, GlDropdownText } from '@gitlab/ui';
+import { GlCollapsibleListbox } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import {
I18N_NO_RESULTS_MESSAGE,
@@ -10,10 +10,7 @@ import {
export default {
name: 'ProjectsDropdown',
components: {
- GlDropdown,
- GlSearchBoxByType,
- GlDropdownItem,
- GlDropdownText,
+ GlCollapsibleListbox,
},
props: {
value: {
@@ -41,17 +38,20 @@ export default {
project.name.toLowerCase().includes(lowerCasedFilterTerm),
);
},
+ listboxItems() {
+ return this.filteredResults.map(({ id, name }) => ({ value: id, text: name }));
+ },
selectedProject() {
return this.sortedProjects.find((project) => project.id === this.targetProjectId) || {};
},
},
methods: {
- selectProject(project) {
- this.$emit('selectProject', project.id);
- this.filterTerm = project.name; // when we select a project, we want the dropdown to filter to the selected project
- },
- isSelected(selectedProject) {
- return selectedProject === this.selectedProject;
+ selectProject(value) {
+ this.$emit('selectProject', value);
+
+ // when we select a project, we want the dropdown to filter to the selected project
+ const project = this.listboxItems.find((x) => x.value === value);
+ this.filterTerm = project?.text || '';
},
filterTermChanged(value) {
this.filterTerm = value;
@@ -60,28 +60,15 @@ export default {
};
</script>
<template>
- <gl-dropdown :text="selectedProject.name" :header-text="$options.i18n.projectHeaderTitle">
- <gl-search-box-by-type
- :value="filterTerm"
- trim
- autocomplete="off"
- :placeholder="$options.i18n.projectSearchPlaceholder"
- data-testid="dropdown-search-box"
- @input="filterTermChanged"
- />
- <gl-dropdown-item
- v-for="project in filteredResults"
- :key="project.name"
- :name="project.name"
- :is-checked="isSelected(project)"
- is-check-item
- data-testid="dropdown-item"
- @click="selectProject(project)"
- >
- {{ project.name }}
- </gl-dropdown-item>
- <gl-dropdown-text v-if="!filteredResults.length" data-testid="empty-result-message">
- <span class="gl-text-gray-500">{{ $options.i18n.noResultsMessage }}</span>
- </gl-dropdown-text>
- </gl-dropdown>
+ <gl-collapsible-listbox
+ :header-text="$options.i18n.projectHeaderTitle"
+ :items="listboxItems"
+ searchable
+ :search-placeholder="$options.i18n.projectSearchPlaceholder"
+ :selected="selectedProject.id"
+ :toggle-text="selectedProject.name"
+ :no-results-text="$options.i18n.noResultsMessage"
+ @search="filterTermChanged"
+ @select="selectProject"
+ />
</template>
diff --git a/app/assets/javascripts/projects/commit/store/getters.js b/app/assets/javascripts/projects/commit/store/getters.js
index e0c36df8a75..b039ee3ba63 100644
--- a/app/assets/javascripts/projects/commit/store/getters.js
+++ b/app/assets/javascripts/projects/commit/store/getters.js
@@ -1,7 +1,7 @@
-import { uniq } from 'lodash';
+import { uniq, uniqBy } from 'lodash';
export const joinedBranches = (state) => {
return uniq(state.branches).sort();
};
-export const sortedProjects = (state) => uniq(state.projects).sort();
+export const sortedProjects = (state) => uniqBy(state.projects, 'id').sort();
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
index 0256eec6d56..dafc4bc5abf 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
@@ -6,6 +6,7 @@ import {
getQueryHeaders,
toggleQueryPollingByVisibility,
} from '~/pipelines/components/graph/utils';
+import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import { formatStages } from '../utils';
import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql';
@@ -91,7 +92,8 @@ export default {
},
computed: {
downstreamPipelines() {
- return this.pipeline?.downstream?.nodes;
+ const downstream = this.pipeline?.downstream?.nodes;
+ return keepLatestDownstreamPipelines(downstream);
},
pipelinePath() {
return this.pipeline?.path ?? '';
diff --git a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql
index c6a0d48626a..9257cc7de7b 100644
--- a/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql
+++ b/app/assets/javascripts/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql
@@ -18,6 +18,10 @@ query getLinkedPipelines($fullPath: ID!, $iid: ID!) {
icon
label
}
+ sourceJob {
+ id
+ retried
+ }
}
}
upstream {
diff --git a/app/assets/javascripts/projects/merge_requests/index.js b/app/assets/javascripts/projects/merge_requests/index.js
deleted file mode 100644
index 25a70121d68..00000000000
--- a/app/assets/javascripts/projects/merge_requests/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Vue from 'vue';
-import ReportAbuseDropdownItem from './components/report_abuse_dropdown_item.vue';
-
-export const initReportAbuse = () => {
- const el = document.getElementById('js-report-abuse-dropdown-item');
-
- if (!el) return false;
-
- const { reportAbusePath, reportedUserId, reportedFromUrl } = el.dataset;
-
- return new Vue({
- el,
- provide: { reportAbusePath, reportedUserId, reportedFromUrl },
- render(createElement) {
- return createElement(ReportAbuseDropdownItem);
- },
- });
-};
diff --git a/app/assets/javascripts/projects/project_name_rules.js b/app/assets/javascripts/projects/project_name_rules.js
index eeef1fb5afc..4f62aa29ce4 100644
--- a/app/assets/javascripts/projects/project_name_rules.js
+++ b/app/assets/javascripts/projects/project_name_rules.js
@@ -1,28 +1,29 @@
import { __ } from '~/locale';
-const rulesReg = [
- {
- reg: /^[a-zA-Z0-9\u{00A9}-\u{1f9ff}_]/u,
- msg: __("Name must start with a letter, digit, emoji, or '_'"),
- },
- {
- reg: /^[a-zA-Z0-9\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_. ]+$/u,
- msg: __("Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces"),
- },
-];
+export const START_RULE = {
+ reg: /^[a-zA-Z0-9\u{00A9}-\u{1f9ff}_]/u,
+ msg: __('Name must start with a letter, digit, emoji, or underscore.'),
+};
+
+export const CONTAINS_RULE = {
+ reg: /^[a-zA-Z0-9\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_. ]+$/u,
+ msg: __(
+ 'Name can contain only lowercase or uppercase letters, digits, emojis, spaces, dots, underscores, dashes, or pluses.',
+ ),
+};
+
+const rulesReg = [START_RULE, CONTAINS_RULE];
/**
*
* @param {string} text
* @returns {string} msg
*/
-function checkRules(text) {
+export const checkRules = (text) => {
for (const item of rulesReg) {
if (!item.reg.test(text)) {
return item.msg;
}
}
return '';
-}
-
-export { checkRules };
+};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index d71e80dffcf..99ea02aaa4f 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -90,13 +90,16 @@ const validateGroupNamespaceDropdown = (e) => {
const checkProjectName = (projectNameInput) => {
const msg = checkRules(projectNameInput.value);
- const projectNameError = document.querySelector('#project_name_error');
+ const projectNameError = document.querySelector('#js-project-name-error');
+ const projectNameDescription = document.getElementById('js-project-name-description');
if (!projectNameError) return;
if (msg) {
projectNameError.innerText = msg;
- projectNameError.classList.remove('hidden');
+ projectNameError.classList.remove('gl-display-none');
+ projectNameDescription.classList.add('gl-display-none');
} else {
- projectNameError.classList.add('hidden');
+ projectNameError.classList.add('gl-display-none');
+ projectNameDescription.classList.remove('gl-display-none');
}
};
diff --git a/app/assets/javascripts/projects/project_visibility.js b/app/assets/javascripts/projects/project_visibility.js
index 84b8936c17f..2dd5f821d90 100644
--- a/app/assets/javascripts/projects/project_visibility.js
+++ b/app/assets/javascripts/projects/project_visibility.js
@@ -44,21 +44,6 @@ function setVisibilityOptions({ name, visibility, showPath, editPath }) {
});
}
-function handleSelect2DropdownChange(namespaceSelector) {
- if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
- return;
- }
- const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
- setVisibilityOptions(selectedNamespace.dataset);
-}
-
export default function initProjectVisibilitySelector() {
eventHub.$on('update-visibility', setVisibilityOptions);
-
- const namespaceSelector = document.querySelector('select.js-select-namespace');
- if (namespaceSelector) {
- const el = document.querySelector('.select2.js-select-namespace');
- el.addEventListener('change', () => handleSelect2DropdownChange(namespaceSelector));
- handleSelect2DropdownChange(namespaceSelector);
- }
}
diff --git a/app/assets/javascripts/projects/prune_objects_button.js b/app/assets/javascripts/projects/prune_objects_button.js
new file mode 100644
index 00000000000..dba73f6a19d
--- /dev/null
+++ b/app/assets/javascripts/projects/prune_objects_button.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import PruneUnreachableObjectsButton from './prune_unreachable_objects_button.vue';
+
+export default (selector = '#js-project-prune-unreachable-objects-button') => {
+ const el = document.querySelector(selector);
+
+ if (!el) return;
+
+ const { pruneObjectsPath, pruneObjectsDocPath } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(PruneUnreachableObjectsButton, {
+ props: {
+ pruneObjectsPath,
+ pruneObjectsDocPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/prune_unreachable_objects_button.vue b/app/assets/javascripts/projects/prune_unreachable_objects_button.vue
new file mode 100644
index 00000000000..1387fbb78c0
--- /dev/null
+++ b/app/assets/javascripts/projects/prune_unreachable_objects_button.vue
@@ -0,0 +1,75 @@
+<script>
+import { GlButton, GlLink, GlModal, GlModalDirective } from '@gitlab/ui';
+import csrf from '~/lib/utils/csrf';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ GlLink,
+ GlModal,
+ },
+ PRUNE_UNREACHABLE_OBJECTS_MODAL_ID: 'prune-objects-modal',
+ MODAL_ACTION_PRIMARY: {
+ text: s__('UpdateProject|Prune'),
+ attributes: [{ variant: 'danger' }],
+ },
+ MODAL_ACTION_CANCEL: {
+ text: s__('UpdateProject|Cancel'),
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ pruneObjectsPath: {
+ type: String,
+ required: true,
+ },
+ pruneObjectsDocPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ csrfToken() {
+ return csrf.token;
+ },
+ },
+ methods: {
+ submitForm() {
+ this.$refs.form.submit();
+ },
+ },
+};
+</script>
+
+<template>
+ <form ref="form" :action="pruneObjectsPath" method="post">
+ <input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <input value="true" type="hidden" name="prune" />
+ <gl-modal
+ :modal-id="$options.PRUNE_UNREACHABLE_OBJECTS_MODAL_ID"
+ :title="s__('UpdateProject|Are you sure you want to prune unreachable objects?')"
+ :action-primary="$options.MODAL_ACTION_PRIMARY"
+ :action-cancel="$options.MODAL_ACTION_CANCEL"
+ size="sm"
+ :no-focus-on-show="true"
+ @ok="submitForm"
+ >
+ <p>
+ {{ s__('UpdateProject|Pruning unreachable objects can lead to repository corruption.') }}
+ <gl-link :href="pruneObjectsDocPath" target="_blank">
+ {{ s__('UpdateProject|Learn more.') }}
+ </gl-link>
+ {{ s__('UpdateProject|Are you sure you want to prune?') }}
+ </p>
+ </gl-modal>
+ <gl-button
+ v-gl-modal="$options.PRUNE_UNREACHABLE_OBJECTS_MODAL_ID"
+ category="primary"
+ variant="danger"
+ >
+ {{ s__('UpdateProject|Prune unreachable objects') }}
+ </gl-button>
+ </form>
+</template>
diff --git a/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue b/app/assets/javascripts/projects/report_abuse/components/report_abuse_dropdown_item.vue
index 31890249f41..ff76ca7c862 100644
--- a/app/assets/javascripts/projects/merge_requests/components/report_abuse_dropdown_item.vue
+++ b/app/assets/javascripts/projects/report_abuse/components/report_abuse_dropdown_item.vue
@@ -12,6 +12,7 @@ export default {
MountingPortal,
AbuseCategorySelector,
},
+ inject: ['reportedUserId', 'reportedFromUrl'],
i18n: {
reportAbuse: s__('ReportAbuse|Report abuse to administrator'),
},
@@ -21,21 +22,23 @@ export default {
};
},
methods: {
- openDrawer() {
- this.open = true;
- },
- closeDrawer() {
- this.open = false;
+ toggleDrawer(open) {
+ this.open = open;
},
},
};
</script>
<template>
<span>
- <gl-dropdown-item @click="openDrawer">{{ $options.i18n.reportAbuse }}</gl-dropdown-item>
+ <gl-dropdown-item @click="toggleDrawer(true)">{{ $options.i18n.reportAbuse }}</gl-dropdown-item>
<mounting-portal mount-to="#js-report-abuse-drawer" name="abuse-category-selector" append>
- <abuse-category-selector :show-drawer="open" @close-drawer="closeDrawer" />
+ <abuse-category-selector
+ :reported-user-id="reportedUserId"
+ :reported-from-url="reportedFromUrl"
+ :show-drawer="open"
+ @close-drawer="toggleDrawer(false)"
+ />
</mounting-portal>
</span>
</template>
diff --git a/app/assets/javascripts/projects/report_abuse/index.js b/app/assets/javascripts/projects/report_abuse/index.js
new file mode 100644
index 00000000000..9bcfdbf6165
--- /dev/null
+++ b/app/assets/javascripts/projects/report_abuse/index.js
@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import ReportAbuseDropdownItem from './components/report_abuse_dropdown_item.vue';
+
+export const initReportAbuse = () => {
+ const items = document.querySelectorAll('.js-report-abuse-dropdown-item');
+
+ items.forEach((el) => {
+ if (!el) return false;
+
+ const { reportAbusePath, reportedUserId, reportedFromUrl } = el.dataset;
+
+ return new Vue({
+ el,
+ name: 'ReportAbuseDropdownItemRoot',
+ provide: {
+ reportAbusePath,
+ reportedUserId: parseInt(reportedUserId, 10),
+ reportedFromUrl,
+ },
+ render(createElement) {
+ return createElement(ReportAbuseDropdownItem);
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/edit/protections/push_protections.vue b/app/assets/javascripts/projects/settings/branch_rules/components/edit/protections/push_protections.vue
index 541923bb735..95e140f30a9 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/edit/protections/push_protections.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/edit/protections/push_protections.vue
@@ -4,7 +4,7 @@ import { s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export const i18n = {
- allowedToPush: s__('BranchRules|Allowed to push'),
+ allowedToPush: s__('BranchRules|Allowed to push and merge'),
forcePushTitle: s__(
'BranchRules|Allow all users with push access to %{linkStart}force push%{linkEnd}.',
),
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 61c37a2348a..a98c2439cde 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
@@ -1,10 +1,10 @@
import { s__ } from '~/locale';
export const I18N = {
- manageProtectionsLinkTitle: s__('BranchRules|Manage in Protected Branches'),
- targetBranch: s__('BranchRules|Target Branch'),
+ manageProtectionsLinkTitle: s__('BranchRules|Manage in protected branches'),
+ targetBranch: s__('BranchRules|Target branch'),
branchNameOrPattern: s__('BranchRules|Branch name or pattern'),
- branch: s__('BranchRules|Target Branch'),
+ branch: s__('BranchRules|Target branch'),
allBranches: s__('BranchRules|All branches'),
matchingBranchesLinkTitle: s__('BranchRules|%{total} matching %{subject}'),
protectBranchTitle: s__('BranchRules|Protect branch'),
@@ -20,7 +20,7 @@ export const I18N = {
),
disallowForcePushDescription: s__('BranchRules|Force push is not allowed.'),
approvalsTitle: s__('BranchRules|Approvals'),
- manageApprovalsLinkTitle: s__('BranchRules|Manage in Merge Request Approvals'),
+ manageApprovalsLinkTitle: s__('BranchRules|Manage in merge request approvals'),
approvalsDescription: s__(
'BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Learn more.%{linkEnd}',
),
@@ -28,9 +28,9 @@ export const I18N = {
statusChecksDescription: s__(
'BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Learn more.%{linkEnd}',
),
- statusChecksLinkTitle: s__('BranchRules|Manage in Status checks'),
+ statusChecksLinkTitle: s__('BranchRules|Manage in status checks'),
statusChecksHeader: s__('BranchRules|Status checks (%{total})'),
- allowedToPushHeader: s__('BranchRules|Allowed to push (%{total})'),
+ allowedToPushHeader: s__('BranchRules|Allowed to push and merge (%{total})'),
allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'),
approvalsHeader: s__('BranchRules|Required approvals (%{total})'),
noData: s__('BranchRules|No data to display'),
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 6260c8dd4d0..740868e1d75 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
@@ -3,7 +3,7 @@ import { GlSprintf, GlLink, GlLoadingIcon } from '@gitlab/ui';
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 branchRulesQuery from 'ee_else_ce/projects/settings/branch_rules/queries/branch_rules_details.query.graphql';
import { getAccessLevels } from '../../../utils';
import Protection from './protection.vue';
import {
@@ -12,22 +12,16 @@ import {
BRANCH_PARAM_NAME,
WILDCARDS_HELP_PATH,
PROTECTED_BRANCHES_HELP_PATH,
- APPROVALS_HELP_PATH,
- STATUS_CHECKS_HELP_PATH,
} from './constants';
const wildcardsHelpDocLink = helpPagePath(WILDCARDS_HELP_PATH);
const protectedBranchesHelpDocLink = helpPagePath(PROTECTED_BRANCHES_HELP_PATH);
-const approvalsHelpDocLink = helpPagePath(APPROVALS_HELP_PATH);
-const statusChecksHelpDocLink = helpPagePath(STATUS_CHECKS_HELP_PATH);
export default {
name: 'RuleView',
i18n: I18N,
wildcardsHelpDocLink,
protectedBranchesHelpDocLink,
- approvalsHelpDocLink,
- statusChecksHelpDocLink,
components: { Protection, GlSprintf, GlLink, GlLoadingIcon },
inject: {
projectPath: {
@@ -36,12 +30,6 @@ export default {
protectedBranchesPath: {
default: '',
},
- approvalRulesPath: {
- default: '',
- },
- statusChecksPath: {
- default: '',
- },
branchesPath: {
default: '',
},
@@ -58,7 +46,7 @@ export default {
const branchRule = branchRules.nodes.find((rule) => rule.name === this.branch);
this.branchRule = branchRule;
this.branchProtection = branchRule?.branchProtection;
- this.approvalRules = branchRule?.approvalRules;
+ this.approvalRules = branchRule?.approvalRules?.nodes || [];
this.statusChecks = branchRule?.externalStatusChecks?.nodes || [];
this.matchingBranchesCount = branchRule?.matchingBranchesCount;
},
@@ -98,20 +86,6 @@ export default {
total: this.pushAccessLevels?.total || 0,
});
},
- approvalsHeader() {
- const total = this.approvals.reduce(
- (sum, { approvalsRequired }) => sum + approvalsRequired,
- 0,
- );
- return sprintf(this.$options.i18n.approvalsHeader, {
- total,
- });
- },
- statusChecksHeader() {
- return sprintf(this.$options.i18n.statusChecksHeader, {
- total: this.statusChecks.length,
- });
- },
allBranches() {
return this.branch === ALL_BRANCHES_WILDCARD;
},
@@ -131,8 +105,13 @@ export default {
const subject = n__('branch', 'branches', total);
return sprintf(this.$options.i18n.matchingBranchesLinkTitle, { total, subject });
},
- approvals() {
- return this.approvalRules?.nodes || [];
+ // needed to override EE component
+ statusChecksHeader() {
+ return '';
+ },
+ // needed to override EE component
+ approvalsHeader() {
+ return '';
},
},
methods: {
@@ -199,40 +178,46 @@ export default {
:groups="mergeAccessLevels.groups"
/>
+ <!-- EE start -->
<!-- Approvals -->
- <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.approvalsTitle }}</h4>
- <gl-sprintf :message="$options.i18n.approvalsDescription">
- <template #link="{ content }">
- <gl-link :href="$options.approvalsHelpDocLink">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
+ <template v-if="approvalsHeader">
+ <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.approvalsTitle }}</h4>
+ <gl-sprintf :message="$options.i18n.approvalsDescription">
+ <template #link="{ content }">
+ <gl-link :href="$options.approvalsHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
- <protection
- class="gl-mt-3"
- :header="approvalsHeader"
- :header-link-title="$options.i18n.manageApprovalsLinkTitle"
- :header-link-href="approvalRulesPath"
- :approvals="approvals"
- />
+ <protection
+ class="gl-mt-3"
+ :header="approvalsHeader"
+ :header-link-title="$options.i18n.manageApprovalsLinkTitle"
+ :header-link-href="approvalRulesPath"
+ :approvals="approvalRules"
+ />
+ </template>
<!-- Status checks -->
- <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.statusChecksTitle }}</h4>
- <gl-sprintf :message="$options.i18n.statusChecksDescription">
- <template #link="{ content }">
- <gl-link :href="$options.statusChecksHelpDocLink">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
+ <template v-if="statusChecksHeader">
+ <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.statusChecksTitle }}</h4>
+ <gl-sprintf :message="$options.i18n.statusChecksDescription">
+ <template #link="{ content }">
+ <gl-link :href="$options.statusChecksHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
- <protection
- class="gl-mt-3"
- :header="statusChecksHeader"
- :header-link-title="$options.i18n.statusChecksLinkTitle"
- :header-link-href="statusChecksPath"
- :status-checks="statusChecks"
- />
+ <protection
+ class="gl-mt-3"
+ :header="statusChecksHeader"
+ :header-link-title="$options.i18n.statusChecksLinkTitle"
+ :header-link-href="statusChecksPath"
+ :status-checks="statusChecks"
+ />
+ </template>
+ <!-- EE end -->
</div>
</template>
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 7639acc1181..081d6cec958 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 View from './components/view/index.vue';
+import View from 'ee_else_ce/projects/settings/branch_rules/components/view/index.vue';
export default function mountBranchRules(el) {
if (!el) {
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 a832e59aa67..aa736469749 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
@@ -4,24 +4,14 @@ query getBranchRulesDetails($projectPath: ID!) {
branchRules {
nodes {
name
+ matchingBranchesCount
branchProtection {
allowForcePush
- codeOwnerApprovalRequired
mergeAccessLevels {
edges {
node {
accessLevel
accessLevelDescription
- group {
- id
- avatarUrl
- }
- user {
- id
- name
- avatarUrl
- webUrl
- }
}
}
}
@@ -30,45 +20,10 @@ query getBranchRulesDetails($projectPath: ID!) {
node {
accessLevel
accessLevelDescription
- group {
- id
- avatarUrl
- }
- user {
- id
- name
- avatarUrl
- webUrl
- }
- }
- }
- }
- }
- approvalRules {
- nodes {
- id
- name
- type
- approvalsRequired
- eligibleApprovers {
- nodes {
- id
- name
- username
- webUrl
- avatarUrl
}
}
}
}
- externalStatusChecks {
- nodes {
- id
- name
- 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 9b669024a8b..f3d392a0ec4 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -1,23 +1,22 @@
<script>
-import { s__ } from '~/locale';
+import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { createAlert } from '~/flash';
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
+import { expandSection } from '~/settings_panels';
+import { scrollToElement } from '~/lib/utils/common_utils';
import BranchRule from './components/branch_rule.vue';
-
-export const i18n = {
- queryError: s__(
- 'ProtectedBranch|An error occurred while loading branch rules. Please try again.',
- ),
- emptyState: s__(
- 'ProtectedBranch|Protected branches, merge request approvals, and status checks will appear here once configured.',
- ),
-};
+import { I18N, PROTECTED_BRANCHES_ANCHOR, BRANCH_PROTECTION_MODAL_ID } from './constants';
export default {
name: 'BranchRules',
- i18n,
+ i18n: I18N,
components: {
BranchRule,
+ GlButton,
+ GlModal,
+ },
+ directives: {
+ GlModal: GlModalDirective,
},
apollo: {
branchRules: {
@@ -36,20 +35,27 @@ export default {
},
},
inject: {
- projectPath: {
- default: '',
- },
+ projectPath: { default: '' },
},
data() {
return {
branchRules: [],
};
},
+ methods: {
+ showProtectedBranches() {
+ // Protected branches section is on the same page as the branch rules section.
+ expandSection(this.$options.protectedBranchesAnchor);
+ scrollToElement(this.$options.protectedBranchesAnchor);
+ },
+ },
+ modalId: BRANCH_PROTECTION_MODAL_ID,
+ protectedBranchesAnchor: PROTECTED_BRANCHES_ANCHOR,
};
</script>
<template>
- <div class="settings-content">
+ <div class="settings-content gl-mb-0">
<branch-rule
v-for="(rule, index) in branchRules"
:key="`${rule.name}-${index}`"
@@ -61,6 +67,21 @@ export default {
:matching-branches-count="rule.matchingBranchesCount"
/>
- <span v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</span>
+ <div v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</div>
+
+ <gl-button v-gl-modal="$options.modalId" class="gl-mt-5" category="secondary" variant="info">{{
+ $options.i18n.addBranchRule
+ }}</gl-button>
+
+ <gl-modal
+ :ref="$options.modalId"
+ :modal-id="$options.modalId"
+ :title="$options.i18n.addBranchRule"
+ :ok-title="$options.i18n.createProtectedBranch"
+ @ok="showProtectedBranches"
+ >
+ <p>{{ $options.i18n.branchRuleModalDescription }}</p>
+ <p>{{ $options.i18n.branchRuleModalContent }}</p>
+ </gl-modal>
</div>
</template>
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 4a24df4b0dc..fa96eee5f92 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
@@ -13,7 +13,7 @@ export const i18n = {
approvalRules: s__('BranchRules|%{total} approval %{subject}'),
matchingBranches: s__('BranchRules|%{total} matching %{subject}'),
pushAccessLevels: s__('BranchRules|Allowed to merge'),
- mergeAccessLevels: s__('BranchRules|Allowed to push'),
+ mergeAccessLevels: s__('BranchRules|Allowed to push and merge'),
};
export default {
@@ -106,7 +106,7 @@ export default {
},
approvalDetails() {
const approvalDetails = [];
- if (this.isWildcard) {
+ if (this.isWildcard || this.matchingBranchesCount > 1) {
approvalDetails.push(this.matchingBranchesText);
}
if (this.branchProtection?.allowForcePush) {
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/constants.js b/app/assets/javascripts/projects/settings/repository/branch_rules/constants.js
new file mode 100644
index 00000000000..4413d8eab4e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/constants.js
@@ -0,0 +1,22 @@
+import { s__ } from '~/locale';
+
+export const I18N = {
+ queryError: s__(
+ 'ProtectedBranch|An error occurred while loading branch rules. Please try again.',
+ ),
+ emptyState: s__(
+ 'ProtectedBranch|After you configure a protected branch, merge request approval, or status check, it appears here.',
+ ),
+ addBranchRule: s__('BranchRules|Add branch rule'),
+ branchRuleModalDescription: s__(
+ 'BranchRules|To create a branch rule, you first need to create a protected branch.',
+ ),
+ branchRuleModalContent: s__(
+ 'BranchRules|After a protected branch is created, it will show up in the list as a branch rule.',
+ ),
+ createProtectedBranch: s__('BranchRules|Create protected branch'),
+};
+
+export const PROTECTED_BRANCHES_ANCHOR = '#js-protected-branches-settings';
+
+export const BRANCH_PROTECTION_MODAL_ID = 'addBranchRuleModal';
diff --git a/app/assets/javascripts/projects/settings/utils.js b/app/assets/javascripts/projects/settings/utils.js
index 7bcfde39178..ea4574119c0 100644
--- a/app/assets/javascripts/projects/settings/utils.js
+++ b/app/assets/javascripts/projects/settings/utils.js
@@ -9,7 +9,7 @@ export const getAccessLevels = (accessLevels = {}) => {
} else if (node.group) {
accessLevelTypes.groups.push(node);
} else {
- accessLevelTypes.roles.push(node);
+ accessLevelTypes.roles.push({ accessLevelDescription: node.accessLevelDescription });
}
});