diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-04 18:08:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-04 18:08:21 +0300 |
commit | ead1d2523c029e3219c3b25b43e8f7972c5fe33a (patch) | |
tree | 35d1f38d38e3033c08950eb2bae2c829e4c743a2 /app | |
parent | 63546c0b11e768f1a82dee9507f27bd31a9fc460 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
15 files changed, 157 insertions, 40 deletions
diff --git a/app/assets/javascripts/code_navigation/components/doc_line.vue b/app/assets/javascripts/code_navigation/components/doc_line.vue new file mode 100644 index 00000000000..69d398893d9 --- /dev/null +++ b/app/assets/javascripts/code_navigation/components/doc_line.vue @@ -0,0 +1,22 @@ +<script> +export default { + props: { + language: { + type: String, + required: true, + }, + tokens: { + type: Array, + required: true, + }, + }, +}; +</script> + +<template> + <span class="line" :lang="language"> + <span v-for="(token, tokenIndex) in tokens" :key="tokenIndex" :class="token.class">{{ + token.value + }}</span> + </span> +</template> diff --git a/app/assets/javascripts/code_navigation/components/popover.vue b/app/assets/javascripts/code_navigation/components/popover.vue index 7147ce227e8..df5f89e4faf 100644 --- a/app/assets/javascripts/code_navigation/components/popover.vue +++ b/app/assets/javascripts/code_navigation/components/popover.vue @@ -1,9 +1,11 @@ <script> import { GlButton } from '@gitlab/ui'; +import DocLine from './doc_line.vue'; export default { components: { GlButton, + DocLine, }, props: { position: { @@ -83,8 +85,7 @@ export default { ref="code-output" :class="$options.colorScheme" class="border-0 bg-transparent m-0 code highlight" - v-html="hover.value" - ></pre> + ><doc-line v-for="(tokens, tokenIndex) in hover.tokens" :key="tokenIndex" :language="hover.language" :tokens="tokens"/></pre> <p v-else ref="doc-output" class="p-3 m-0"> {{ hover.value }} </p> diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue index 49cf7265c0d..731cf960dc2 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue @@ -53,6 +53,10 @@ export default { type: Boolean, required: true, }, + discussionWithOpenForm: { + type: String, + required: true, + }, }, apollo: { activeDiscussion: { @@ -127,6 +131,9 @@ export default { isReplyPlaceholderVisible() { return this.areRepliesShown || !this.discussionReplies.length; }, + isFormVisible() { + return this.isFormRendered && this.discussionWithOpenForm === this.discussion.id; + }, }, methods: { addDiscussionComment( @@ -158,14 +165,9 @@ export default { this.discussionComment = ''; }, showForm() { + this.$emit('openForm', this.discussion.id); this.isFormRendered = true; }, - handleReplyFormBlur() { - if (this.discussionComment) { - return; - } - this.isFormRendered = false; - }, toggleResolvedStatus() { this.isResolving = true; this.$apollo @@ -256,10 +258,10 @@ export default { /> <li v-show="isReplyPlaceholderVisible" class="reply-wrapper"> <reply-placeholder - v-if="!isFormRendered" + v-if="!isFormVisible" class="qa-discussion-reply" :button-text="__('Reply...')" - @onMouseDown="showForm" + @onClick="showForm" /> <apollo-mutation v-else @@ -278,7 +280,6 @@ export default { :markdown-preview-path="markdownPreviewPath" @submitForm="mutate" @cancelForm="hideForm" - @onBlur="handleReplyFormBlur" > <template v-if="discussion.resolvable" #resolveCheckbox> <label data-testid="resolve-checkbox"> diff --git a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue index 249258c577e..756da7f55aa 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue @@ -103,7 +103,6 @@ export default { @keydown.meta.enter="submitForm" @keydown.ctrl.enter="submitForm" @keyup.esc.stop="cancelComment" - @blur="$emit('onBlur')" > </textarea> </template> diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue index 1be016ae41e..333ad2557e8 100644 --- a/app/assets/javascripts/design_management/components/design_sidebar.vue +++ b/app/assets/javascripts/design_management/components/design_sidebar.vue @@ -34,6 +34,7 @@ export default { data() { return { isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)), + discussionWithOpenForm: '', }; }, computed: { @@ -78,6 +79,9 @@ export default { this.comment = ''; this.$emit('closeCommentForm'); }, + updateDiscussionWithOpenForm(id) { + this.discussionWithOpenForm = id; + }, }, resolveCommentsToggleText: s__('DesignManagement|Resolved Comments'), cookieKey: 'hide_design_resolved_comments_popover', @@ -114,11 +118,13 @@ export default { :noteable-id="design.id" :markdown-preview-path="markdownPreviewPath" :resolved-discussions-expanded="resolvedDiscussionsExpanded" + :discussion-with-open-form="discussionWithOpenForm" data-testid="unresolved-discussion" @createNoteError="$emit('onDesignDiscussionError', $event)" @updateNoteError="$emit('updateNoteError', $event)" @resolveDiscussionError="$emit('resolveDiscussionError', $event)" @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" + @openForm="updateDiscussionWithOpenForm" /> <template v-if="resolvedDiscussions.length > 0"> <gl-button @@ -158,9 +164,11 @@ export default { :noteable-id="design.id" :markdown-preview-path="markdownPreviewPath" :resolved-discussions-expanded="resolvedDiscussionsExpanded" + :discussion-with-open-form="discussionWithOpenForm" data-testid="resolved-discussion" @error="$emit('onDesignDiscussionError', $event)" @updateNoteError="$emit('updateNoteError', $event)" + @openForm="updateDiscussionWithOpenForm" @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" /> </gl-collapse> diff --git a/app/assets/javascripts/notes/components/discussion_reply_placeholder.vue b/app/assets/javascripts/notes/components/discussion_reply_placeholder.vue index b4d67c9377d..0204169214b 100644 --- a/app/assets/javascripts/notes/components/discussion_reply_placeholder.vue +++ b/app/assets/javascripts/notes/components/discussion_reply_placeholder.vue @@ -17,7 +17,6 @@ export default { class="js-vue-discussion-reply btn btn-text-field" :title="s__('MergeRequests|Add a reply')" @click="$emit('onClick')" - @mousedown.prevent="$emit('onMouseDown')" > {{ buttonText }} </button> diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js index f7cc97f386f..967e49627c2 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js @@ -22,10 +22,9 @@ export const generateToolbarItem = config => { return { type: 'button', options: { - el: buildWrapper({ props: { icon }, class: classes }), + el: buildWrapper({ props: { icon, tooltip }, class: classes }), event, command, - tooltip, }, }; }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/toolbar_item.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/toolbar_item.vue index 58aaeef45f2..4271f6053ed 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/toolbar_item.vue +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/toolbar_item.vue @@ -1,20 +1,27 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; export default { components: { GlIcon, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { icon: { type: String, required: true, }, + tooltip: { + type: String, + required: true, + }, }, }; </script> <template> - <button class="p-0 gl-display-flex toolbar-button"> - <gl-icon class="gl-mx-auto" :name="icon" /> + <button v-gl-tooltip="{ title: tooltip }" class="p-0 gl-display-flex toolbar-button"> + <gl-icon class="gl-mx-auto gl-align-self-center" :name="icon" /> </button> </template> diff --git a/app/assets/stylesheets/components/rich_content_editor.scss b/app/assets/stylesheets/components/rich_content_editor.scss index eca0f1114af..bedd06ec9a1 100644 --- a/app/assets/stylesheets/components/rich_content_editor.scss +++ b/app/assets/stylesheets/components/rich_content_editor.scss @@ -1,4 +1,8 @@ -// Overrides styles from ToastUI editor +/** +* Overrides styles from ToastUI editor +*/ + +// Toolbar buttons .tui-editor-defaultUI-toolbar .toolbar-button { color: $gl-gray-600; border: 0; @@ -9,3 +13,19 @@ border: 0; } } + +// Contextual menu's & popups +.tui-editor-defaultUI .tui-popup-wrapper { + @include gl-overflow-hidden; + @include gl-rounded-base; + @include gl-border-gray-400; + + hr { + @include gl-m-0; + @include gl-bg-gray-400; + } + + button { + @include gl-text-gray-800; + } +} diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb new file mode 100644 index 00000000000..3846531762e --- /dev/null +++ b/app/graphql/resolvers/project_members_resolver.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectMembersResolver < BaseResolver + argument :search, GraphQL::STRING_TYPE, + required: false, + description: 'Search query' + + type Types::ProjectMemberType, null: true + + alias_method :project, :object + + def resolve(**args) + return Member.none unless project.present? + + MembersFinder + .new(project, context[:current_user], params: args) + .execute + end + end +end diff --git a/app/graphql/types/project_member_type.rb b/app/graphql/types/project_member_type.rb new file mode 100644 index 00000000000..afd95aa5136 --- /dev/null +++ b/app/graphql/types/project_member_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + class ProjectMemberType < BaseObject + graphql_name 'ProjectMember' + description 'Member of a project' + + authorize :read_project + + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the member' + + field :access_level, GraphQL::INT_TYPE, null: false, + description: 'Access level of the member' + + field :user, Types::UserType, null: false, + description: 'User that is associated with the member object', + resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.user_id).find } + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 3cb53eb951e..1ea7c2f1421 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -139,6 +139,11 @@ module Types description: 'Issues of the project', resolver: Resolvers::IssuesResolver + field :project_members, + Types::ProjectMemberType.connection_type, + description: 'Members of the project', + resolver: Resolvers::ProjectMembersResolver + field :environments, Types::EnvironmentType.connection_type, null: true, diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb index 1653ecdb305..1d89a4497d9 100644 --- a/app/models/concerns/relative_positioning.rb +++ b/app/models/concerns/relative_positioning.rb @@ -50,7 +50,7 @@ module RelativePositioning # This method takes two integer values (positions) and # calculates the position between them. The range is huge as # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time - # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number + # when we have enough space. If distance is less than IDEAL_DISTANCE, we are calculating an average number. def position_between(pos_before, pos_after) pos_before ||= MIN_POSITION pos_after ||= MAX_POSITION diff --git a/app/models/group.rb b/app/models/group.rb index 04cb6b8b4da..5e835319b1e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -325,15 +325,17 @@ class Group < Namespace def members_with_parents # Avoids an unnecessary SELECT when the group has no parents source_ids = - if parent_id + if has_parent? self_and_ancestors.reorder(nil).select(:id) else id end - GroupMember - .active_without_invites_and_requests - .where(source_id: source_ids) + group_hierarchy_members = GroupMember.active_without_invites_and_requests + .where(source_id: source_ids) + + GroupMember.from_union([group_hierarchy_members, + members_from_self_and_ancestor_group_shares]) end def members_from_self_and_ancestors_with_effective_access_level @@ -398,7 +400,7 @@ class Group < Namespace .first &.access_level - max_member_access || max_member_access_for_user_from_shared_groups(user) || GroupMember::NO_ACCESS + max_member_access || GroupMember::NO_ACCESS end def mattermost_team_params @@ -524,27 +526,39 @@ class Group < Namespace errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.") end - def max_member_access_for_user_from_shared_groups(user) + def members_from_self_and_ancestor_group_shares group_group_link_table = GroupGroupLink.arel_table group_member_table = GroupMember.arel_table - group_group_links_query = GroupGroupLink.where(shared_group_id: self_and_ancestors_ids) + source_ids = + if has_parent? + self_and_ancestors.reorder(nil).select(:id) + else + id + end + + group_group_links_query = GroupGroupLink.where(shared_group_id: source_ids) cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query) cte_alias = cte.table.alias(GroupGroupLink.table_name) - link = GroupGroupLink - .with(cte.to_arel) - .select(smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]], - 'group_access')) - .from([group_member_table, cte.alias_to(group_group_link_table)]) - .where(group_member_table[:user_id].eq(user.id)) - .where(group_member_table[:requested_at].eq(nil)) - .where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id])) - .where(group_member_table[:source_type].eq('Namespace')) - .reorder(Arel::Nodes::Descending.new(group_group_link_table[:group_access])) - .first - - link&.group_access + # Instead of members.access_level, we need to maximize that access_level at + # the respective group_group_links.group_access. + member_columns = GroupMember.attribute_names.map do |column_name| + if column_name == 'access_level' + smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]], + 'access_level') + else + group_member_table[column_name] + end + end + + GroupMember + .with(cte.to_arel) + .select(*member_columns) + .from([group_member_table, cte.alias_to(group_group_link_table)]) + .where(group_member_table[:requested_at].eq(nil)) + .where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id])) + .where(group_member_table[:source_type].eq('Namespace')) end def smallest_value_arel(args, column_alias) diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 3f4de659c78..fbbf4ce8baf 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -88,6 +88,7 @@ module Groups end @group.parent = @new_parent_group + @group.clear_memoization(:self_and_ancestors_ids) @group.save! end |