diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-16 03:11:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-16 03:11:15 +0300 |
commit | d089b5729e472d68256aa39fade51e7ed99f042b (patch) | |
tree | d668fe62261e53daa2b2c1a4b4b9019eaadecf06 /app | |
parent | c568cb4dbc0421212a28f3cd5b77223aad8888ba (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
10 files changed, 205 insertions, 27 deletions
diff --git a/app/assets/javascripts/crm/components/organizations_root.vue b/app/assets/javascripts/crm/components/organizations_root.vue index 6d32ba41eae..98b45d0a042 100644 --- a/app/assets/javascripts/crm/components/organizations_root.vue +++ b/app/assets/javascripts/crm/components/organizations_root.vue @@ -1,7 +1,71 @@ <script> -export default {}; +import { GlLoadingIcon, GlTable } from '@gitlab/ui'; +import createFlash from '~/flash'; +import { s__, __ } from '~/locale'; +import getGroupOrganizationsQuery from './queries/get_group_organizations.query.graphql'; + +export default { + components: { + GlLoadingIcon, + GlTable, + }, + inject: ['groupFullPath'], + data() { + return { organizations: [] }; + }, + apollo: { + organizations: { + query() { + return getGroupOrganizationsQuery; + }, + variables() { + return { + groupFullPath: this.groupFullPath, + }; + }, + update(data) { + return this.extractOrganizations(data); + }, + error(error) { + createFlash({ + message: __('Something went wrong. Please try again.'), + error, + captureError: true, + }); + }, + }, + }, + computed: { + isLoading() { + return this.$apollo.queries.organizations.loading; + }, + }, + methods: { + extractOrganizations(data) { + const organizations = data?.group?.organizations?.nodes || []; + return organizations.slice().sort((a, b) => a.name.localeCompare(b.name)); + }, + }, + fields: [ + { key: 'name', sortable: true }, + { key: 'defaultRate', sortable: true }, + { key: 'description', sortable: true }, + ], + i18n: { + emptyText: s__('Crm|No organizations found'), + }, +}; </script> <template> - <div></div> + <div> + <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" /> + <gl-table + v-else + :items="organizations" + :fields="$options.fields" + :empty-text="$options.i18n.emptyText" + show-empty + /> + </div> </template> diff --git a/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql b/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql new file mode 100644 index 00000000000..7c4ec6ec585 --- /dev/null +++ b/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql @@ -0,0 +1,15 @@ +query organizations($groupFullPath: ID!) { + group(fullPath: $groupFullPath) { + __typename + id + organizations { + nodes { + __typename + id + name + defaultRate + description + } + } + } +} diff --git a/app/assets/javascripts/crm/organizations_bundle.js b/app/assets/javascripts/crm/organizations_bundle.js index d4b2dff8f3e..ac9990b9fb4 100644 --- a/app/assets/javascripts/crm/organizations_bundle.js +++ b/app/assets/javascripts/crm/organizations_bundle.js @@ -1,15 +1,25 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import CrmOrganizationsRoot from './components/organizations_root.vue'; +Vue.use(VueApollo); + export default () => { const el = document.getElementById('js-crm-organizations-app'); + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + if (!el) { return false; } return new Vue({ el, + apolloProvider, + provide: { groupFullPath: el.dataset.groupFullPath }, render(createElement) { return createElement(CrmOrganizationsRoot); }, diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index bce823f1106..f0db3e5594b 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -63,9 +63,9 @@ "items": { "type": "object", "properties": { - "if": { - "type": "string" - }, + "if": { "$ref": "#/definitions/if" }, + "changes": { "$ref": "#/definitions/changes" }, + "exists": { "$ref": "#/definitions/exists" }, "variables": { "$ref": "#/definitions/variables" }, "when": { "type": "string", @@ -497,24 +497,9 @@ "type": "object", "additionalProperties": false, "properties": { - "if": { - "type": "string", - "description": "Expression to evaluate whether additional attributes should be provided to the job" - }, - "changes": { - "type": "array", - "description": "Additional attributes will be provided to job if any of the provided paths matches a modified file", - "items": { - "type": "string" - } - }, - "exists": { - "type": "array", - "description": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository", - "items": { - "type": "string" - } - }, + "if": { "$ref": "#/definitions/if" }, + "changes": { "$ref": "#/definitions/changes" }, + "exists": { "$ref": "#/definitions/exists" }, "variables": { "$ref": "#/definitions/variables" }, "when": { "$ref": "#/definitions/when" }, "start_in": { "$ref": "#/definitions/start_in" }, @@ -541,6 +526,24 @@ ] } }, + "if": { + "type": "string", + "description": "Expression to evaluate whether additional attributes should be provided to the job" + }, + "changes": { + "type": "array", + "description": "Additional attributes will be provided to job if any of the provided paths matches a modified file", + "items": { + "type": "string" + } + }, + "exists": { + "type": "array", + "description": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository", + "items": { + "type": "string" + } + }, "variables": { "type": "object", "description": "Defines environment variables for specific jobs. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off.", diff --git a/app/graphql/resolvers/users/groups_resolver.rb b/app/graphql/resolvers/users/groups_resolver.rb index 0c1cdc70163..eafb56d8f4c 100644 --- a/app/graphql/resolvers/users/groups_resolver.rb +++ b/app/graphql/resolvers/users/groups_resolver.rb @@ -20,7 +20,7 @@ module Resolvers description: 'Filter by permissions the user has on groups.' before_connection_authorization do |nodes, current_user| - Preloaders::UserMaxAccessLevelInGroupsPreloader.new(nodes, current_user).execute + Preloaders::GroupPolicyPreloader.new(nodes, current_user).execute end def ready?(**args) diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index a1c6793607f..1a8bd05c42c 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -30,6 +30,8 @@ class AuditEvent < ApplicationRecord scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) } scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) } scope :by_author_id, -> (author_id) { where(author_id: author_id) } + scope :by_entity_username, -> (username) { where(entity_id: find_user_id(username)) } + scope :by_author_username, -> (username) { where(author_id: find_user_id(username)) } after_initialize :initialize_details @@ -106,6 +108,10 @@ class AuditEvent < ApplicationRecord self[name] = self.details[name] = original end end + + def self.find_user_id(username) + User.find_by_username(username)&.id + end end AuditEvent.prepend_mod_with('AuditEvent') diff --git a/app/models/preloaders/group_policy_preloader.rb b/app/models/preloaders/group_policy_preloader.rb new file mode 100644 index 00000000000..95d6e0b5c1f --- /dev/null +++ b/app/models/preloaders/group_policy_preloader.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Preloaders + class GroupPolicyPreloader + def initialize(groups, current_user) + @groups = groups + @current_user = current_user + end + + def execute + Preloaders::UserMaxAccessLevelInGroupsPreloader.new(@groups, @current_user).execute + Preloaders::GroupRootAncestorPreloader.new(@groups, root_ancestor_preloads).execute + end + + private + + def root_ancestor_preloads + [] + end + end +end + +Preloaders::GroupPolicyPreloader.prepend_mod_with('Preloaders::GroupPolicyPreloader') diff --git a/app/models/preloaders/group_root_ancestor_preloader.rb b/app/models/preloaders/group_root_ancestor_preloader.rb new file mode 100644 index 00000000000..3ca713d9635 --- /dev/null +++ b/app/models/preloaders/group_root_ancestor_preloader.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Preloaders + class GroupRootAncestorPreloader + def initialize(groups, root_ancestor_preloads = []) + @groups = groups + @root_ancestor_preloads = root_ancestor_preloads + end + + def execute + return unless ::Feature.enabled?(:use_traversal_ids, default_enabled: :yaml) + + # type == 'Group' condition located on subquery to prevent a filter in the query + root_query = Namespace.joins("INNER JOIN (#{join_sql}) as root_query ON root_query.root_id = namespaces.id") + .select('namespaces.*, root_query.id as source_id') + + root_query = root_query.preload(*@root_ancestor_preloads) if @root_ancestor_preloads.any? + + root_ancestors_by_id = root_query.group_by(&:source_id) + + @groups.each do |group| + group.root_ancestor = root_ancestors_by_id[group.id].first + end + end + + private + + def join_sql + Group.select('id, traversal_ids[1] as root_id').where(id: @groups.map(&:id)).to_sql + end + end +end diff --git a/app/models/preloaders/user_max_access_level_in_groups_preloader.rb b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb index 14f1d271572..bdd76d39ec1 100644 --- a/app/models/preloaders/user_max_access_level_in_groups_preloader.rb +++ b/app/models/preloaders/user_max_access_level_in_groups_preloader.rb @@ -3,7 +3,6 @@ module Preloaders # This class preloads the max access level (role) for the user within the given groups and # stores the values in requests store. - # Will only be able to preload max access level for groups where the user is a direct member class UserMaxAccessLevelInGroupsPreloader include BulkMemberAccessLoad @@ -13,8 +12,17 @@ module Preloaders end def execute + if ::Feature.enabled?(:use_traversal_ids, default_enabled: :yaml) + preload_with_traversal_ids + else + preload_direct_memberships + end + end + + private + + def preload_direct_memberships group_memberships = GroupMember.active_without_invites_and_requests - .non_minimal_access .where(user: @user, source_id: @groups) .group(:source_id) .maximum(:access_level) @@ -23,5 +31,22 @@ module Preloaders merge_value_to_request_store(User, @user.id, group_id, max_access_level) end end + + def preload_with_traversal_ids + max_access_levels = GroupMember.active_without_invites_and_requests + .where(user: @user) + .joins("INNER JOIN (#{traversal_join_sql}) as hierarchy ON members.source_id = hierarchy.traversal_id") + .group('hierarchy.id') + .maximum(:access_level) + + @groups.each do |group| + max_access_level = max_access_levels[group.id] || Gitlab::Access::NO_ACCESS + merge_value_to_request_store(User, @user.id, group.id, max_access_level) + end + end + + def traversal_join_sql + Namespace.select('id, unnest(traversal_ids) as traversal_id').where(id: @groups.map(&:id)).to_sql + end end end diff --git a/app/views/groups/crm/organizations.html.haml b/app/views/groups/crm/organizations.html.haml index b22c095d033..e83dab9fda6 100644 --- a/app/views/groups/crm/organizations.html.haml +++ b/app/views/groups/crm/organizations.html.haml @@ -1,4 +1,4 @@ - breadcrumb_title _('Customer Relations Organizations') - page_title _('Customer Relations Organizations') -#js-crm-organizations-app +#js-crm-organizations-app{ data: { group_full_path: @group.full_path } } |