diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 18:42:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 18:42:06 +0000 |
commit | 6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch) | |
tree | 78be5963ec075d80116a932011d695dd33910b4e /app/assets/javascripts/projects | |
parent | 1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff) | |
download | gitlab-ce-6e4e1050d9dba2b7b2523fdd1768823ab85feef4.tar.gz |
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'app/assets/javascripts/projects')
13 files changed, 706 insertions, 123 deletions
diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js index f0832bd36a5..927501748a5 100644 --- a/app/assets/javascripts/projects/commits/store/actions.js +++ b/app/assets/javascripts/projects/commits/store/actions.js @@ -1,7 +1,7 @@ import * as Sentry from '@sentry/browser'; import * as types from './mutation_types'; import axios from '~/lib/utils/axios_utils'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import { __ } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; diff --git a/app/assets/javascripts/projects/components/project_delete_button.vue b/app/assets/javascripts/projects/components/project_delete_button.vue new file mode 100644 index 00000000000..4b27c5e3d30 --- /dev/null +++ b/app/assets/javascripts/projects/components/project_delete_button.vue @@ -0,0 +1,52 @@ +<script> +import { GlAlert, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; +import SharedDeleteButton from './shared/delete_button.vue'; + +export default { + components: { + GlSprintf, + GlAlert, + SharedDeleteButton, + }, + props: { + confirmPhrase: { + type: String, + required: true, + }, + formPath: { + type: String, + required: true, + }, + }, + strings: { + alertTitle: __('You are about to permanently delete this project'), + alertBody: __( + 'Once a project is permanently deleted it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its respositories and %{strongStart}all related resources%{strongEnd} including issues, merge requests etc.', + ), + modalBody: __( + "This action cannot be undone. You will lose the project's respository and all conent: issues, merge requests, etc.", + ), + }, +}; +</script> + +<template> + <shared-delete-button v-bind="{ confirmPhrase, formPath }"> + <template #modal-body> + <gl-alert + class="gl-mb-5" + variant="danger" + :title="$options.strings.alertTitle" + :dismissible="false" + > + <gl-sprintf :message="$options.strings.alertBody"> + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> + </gl-alert> + <p>{{ $options.strings.modalBody }}</p> + </template> + </shared-delete-button> +</template> diff --git a/app/assets/javascripts/projects/components/remove_modal.vue b/app/assets/javascripts/projects/components/remove_modal.vue deleted file mode 100644 index 37f58efcb30..00000000000 --- a/app/assets/javascripts/projects/components/remove_modal.vue +++ /dev/null @@ -1,108 +0,0 @@ -<script> -import { GlModal, GlModalDirective, GlSprintf, GlFormInput, GlButton } from '@gitlab/ui'; -import { __ } from '~/locale'; -import { rstrip } from '~/lib/utils/common_utils'; -import csrf from '~/lib/utils/csrf'; - -export default { - components: { - GlModal, - GlSprintf, - GlFormInput, - GlButton, - }, - directives: { - GlModal: GlModalDirective, - }, - props: { - confirmPhrase: { - type: String, - required: true, - }, - warningMessage: { - type: String, - required: true, - }, - formPath: { - type: String, - required: true, - }, - }, - data() { - return { - userInput: null, - }; - }, - computed: { - buttonDisabled() { - return rstrip(this.userInput) !== this.confirmPhrase; - }, - csrfToken() { - return csrf.token; - }, - }, - methods: { - submitForm() { - this.$refs.form.submit(); - }, - }, - strings: { - removeProject: __('Remove project'), - title: __('Confirmation required'), - confirm: __('Confirm'), - dataLoss: __( - 'This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.', - ), - confirmText: __('Please type %{phrase_code} to proceed or close this modal to cancel.'), - }, - modalId: 'remove-project-modal', -}; -</script> - -<template> - <form ref="form" :action="formPath" method="post"> - <input type="hidden" name="_method" value="delete" /> - <input :value="csrfToken" type="hidden" name="authenticity_token" /> - <gl-button v-gl-modal="$options.modalId" category="primary" variant="danger">{{ - $options.strings.removeProject - }}</gl-button> - <gl-modal - ref="removeModal" - :modal-id="$options.modalId" - size="sm" - ok-variant="danger" - footer-class="bg-gray-light gl-p-5" - > - <template #modal-title>{{ $options.strings.title }}</template> - <template #modal-footer> - <div class="gl-w-full gl-display-flex gl-just-content-start gl-m-0"> - <gl-button - :disabled="buttonDisabled" - category="primary" - variant="danger" - @click="submitForm" - > - {{ $options.strings.confirm }} - </gl-button> - </div> - </template> - <div> - <p class="gl-text-red-500 gl-font-weight-bold">{{ warningMessage }}</p> - <p class="gl-mb-0">{{ $options.strings.dataLoss }}</p> - <p> - <gl-sprintf :message="$options.strings.confirmText"> - <template #phrase_code> - <code>{{ confirmPhrase }}</code> - </template> - </gl-sprintf> - </p> - <gl-form-input - id="confirm_name_input" - v-model="userInput" - name="confirm_name_input" - type="text" - /> - </div> - </gl-modal> - </form> -</template> diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue new file mode 100644 index 00000000000..e3f4500d404 --- /dev/null +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -0,0 +1,101 @@ +<script> +import { uniqueId } from 'lodash'; +import { GlModal, GlModalDirective, GlFormInput, GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; +import csrf from '~/lib/utils/csrf'; + +export default { + components: { + GlModal, + GlFormInput, + GlButton, + }, + directives: { + GlModal: GlModalDirective, + }, + props: { + confirmPhrase: { + type: String, + required: true, + }, + formPath: { + type: String, + required: true, + }, + }, + data() { + return { + userInput: null, + modalId: uniqueId('delete-project-modal-'), + }; + }, + computed: { + confirmDisabled() { + return this.userInput !== this.confirmPhrase; + }, + csrfToken() { + return csrf.token; + }, + modalActionProps() { + return { + primary: { + text: __('Yes, delete project'), + attributes: [{ variant: 'danger' }, { disabled: this.confirmDisabled }], + }, + cancel: { + text: __('Cancel, keep project'), + }, + }; + }, + }, + methods: { + submitForm() { + this.$refs.form.submit(); + }, + }, + strings: { + deleteProject: __('Delete project'), + title: __('Delete project. Are you ABSOLUTELY SURE?'), + confirmText: __('Please type the following to confirm:'), + }, +}; +</script> + +<template> + <form ref="form" :action="formPath" method="post"> + <input type="hidden" name="_method" value="delete" /> + <input :value="csrfToken" type="hidden" name="authenticity_token" /> + + <gl-button v-gl-modal="modalId" category="primary" variant="danger">{{ + $options.strings.deleteProject + }}</gl-button> + + <gl-modal + ref="removeModal" + :modal-id="modalId" + size="sm" + ok-variant="danger" + footer-class="gl-bg-gray-10 gl-p-5" + title-class="gl-text-red-500" + :action-primary="modalActionProps.primary" + :action-cancel="modalActionProps.cancel" + @ok="submitForm" + > + <template #modal-title>{{ $options.strings.title }}</template> + <div> + <slot name="modal-body"></slot> + <p class="gl-mb-1">{{ $options.strings.confirmText }}</p> + <p> + <code>{{ confirmPhrase }}</code> + </p> + <gl-form-input + id="confirm_name_input" + v-model="userInput" + name="confirm_name_input" + type="text" + /> + <slot name="modal-footer"></slot> + </div> + </gl-modal> + </form> +</template> diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue index e553599357c..ee4a00dbc75 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue @@ -1,7 +1,7 @@ <script> +import { GlBreadcrumb, GlIcon } from '@gitlab/ui'; import WelcomePage from './welcome.vue'; import LegacyContainer from './legacy_container.vue'; -import { GlBreadcrumb, GlIcon } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import blankProjectIllustration from '../illustrations/blank-project.svg'; diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue index ea22818da0e..cd9a72996cf 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue @@ -1,6 +1,6 @@ <script> -import Tracking from '~/tracking'; import { GlPopover } from '@gitlab/ui'; +import Tracking from '~/tracking'; import LegacyContainer from './legacy_container.vue'; const trackingMixin = Tracking.mixin(gon.tracking_data); diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue index cdf03a5013f..0777dddfc19 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue @@ -1,7 +1,7 @@ <script> import dateFormat from 'dateformat'; -import { __, sprintf } from '~/locale'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import { __, sprintf } from '~/locale'; import { getDateInPast } from '~/lib/utils/datetime_utility'; import StatisticsList from './statistics_list.vue'; import PipelinesAreaChart from './pipelines_area_chart.vue'; diff --git a/app/assets/javascripts/projects/project_remove_modal.js b/app/assets/javascripts/projects/project_delete_button.js index dbdad1bf6f1..aa7fc31d307 100644 --- a/app/assets/javascripts/projects/project_remove_modal.js +++ b/app/assets/javascripts/projects/project_delete_button.js @@ -1,21 +1,20 @@ import Vue from 'vue'; -import RemoveProjectModal from './components/remove_modal.vue'; +import ProjectDeleteButton from './components/project_delete_button.vue'; -export default (selector = '#js-confirm-project-remove') => { +export default (selector = '#js-project-delete-button') => { const el = document.querySelector(selector); if (!el) return; - const { formPath, confirmPhrase, warningMessage } = el.dataset; + const { confirmPhrase, formPath } = el.dataset; // eslint-disable-next-line no-new new Vue({ el, render(createElement) { - return createElement(RemoveProjectModal, { + return createElement(ProjectDeleteButton, { props: { confirmPhrase, - warningMessage, formPath, }, }); diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index ebf745fd046..ec0a83b5736 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,7 +1,7 @@ import $ from 'jquery'; +import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { convertToTitleCase, humanize, slugify } from '../lib/utils/text_utility'; -import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; let hasUserDefinedProjectPath = false; let hasUserDefinedProjectName = false; diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js new file mode 100644 index 00000000000..4dbf6675357 --- /dev/null +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -0,0 +1,524 @@ +/* eslint-disable no-underscore-dangle, class-methods-use-this */ +import { escape, find, countBy } from 'lodash'; +import axios from '~/lib/utils/axios_utils'; +import { deprecatedCreateFlash as Flash } from '~/flash'; +import { n__, s__, __ } from '~/locale'; +import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants'; + +export default class AccessDropdown { + constructor(options) { + const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options; + this.options = options; + this.hasLicense = hasLicense; + this.groups = []; + this.accessLevel = accessLevel; + this.accessLevelsData = accessLevelsData.roles; + this.$dropdown = $dropdown; + this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`); + this.usersPath = '/-/autocomplete/users.json'; + this.groupsPath = '/-/autocomplete/project_groups.json'; + this.defaultLabel = this.$dropdown.data('defaultLabel'); + + this.setSelectedItems([]); + this.persistPreselectedItems(); + + this.noOneObj = this.accessLevelsData.find(level => level.id === ACCESS_LEVEL_NONE); + + this.initDropdown(); + } + + initDropdown() { + const { onSelect, onHide } = this.options; + this.$dropdown.glDropdown({ + data: this.getData.bind(this), + selectable: true, + filterable: true, + filterRemote: true, + multiSelect: this.$dropdown.hasClass('js-multiselect'), + renderRow: this.renderRow.bind(this), + toggleLabel: this.toggleLabel.bind(this), + hidden() { + if (onHide) { + onHide(); + } + }, + clicked: options => { + const { $el, e } = options; + const item = options.selectedObj; + + e.preventDefault(); + + if (!this.hasLicense) { + // We're not multiselecting quite yet with FOSS: + // remove all preselected items before selecting this item + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37499 + this.accessLevelsData.forEach(level => { + this.removeSelectedItem(level); + }); + } + + if ($el.is('.is-active')) { + if (this.noOneObj) { + if (item.id === this.noOneObj.id && this.hasLicense) { + // remove all others selected items + this.accessLevelsData.forEach(level => { + if (level.id !== item.id) { + this.removeSelectedItem(level); + } + }); + + // remove selected item visually + this.$wrap.find(`.item-${item.type}`).removeClass('is-active'); + } else { + const $noOne = this.$wrap.find( + `.is-active.item-${item.type}[data-role-id="${this.noOneObj.id}"]`, + ); + if ($noOne.length) { + $noOne.removeClass('is-active'); + this.removeSelectedItem(this.noOneObj); + } + } + } + + // make element active right away + $el.addClass(`is-active item-${item.type}`); + + // Add "No one" + this.addSelectedItem(item); + } else { + this.removeSelectedItem(item); + } + + if (onSelect) { + onSelect(item, $el, this); + } + }, + }); + + this.$dropdown.find('.dropdown-toggle-text').text(this.toggleLabel()); + } + + persistPreselectedItems() { + const itemsToPreselect = this.$dropdown.data('preselectedItems'); + + if (!itemsToPreselect || !itemsToPreselect.length) { + return; + } + + const persistedItems = itemsToPreselect.map(item => { + const persistedItem = { ...item }; + persistedItem.persisted = true; + return persistedItem; + }); + + this.setSelectedItems(persistedItems); + } + + setSelectedItems(items = []) { + this.items = items; + } + + getSelectedItems() { + return this.items.filter(item => !item._destroy); + } + + getAllSelectedItems() { + return this.items; + } + + // Return dropdown as input data ready to submit + getInputData() { + const selectedItems = this.getAllSelectedItems(); + + const accessLevels = selectedItems.map(item => { + const obj = {}; + + if (typeof item.id !== 'undefined') { + obj.id = item.id; + } + + if (typeof item._destroy !== 'undefined') { + obj._destroy = item._destroy; + } + + if (item.type === LEVEL_TYPES.ROLE) { + obj.access_level = item.access_level; + } else if (item.type === LEVEL_TYPES.USER) { + obj.user_id = item.user_id; + } else if (item.type === LEVEL_TYPES.GROUP) { + obj.group_id = item.group_id; + } + + return obj; + }); + + return accessLevels; + } + + addSelectedItem(selectedItem) { + let itemToAdd = {}; + + let index = -1; + let alreadyAdded = false; + const selectedItems = this.getAllSelectedItems(); + + // Compare IDs based on selectedItem.type + selectedItems.forEach((item, i) => { + let comparator; + switch (selectedItem.type) { + case LEVEL_TYPES.ROLE: + comparator = LEVEL_ID_PROP.ROLE; + // If the item already exists, just use it + if (item[comparator] === selectedItem.id) { + alreadyAdded = true; + } + break; + case LEVEL_TYPES.GROUP: + comparator = LEVEL_ID_PROP.GROUP; + break; + case LEVEL_TYPES.USER: + comparator = LEVEL_ID_PROP.USER; + break; + default: + break; + } + + if (selectedItem.id === item[comparator]) { + index = i; + } + }); + + if (alreadyAdded) { + return; + } + + if (index !== -1 && selectedItems[index]._destroy) { + delete selectedItems[index]._destroy; + return; + } + + itemToAdd.type = selectedItem.type; + + if (selectedItem.type === LEVEL_TYPES.USER) { + itemToAdd = { + user_id: selectedItem.id, + name: selectedItem.name || '_name1', + username: selectedItem.username || '_username1', + avatar_url: selectedItem.avatar_url || '_avatar_url1', + type: LEVEL_TYPES.USER, + }; + } else if (selectedItem.type === LEVEL_TYPES.ROLE) { + itemToAdd = { + access_level: selectedItem.id, + type: LEVEL_TYPES.ROLE, + }; + } else if (selectedItem.type === LEVEL_TYPES.GROUP) { + itemToAdd = { + group_id: selectedItem.id, + type: LEVEL_TYPES.GROUP, + }; + } + + this.items.push(itemToAdd); + } + + removeSelectedItem(itemToDelete) { + let index = -1; + const selectedItems = this.getAllSelectedItems(); + + // To find itemToDelete on selectedItems, first we need the index + selectedItems.every((item, i) => { + if (item.type !== itemToDelete.type) { + return true; + } + + if (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) { + index = i; + } else if (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) { + index = i; + } else if (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id) { + index = i; + } + + // Break once we have index set + return !(index > -1); + }); + + // if ItemToDelete is not really selected do nothing + if (index === -1) { + return; + } + + if (selectedItems[index].persisted) { + // If we toggle an item that has been already marked with _destroy + if (selectedItems[index]._destroy) { + delete selectedItems[index]._destroy; + } else { + selectedItems[index]._destroy = '1'; + } + } else { + selectedItems.splice(index, 1); + } + } + + toggleLabel() { + const currentItems = this.getSelectedItems(); + const $dropdownToggleText = this.$dropdown.find('.dropdown-toggle-text'); + + if (currentItems.length === 0) { + $dropdownToggleText.addClass('is-default'); + return this.defaultLabel; + } + + $dropdownToggleText.removeClass('is-default'); + + if (currentItems.length === 1 && currentItems[0].type === LEVEL_TYPES.ROLE) { + const roleData = this.accessLevelsData.find(data => data.id === currentItems[0].access_level); + return roleData.text; + } + + const labelPieces = []; + const counts = countBy(currentItems, item => item.type); + + if (counts[LEVEL_TYPES.ROLE] > 0) { + labelPieces.push(n__('1 role', '%d roles', counts[LEVEL_TYPES.ROLE])); + } + + if (counts[LEVEL_TYPES.USER] > 0) { + labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER])); + } + + if (counts[LEVEL_TYPES.GROUP] > 0) { + labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP])); + } + + return labelPieces.join(', '); + } + + getData(query, callback) { + if (this.hasLicense) { + Promise.all([ + this.getUsers(query), + this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), + ]) + .then(([usersResponse, groupsResponse]) => { + this.groupsData = groupsResponse; + callback(this.consolidateData(usersResponse.data, groupsResponse.data)); + }) + .catch(() => Flash(__('Failed to load groups & users.'))); + } else { + callback(this.consolidateData()); + } + } + + consolidateData(usersResponse = [], groupsResponse = []) { + let consolidatedData = []; + + // ID property is handled differently locally from the server + // + // For Groups + // In dropdown: `id` + // For submit: `group_id` + // + // For Roles + // In dropdown: `id` + // For submit: `access_level` + // + // For Users + // In dropdown: `id` + // For submit: `user_id` + + /* + * Build roles + */ + const roles = this.accessLevelsData.map(level => { + /* eslint-disable no-param-reassign */ + // This re-assignment is intentional as + // level.type property is being used in removeSelectedItem() + // for comparision, and accessLevelsData is provided by + // gon.create_access_levels which doesn't have `type` included. + // See this discussion https://gitlab.com/gitlab-org/gitlab/merge_requests/1629#note_31285823 + level.type = LEVEL_TYPES.ROLE; + return level; + }); + + if (roles.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'header', content: s__('AccessDropdown|Roles') }], + roles, + ); + } + + if (this.hasLicense) { + const map = []; + const selectedItems = this.getSelectedItems(); + /* + * Build groups + */ + const groups = groupsResponse.map(group => ({ + ...group, + type: LEVEL_TYPES.GROUP, + })); + + /* + * Build users + */ + const users = selectedItems + .filter(item => item.type === LEVEL_TYPES.USER) + .map(item => { + // Save identifiers for easy-checking more later + map.push(LEVEL_TYPES.USER + item.user_id); + + return { + id: item.user_id, + name: item.name, + username: item.username, + avatar_url: item.avatar_url, + type: LEVEL_TYPES.USER, + }; + }); + + // Has to be checked against server response + // because the selected item can be in filter results + usersResponse.forEach(response => { + // Add is it has not been added + if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) { + const user = { ...response }; + user.type = LEVEL_TYPES.USER; + users.push(user); + } + }); + + if (groups.length) { + if (roles.length) { + consolidatedData = consolidatedData.concat([{ type: 'divider' }]); + } + + consolidatedData = consolidatedData.concat( + [{ type: 'header', content: s__('AccessDropdown|Groups') }], + groups, + ); + } + + if (users.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'divider' }], + [{ type: 'header', content: s__('AccessDropdown|Users') }], + users, + ); + } + } + + return consolidatedData; + } + + getUsers(query) { + return axios.get(this.buildUrl(gon.relative_url_root, this.usersPath), { + params: { + search: query, + per_page: 20, + active: true, + project_id: gon.current_project_id, + push_code: true, + }, + }); + } + + getGroups() { + return axios.get(this.buildUrl(gon.relative_url_root, this.groupsPath), { + params: { + project_id: gon.current_project_id, + }, + }); + } + + buildUrl(urlRoot, url) { + let newUrl; + if (urlRoot != null) { + newUrl = urlRoot.replace(/\/$/, '') + url; + } + return newUrl; + } + + renderRow(item) { + let criteria = {}; + let groupRowEl; + + // Dectect if the current item is already saved so we can add + // the `is-active` class so the item looks as marked + switch (item.type) { + case LEVEL_TYPES.USER: + criteria = { user_id: item.id }; + break; + case LEVEL_TYPES.ROLE: + criteria = { access_level: item.id }; + break; + case LEVEL_TYPES.GROUP: + criteria = { group_id: item.id }; + break; + default: + break; + } + + const isActive = find(this.getSelectedItems(), criteria) ? 'is-active' : ''; + + switch (item.type) { + case LEVEL_TYPES.USER: + groupRowEl = this.userRowHtml(item, isActive); + break; + case LEVEL_TYPES.ROLE: + groupRowEl = this.roleRowHtml(item, isActive); + break; + case LEVEL_TYPES.GROUP: + groupRowEl = this.groupRowHtml(item, isActive); + break; + default: + groupRowEl = ''; + break; + } + + return groupRowEl; + } + + userRowHtml(user, isActive) { + const isActiveClass = isActive || ''; + + return ` + <li> + <a href="#" class="${isActiveClass}"> + <img src="${user.avatar_url}" class="avatar avatar-inline" width="30"> + <strong class="dropdown-menu-user-full-name">${escape(user.name)}</strong> + <span class="dropdown-menu-user-username">${user.username}</span> + </a> + </li> + `; + } + + groupRowHtml(group, isActive) { + const isActiveClass = isActive || ''; + const avatarEl = group.avatar_url + ? `<img src="${group.avatar_url}" class="avatar avatar-inline" width="30">` + : ''; + + return ` + <li> + <a href="#" class="${isActiveClass}"> + ${avatarEl} + <span class="dropdown-menu-group-groupname">${group.name}</span> + </a> + </li> + `; + } + + roleRowHtml(role, isActive) { + const isActiveClass = isActive || ''; + + return ` + <li> + <a href="#" class="${isActiveClass} item-${role.type}" data-role-id="${role.id}"> + ${role.text} + </a> + </li> + `; + } +} diff --git a/app/assets/javascripts/projects/settings/constants.js b/app/assets/javascripts/projects/settings/constants.js new file mode 100644 index 00000000000..fadb1f4f178 --- /dev/null +++ b/app/assets/javascripts/projects/settings/constants.js @@ -0,0 +1,13 @@ +export const LEVEL_TYPES = { + ROLE: 'role', + USER: 'user', + GROUP: 'group', +}; + +export const LEVEL_ID_PROP = { + ROLE: 'access_level', + USER: 'user_id', + GROUP: 'group_id', +}; + +export const ACCESS_LEVEL_NONE = 0; diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index 43c20fea43e..0b7433d6aaa 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -1,5 +1,5 @@ <script> -import { GlDeprecatedButton, GlFormSelect, GlToggle, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon } from '@gitlab/ui'; import { __ } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -13,7 +13,7 @@ export default { }, components: { ClipboardButton, - GlDeprecatedButton, + GlButton, GlFormSelect, GlToggle, GlLoadingIcon, @@ -157,12 +157,14 @@ export default { }} </span> </template> - <gl-deprecated-button + <gl-button variant="success" + class="gl-mt-5" :disabled="isTemplateSaving" @click="onSaveTemplate" - >{{ __('Save template') }}</gl-deprecated-button > + {{ __('Save template') }} + </gl-button> </div> </div> </div> diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index 571d305a50c..e691f675e59 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -3,7 +3,7 @@ import Visibility from 'visibilityjs'; import { GlLoadingIcon } from '@gitlab/ui'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; import Poll from '~/lib/utils/poll'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import { __, s__, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; import CommitPipelineService from '../services/commit_pipeline_service'; |