diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-26 00:15:18 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-26 00:15:18 +0300 |
commit | 616a16ea4d50ad4858f2089f8bfc56c105516599 (patch) | |
tree | 53d2e4986ec9f9c23fdad68febc0d811536da602 /app | |
parent | 2b2d833ab3e78f8c9f626af950a16d43fc38c9f8 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
12 files changed, 153 insertions, 19 deletions
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index 6b7c08d05a5..24071c6f0b4 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlDrawer, GlLabel } from '@gitlab/ui'; +import { GlButton, GlDrawer, GlLabel, GlModal, GlModalDirective } from '@gitlab/ui'; import { MountingPortal } from 'portal-vue'; import { mapActions, mapState, mapGetters } from 'vuex'; import { LIST, ListType, ListTypeTitles } from '~/boards/constants'; @@ -11,8 +11,14 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { listSettingsText: __('List settings'), + i18n: { + modalAction: __('Remove list'), + modalCopy: __('Are you sure you want to remove this list?'), + modalCancel: __('Cancel'), + }, components: { GlButton, + GlModal, GlDrawer, GlLabel, MountingPortal, @@ -21,6 +27,9 @@ export default { BoardSettingsListTypes: () => import('ee_component/boards/components/board_settings_list_types.vue'), }, + directives: { + GlModal: GlModalDirective, + }, mixins: [glFeatureFlagMixin(), Tracking.mixin()], inject: ['canAdminList', 'scopedLabelsAvailable'], inheritAttrs: false, @@ -29,6 +38,7 @@ export default { ListType, }; }, + modalId: 'board-settings-sidebar-modal', computed: { ...mapGetters(['isSidebarOpen', 'isEpicBoard']), ...mapState(['activeId', 'sidebarType', 'boardLists']), @@ -59,16 +69,16 @@ export default { }, methods: { ...mapActions(['unsetActiveId', 'removeList']), + handleModalPrimary() { + this.deleteBoard(); + }, showScopedLabels(label) { return this.scopedLabelsAvailable && isScopedLabel(label); }, deleteBoard() { - // eslint-disable-next-line no-alert - if (window.confirm(__('Are you sure you want to remove this list?'))) { - this.track('click_button', { label: 'remove_list' }); - this.removeList(this.activeId); - this.unsetActiveId(); - } + this.track('click_button', { label: 'remove_list' }); + this.removeList(this.activeId); + this.unsetActiveId(); }, }, }; @@ -92,11 +102,10 @@ export default { <template #header> <div v-if="canAdminList && activeList.id" class="gl-mt-3"> <gl-button + v-gl-modal="$options.modalId" variant="danger" category="secondary" size="small" - data-testid="remove-list" - @click.stop="deleteBoard" >{{ __('Remove list') }} </gl-button> </div> @@ -122,5 +131,21 @@ export default { /> </template> </gl-drawer> + <gl-modal + :modal-id="$options.modalId" + :title="$options.i18n.modalAction" + size="sm" + :action-primary="{ + text: $options.i18n.modalAction, + attributes: [{ variant: 'danger' }], + }" + :action-secondary="{ + text: $options.i18n.modalCancel, + attributes: [{ variant: 'default' }], + }" + @primary="handleModalPrimary" + > + <p>{{ $options.i18n.modalCopy }}</p> + </gl-modal> </mounting-portal> </template> diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue index 13f2e775fc3..b1f6f2c87b9 100644 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -4,7 +4,12 @@ import { listen } from 'codesandbox-api'; import { isEmpty, debounce } from 'lodash'; import { Manager } from 'smooshpack'; import { mapActions, mapGetters, mapState } from 'vuex'; -import { packageJsonPath, LIVE_PREVIEW_DEBOUNCE } from '../../constants'; +import { + packageJsonPath, + LIVE_PREVIEW_DEBOUNCE, + PING_USAGE_PREVIEW_KEY, + PING_USAGE_PREVIEW_SUCCESS_KEY, +} from '../../constants'; import eventHub from '../../eventhub'; import { createPathWithExt } from '../../utils'; import Navigator from './navigator.vue'; @@ -62,6 +67,15 @@ export default { }; }, }, + watch: { + sandpackReady: { + handler(val) { + if (val) { + this.pingUsage(PING_USAGE_PREVIEW_SUCCESS_KEY); + } + }, + }, + }, mounted() { this.onFilesChangeCallback = debounce(() => this.update(), LIVE_PREVIEW_DEBOUNCE); eventHub.$on('ide.files.change', this.onFilesChangeCallback); @@ -101,7 +115,7 @@ export default { initPreview() { if (!this.mainEntry) return null; - this.pingUsage(); + this.pingUsage(PING_USAGE_PREVIEW_KEY); return this.loadFileContent(this.mainEntry) .then(() => this.$nextTick()) diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 775b6906498..bfe4c3ac271 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -114,3 +114,7 @@ export const LIVE_PREVIEW_DEBOUNCE = 2000; export const MAX_MR_FILES_AUTO_OPEN = 10; export const DEFAULT_BRANCH = 'main'; + +// Ping Usage Metrics Keys +export const PING_USAGE_PREVIEW_KEY = 'web_ide_clientside_preview'; +export const PING_USAGE_PREVIEW_SUCCESS_KEY = 'web_ide_clientside_preview_success'; diff --git a/app/assets/javascripts/ide/stores/modules/clientside/actions.js b/app/assets/javascripts/ide/stores/modules/clientside/actions.js index e36419cd7eb..1a8e665867f 100644 --- a/app/assets/javascripts/ide/stores/modules/clientside/actions.js +++ b/app/assets/javascripts/ide/stores/modules/clientside/actions.js @@ -1,9 +1,9 @@ import axios from '~/lib/utils/axios_utils'; -export const pingUsage = ({ rootGetters }) => { +export const pingUsage = ({ rootGetters }, metricName) => { const { web_url: projectUrl } = rootGetters.currentProject; - const url = `${projectUrl}/service_ping/web_ide_clientside_preview`; + const url = `${projectUrl}/service_ping/${metricName}`; return axios.post(url); }; diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index e09d16cf680..b4ba9aa36e7 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -11,7 +11,9 @@ import { ACTIVE_TAB_QUERY_PARAM_NAME, TAB_QUERY_PARAM_VALUES, MEMBER_STATE_AWAITING, + MEMBER_STATE_ACTIVE, USER_STATE_BLOCKED_PENDING_APPROVAL, + BADGE_LABELS_AWAITING_USER_SIGNUP, BADGE_LABELS_PENDING_OWNER_APPROVAL, } from '../../constants'; import RemoveGroupLinkModal from '../modals/remove_group_link_modal.vue'; @@ -154,8 +156,12 @@ export default { * @see {@link ~/app/serializers/member_entity.rb} * @returns {boolean} */ - isNewUser(memberInviteMetadata) { - return memberInviteMetadata && !memberInviteMetadata.userState; + isNewUser(memberInviteMetadata, memberState) { + return ( + memberInviteMetadata && + !memberInviteMetadata.userState && + memberState !== MEMBER_STATE_ACTIVE + ); }, /** * Returns whether the user is awaiting root approval @@ -204,6 +210,10 @@ export default { * @returns {string} */ inviteBadge(memberInviteMetadata, memberState) { + if (this.isNewUser(memberInviteMetadata, memberState)) { + return BADGE_LABELS_AWAITING_USER_SIGNUP; + } + if (this.shouldAddPendingOwnerApprovalBadge(memberInviteMetadata, memberState)) { return BADGE_LABELS_PENDING_OWNER_APPROVAL; } diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js index 62241eaed04..273f1acebc7 100644 --- a/app/assets/javascripts/members/constants.js +++ b/app/assets/javascripts/members/constants.js @@ -111,6 +111,7 @@ export const MEMBER_STATE_CREATED = 0; export const MEMBER_STATE_AWAITING = 1; export const MEMBER_STATE_ACTIVE = 2; +export const BADGE_LABELS_AWAITING_USER_SIGNUP = __('Awaiting user signup'); export const BADGE_LABELS_PENDING_OWNER_APPROVAL = __('Pending owner approval'); export const DAYS_TO_EXPIRE_SOON = 7; diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 8e32c3b3073..ddf72587ba3 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -5,6 +5,7 @@ import DraftNote from '~/batch_comments/components/draft_note.vue'; import createFlash from '~/flash'; import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave'; import { isLoggedIn } from '~/lib/utils/common_utils'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { s__, __ } from '~/locale'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; @@ -170,12 +171,13 @@ export default { this.expandDiscussion({ discussionId: this.discussion.id }); } }, - cancelReplyForm(shouldConfirm, isDirty) { + async cancelReplyForm(shouldConfirm, isDirty) { if (shouldConfirm && isDirty) { const msg = s__('Notes|Are you sure you want to cancel creating this comment?'); - // eslint-disable-next-line no-alert - if (!window.confirm(msg)) { + const confirmed = await confirmAction(msg); + + if (!confirmed) { return; } } diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index cf432cfb429..f678e19d05d 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -2,8 +2,9 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController before_action :authorize_read_milestone!, only: :milestones + before_action :authorize_read_crm_contact!, only: :contacts - feature_category :team_planning, [:issues, :labels, :milestones, :commands] + feature_category :team_planning, [:issues, :labels, :milestones, :commands, :contacts] feature_category :code_review, [:merge_requests] feature_category :users, [:members] feature_category :snippets, [:snippets] @@ -38,6 +39,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController render json: autocomplete_service.snippets end + def contacts + render json: autocomplete_service.contacts + end + private def autocomplete_service @@ -49,6 +54,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController .new(project, current_user) .execute(params[:type], params[:type_id]) end + + def authorize_read_crm_contact! + render_404 unless can?(current_user, :read_crm_contact, project.root_ancestor) + end end Projects::AutocompleteSourcesController.prepend_mod_with('Projects::AutocompleteSourcesController') diff --git a/app/controllers/projects/service_ping_controller.rb b/app/controllers/projects/service_ping_controller.rb index 00530c09be8..368da8d1ef2 100644 --- a/app/controllers/projects/service_ping_controller.rb +++ b/app/controllers/projects/service_ping_controller.rb @@ -13,6 +13,14 @@ class Projects::ServicePingController < Projects::ApplicationController head(200) end + def web_ide_clientside_preview_success + return render_404 unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled? + + Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_success_count + + head(200) + end + def web_ide_pipelines_count Gitlab::UsageDataCounters::WebIdeCounter.increment_pipelines_count diff --git a/app/finders/crm/contacts_finder.rb b/app/finders/crm/contacts_finder.rb new file mode 100644 index 00000000000..c2d44bec27b --- /dev/null +++ b/app/finders/crm/contacts_finder.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# Finder for retrieving contacts scoped to a group +# +# Arguments: +# current_user - user performing the action. Must have the correct permission level for the group. +# params: +# group: Group, required +module Crm + class ContactsFinder + include Gitlab::Allowable + include Gitlab::Utils::StrongMemoize + + attr_reader :params, :current_user + + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute + return CustomerRelations::Contact.none unless root_group + + root_group.contacts + end + + private + + def root_group + strong_memoize(:root_group) do + group = params[:group]&.root_ancestor + + next unless can?(@current_user, :read_crm_contact, group) + + group + end + end + end +end diff --git a/app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb b/app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb new file mode 100644 index 00000000000..179214666ed --- /dev/null +++ b/app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Preloaders + class SingleHierarchyProjectGroupPlansPreloader + attr_reader :projects + + def initialize(projects_relation) + @projects = projects_relation + end + + def execute + # no-op in FOSS + end + end +end + +Preloaders::SingleHierarchyProjectGroupPlansPreloader.prepend_mod_with('Preloaders::SingleHierarchyProjectGroupPlansPreloader') diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 55f16aa3e3d..e6b1b33a82a 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -33,6 +33,11 @@ module Projects SnippetsFinder.new(current_user, project: project).execute.select([:id, :title]) end + def contacts + Crm::ContactsFinder.new(current_user, group: project.group).execute + .select([:id, :email, :first_name, :last_name]) + end + def labels_as_hash(target) super(target, project_id: project.id, include_ancestor_groups: true) end |