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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-04 18:08:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-04 18:08:21 +0300
commitead1d2523c029e3219c3b25b43e8f7972c5fe33a (patch)
tree35d1f38d38e3033c08950eb2bae2c829e4c743a2 /app
parent63546c0b11e768f1a82dee9507f27bd31a9fc460 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/code_navigation/components/doc_line.vue22
-rw-r--r--app/assets/javascripts/code_navigation/components/popover.vue5
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue19
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue1
-rw-r--r--app/assets/javascripts/design_management/components/design_sidebar.vue8
-rw-r--r--app/assets/javascripts/notes/components/discussion_reply_placeholder.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/editor_service.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/toolbar_item.vue13
-rw-r--r--app/assets/stylesheets/components/rich_content_editor.scss22
-rw-r--r--app/graphql/resolvers/project_members_resolver.rb21
-rw-r--r--app/graphql/types/project_member_type.rb20
-rw-r--r--app/graphql/types/project_type.rb5
-rw-r--r--app/models/concerns/relative_positioning.rb2
-rw-r--r--app/models/group.rb54
-rw-r--r--app/services/groups/transfer_service.rb1
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