Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-16 00:11:15 +00:00
parent c568cb4dbc
commit d089b5729e
40 changed files with 687 additions and 1867 deletions

View file

@ -119,7 +119,6 @@ overrides:
- '@graphql-eslint'
parserOptions:
parser: '@graphql-eslint/eslint-plugin'
schema: './tmp/tests/graphql/gitlab_schema_apollo.graphql'
operations:
- '{,ee/,jh/}app/**/*.graphql'
# You can run `bundle exec rake gitlab:graphql:schema:dump` and then uncomment this line

2
.gitignore vendored
View file

@ -95,7 +95,7 @@ jsdoc/
webpack-dev-server.json
/.nvimrc
.solargraph.yml
./apollo.config.js
apollo.config.js
/tmp/matching_foss_tests.txt
/tmp/matching_tests.txt
ee/changelogs/unreleased-ee

View file

@ -51,18 +51,13 @@ eslint:
extends:
- .static-analysis-base
- .yarn-cache
- .frontend:rules:default-frontend-jobs
needs: ['graphql-schema-dump']
- .static-analysis:rules:ee
needs: []
variables:
USE_BUNDLE_INSTALL: "false"
script:
- run_timed_command "retry yarn install --frozen-lockfile"
- yarn run apollo client:download-schema --config=config/apollo.config.js tmp/tests/graphql/gitlab_schema_apollo.graphql
- run_timed_command "yarn run lint:eslint:all"
artifacts:
name: graphql-schema-apollo
paths:
- tmp/tests/graphql/gitlab_schema_apollo.graphql
eslint as-if-foss:
extends:

View file

@ -176,7 +176,6 @@ Rails/TimeZone:
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_renamed_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/repository_updated_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/reset_checksum_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/events/upload_deleted_event_spec.rb'
- 'ee/spec/lib/gitlab/geo/log_cursor/logger_spec.rb'
- 'ee/spec/lib/gitlab/git_access_spec.rb'
- 'ee/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb'

View file

@ -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>

View file

@ -0,0 +1,15 @@
query organizations($groupFullPath: ID!) {
group(fullPath: $groupFullPath) {
__typename
id
organizations {
nodes {
__typename
id
name
defaultRate
description
}
}
}
}

View file

@ -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);
},

View file

@ -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.",

View file

@ -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)

View file

@ -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')

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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 } }

View file

@ -1,10 +0,0 @@
module.exports = {
client: {
service: {
name: 'gitlab',
localSchemaFile: './tmp/tests/graphql/gitlab_schema.graphql',
},
includes: ['../{ee/,jh/,}app/assets/javascripts/**/*.{js,graphql}'],
excludes: ['../{ee/,jh/,}spec/{frontend,frontend_integration}/**/*'],
},
};

View file

@ -256,8 +256,8 @@
:when: 2019-09-11 13:08:28.431132000 Z
- - :permit
- "(MIT OR CC0-1.0)"
- :who:
:why:
- :who:
:why:
:versions: []
:when: 2019-11-08 10:03:31.787226000 Z
- - :permit
@ -362,9 +362,3 @@
- - :approve
- 0.0.62
- *2
- - :approve
- sha.js
- :who: Vitaly Slobodin
:why: Dual license BSD-3-Clause and MIT. See https://gitlab.com/gitlab-com/legal-and-compliance/-/issues/686
:versions: []
:when: 2021-11-12 18:08:27.036400090 Z

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
class RemoveGeoUploadDeprecatedFields < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
with_lock_retries do
remove_column :geo_event_log, :upload_deleted_event_id, :bigint
end
end
def down
with_lock_retries do
add_column(:geo_event_log, :upload_deleted_event_id, :bigint) unless column_exists?(:geo_event_log, :upload_deleted_event_id)
end
add_concurrent_foreign_key :geo_event_log, :geo_upload_deleted_events,
column: :upload_deleted_event_id,
name: 'fk_c1f241c70d',
on_delete: :cascade
add_concurrent_index :geo_event_log,
:upload_deleted_event_id,
name: 'index_geo_event_log_on_upload_deleted_event_id',
where: "(upload_deleted_event_id IS NOT NULL)"
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class DropGeoUploadDeletedEventsTable < Gitlab::Database::Migration[1.0]
def up
drop_table :geo_upload_deleted_events
end
def down
create_table :geo_upload_deleted_events, id: :bigserial do |t|
t.integer :upload_id, null: false, index: true
t.string :file_path, null: false
t.integer :model_id, null: false
t.string :model_type, null: false
t.string :uploader, null: false
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
class RemoveOutdatedFieldsFromGeoNodeStatus < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def change
remove_column :geo_node_statuses, :attachments_count, :integer
remove_column :geo_node_statuses, :attachments_synced_count, :integer
remove_column :geo_node_statuses, :attachments_failed_count, :integer
remove_column :geo_node_statuses, :attachments_synced_missing_on_primary_count, :integer
end
end

View file

@ -0,0 +1 @@
bc7974917509bfbda47375299009295bc5a55970b92443dd5d7134075b161279

View file

@ -0,0 +1 @@
483e4cbe2a0be2afbda511f2298e3715abaca29afafeeae26449fc862f49a08f

View file

@ -0,0 +1 @@
c474870a626c909da772a1c9f459f369d50658ce8f585a35e7cc3c7ef64af657

View file

@ -14170,7 +14170,6 @@ CREATE TABLE geo_event_log (
hashed_storage_migrated_event_id bigint,
lfs_object_deleted_event_id bigint,
hashed_storage_attachments_event_id bigint,
upload_deleted_event_id bigint,
job_artifact_deleted_event_id bigint,
reset_checksum_event_id bigint,
cache_invalidation_event_id bigint,
@ -14300,9 +14299,6 @@ CREATE TABLE geo_node_statuses (
lfs_objects_count integer,
lfs_objects_synced_count integer,
lfs_objects_failed_count integer,
attachments_count integer,
attachments_synced_count integer,
attachments_failed_count integer,
last_event_id integer,
last_event_date timestamp without time zone,
cursor_last_event_id integer,
@ -14327,7 +14323,6 @@ CREATE TABLE geo_node_statuses (
wikis_verification_failed_count integer,
lfs_objects_synced_missing_on_primary_count integer,
job_artifacts_synced_missing_on_primary_count integer,
attachments_synced_missing_on_primary_count integer,
repositories_checksummed_count integer,
repositories_checksum_failed_count integer,
repositories_checksum_mismatch_count integer,
@ -14496,24 +14491,6 @@ CREATE SEQUENCE geo_reset_checksum_events_id_seq
ALTER SEQUENCE geo_reset_checksum_events_id_seq OWNED BY geo_reset_checksum_events.id;
CREATE TABLE geo_upload_deleted_events (
id bigint NOT NULL,
upload_id integer NOT NULL,
file_path character varying NOT NULL,
model_id integer NOT NULL,
model_type character varying NOT NULL,
uploader character varying NOT NULL
);
CREATE SEQUENCE geo_upload_deleted_events_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE geo_upload_deleted_events_id_seq OWNED BY geo_upload_deleted_events.id;
CREATE TABLE gitlab_subscription_histories (
id bigint NOT NULL,
gitlab_subscription_created_at timestamp with time zone,
@ -21510,8 +21487,6 @@ ALTER TABLE ONLY geo_repository_updated_events ALTER COLUMN id SET DEFAULT nextv
ALTER TABLE ONLY geo_reset_checksum_events ALTER COLUMN id SET DEFAULT nextval('geo_reset_checksum_events_id_seq'::regclass);
ALTER TABLE ONLY geo_upload_deleted_events ALTER COLUMN id SET DEFAULT nextval('geo_upload_deleted_events_id_seq'::regclass);
ALTER TABLE ONLY gitlab_subscription_histories ALTER COLUMN id SET DEFAULT nextval('gitlab_subscription_histories_id_seq'::regclass);
ALTER TABLE ONLY gitlab_subscriptions ALTER COLUMN id SET DEFAULT nextval('gitlab_subscriptions_id_seq'::regclass);
@ -23129,9 +23104,6 @@ ALTER TABLE ONLY geo_repository_updated_events
ALTER TABLE ONLY geo_reset_checksum_events
ADD CONSTRAINT geo_reset_checksum_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY geo_upload_deleted_events
ADD CONSTRAINT geo_upload_deleted_events_pkey PRIMARY KEY (id);
ALTER TABLE ONLY gitlab_subscription_histories
ADD CONSTRAINT gitlab_subscription_histories_pkey PRIMARY KEY (id);
@ -25950,8 +25922,6 @@ CREATE INDEX index_geo_event_log_on_repository_updated_event_id ON geo_event_log
CREATE INDEX index_geo_event_log_on_reset_checksum_event_id ON geo_event_log USING btree (reset_checksum_event_id) WHERE (reset_checksum_event_id IS NOT NULL);
CREATE INDEX index_geo_event_log_on_upload_deleted_event_id ON geo_event_log USING btree (upload_deleted_event_id) WHERE (upload_deleted_event_id IS NOT NULL);
CREATE INDEX index_geo_hashed_storage_attachments_events_on_project_id ON geo_hashed_storage_attachments_events USING btree (project_id);
CREATE INDEX index_geo_hashed_storage_migrated_events_on_project_id ON geo_hashed_storage_migrated_events USING btree (project_id);
@ -25988,8 +25958,6 @@ CREATE INDEX index_geo_repository_updated_events_on_source ON geo_repository_upd
CREATE INDEX index_geo_reset_checksum_events_on_project_id ON geo_reset_checksum_events USING btree (project_id);
CREATE INDEX index_geo_upload_deleted_events_on_upload_id ON geo_upload_deleted_events USING btree (upload_id);
CREATE INDEX index_gin_ci_pending_builds_on_namespace_traversal_ids ON ci_pending_builds USING gin (namespace_traversal_ids);
CREATE INDEX index_gitlab_subscription_histories_on_gitlab_subscription_id ON gitlab_subscription_histories USING btree (gitlab_subscription_id);
@ -29352,9 +29320,6 @@ ALTER TABLE ONLY design_management_versions
ALTER TABLE ONLY packages_packages
ADD CONSTRAINT fk_c188f0dba4 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_c1f241c70d FOREIGN KEY (upload_deleted_event_id) REFERENCES geo_upload_deleted_events(id) ON DELETE CASCADE;
ALTER TABLE ONLY analytics_cycle_analytics_project_stages
ADD CONSTRAINT fk_c3339bdfc9 FOREIGN KEY (stage_event_hash_id) REFERENCES analytics_cycle_analytics_stage_event_hashes(id) ON DELETE CASCADE;

View file

@ -190,9 +190,6 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_lfs_objects` | Gauge | 10.2 | Total number of LFS objects available on primary | `url` |
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of LFS objects synced on secondary | `url` |
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | `url` |
| `geo_attachments` | Gauge | 10.2 | Total number of file attachments available on primary | `url` |
| `geo_attachments_synced` | Gauge | 10.2 | Number of attachments synced on secondary | `url` |
| `geo_attachments_failed` | Gauge | 10.2 | Number of attachments failed to sync on secondary | `url` |
| `geo_last_event_id` | Gauge | 10.2 | Database ID of the latest event log entry on the primary | `url` |
| `geo_last_event_timestamp` | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | `url` |
| `geo_cursor_last_event_id` | Gauge | 10.2 | Last database ID of the event log processed by the secondary | `url` |
@ -201,7 +198,6 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_last_successful_status_check_timestamp` | Gauge | 10.2 | Last timestamp when the status was successfully updated | `url` |
| `geo_lfs_objects_synced_missing_on_primary` | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | `url` |
| `geo_job_artifacts_synced_missing_on_primary` | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | `url` |
| `geo_attachments_synced_missing_on_primary` | Gauge | 10.7 | Number of attachments marked as synced due to the file missing on the primary | `url` |
| `geo_repositories_checksummed` | Gauge | 10.7 | Number of repositories checksummed on primary | `url` |
| `geo_repositories_checksum_failed` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` |
| `geo_wikis_checksummed` | Gauge | 10.7 | Number of wikis checksummed on primary | `url` |

View file

@ -259,7 +259,8 @@ control over how the Pages daemon runs and serves content in your environment.
| `FF_ENABLE_REDIRECTS` | Feature flag to enable/disable redirects (enabled by default). Read the [redirects documentation](../../user/project/pages/redirects.md#feature-flag-for-redirects) for more information. |
| `FF_ENABLE_PLACEHOLDERS` | Feature flag to enable/disable rewrites (disabled by default). Read the [redirects documentation](../../user/project/pages/redirects.md#feature-flag-for-rewrites) for more information. |
| `use_legacy_storage` | Temporarily-introduced parameter allowing to use legacy domain configuration source and storage. [Removed in 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166). |
| `rate_limit_source_ip` | Rate limit per source IP in number of requests per second. Set to `0` to disable this feature. |
| `rate_limit_source_ip_burst` | Rate limit per source IP maximum burst allowed per second. |
---
## Advanced configuration
@ -1032,6 +1033,38 @@ GitLab Pages are part of the [regular backup](../../raketasks/backup_restore.md)
You should strongly consider running GitLab Pages under a different hostname
than GitLab to prevent XSS attacks.
### Rate limits
> [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/631) in GitLab 14.5.
You can enforce source-IP rate limits to help minimize the risk of a Denial of Service (DoS) attack. GitLab Pages
uses a [token bucket algorithm](https://en.wikipedia.org/wiki/Token_bucket) to enforce rate limiting. By default,
requests that exceed the specified limits are reported but not rejected.
Source-IP rate limits are enforced using the following:
- `rate_limit_source_ip`: Set the maximum threshold in number of requests per second. Set to 0 to disable this feature.
- `rate_limit_source_ip_burst`: Sets the maximum threshold of number of requests allowed in an initial outburst of requests.
For example, when you load a web page that loads a number of resources at the same time.
#### Enable source-IP rate limits
1. Set rate limits in `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_pages['rate_limit_source_ip'] = 20.0
gitlab_pages['rate_limit_source_ip_burst'] = 600
```
1. To reject requests that exceed the specified limits, enable the `FF_ENABLE_RATE_LIMITER` feature flag in
`/etc/gitlab/gitlab.rb`:
```ruby
gitlab_pages['env'] = {'FF_ENABLE_RATE_LIMITER' => 'true'}
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View file

@ -306,11 +306,6 @@ Example response:
"health": "Healthy",
"health_status": "Healthy",
"missing_oauth_application": false,
"attachments_count": 1,
"attachments_synced_count": null,
"attachments_failed_count": null,
"attachments_synced_missing_on_primary_count": 0,
"attachments_synced_in_percentage": "0.00%",
"db_replication_lag_seconds": null,
"lfs_objects_count": 0,
"lfs_objects_synced_count": null,
@ -465,11 +460,6 @@ Example response:
"health": "Healthy",
"health_status": "Healthy",
"missing_oauth_application": false,
"attachments_count": 1,
"attachments_synced_count": 1,
"attachments_failed_count": 0,
"attachments_synced_missing_on_primary_count": 0,
"attachments_synced_in_percentage": "100.00%",
"db_replication_lag_seconds": 0,
"lfs_objects_count": 0,
"lfs_objects_synced_count": 0,
@ -628,11 +618,6 @@ Example response:
"health": "Healthy",
"health_status": "Healthy",
"missing_oauth_application": false,
"attachments_count": 1,
"attachments_synced_count": 1,
"attachments_failed_count": 0,
"attachments_synced_missing_on_primary_count": 0,
"attachments_synced_in_percentage": "100.00%",
"db_replication_lag_seconds": 0,
"lfs_objects_count": 0,
"lfs_objects_synced_count": 0,

View file

@ -231,10 +231,6 @@ We also collect metrics specific to [Geo](../../administration/geo/index.md) sec
"repositories_replication_enabled"=>true,
"repositories_synced_count"=>24,
"repositories_failed_count"=>0,
"attachments_replication_enabled"=>true,
"attachments_count"=>1,
"attachments_synced_count"=>1,
"attachments_failed_count"=>0,
"git_fetch_event_count_weekly"=>nil,
"git_push_event_count_weekly"=>nil,
... other geo node status fields

View file

@ -10,6 +10,7 @@ type: reference, howto
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210181) in GitLab 13.0.
> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5 for paid groups only.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/342327) in GitLab 14.5. Default prefix added.
Project access tokens are similar to [personal access tokens](../../profile/personal_access_tokens.md)
except they are attached to a project rather than a user. They can be used to:
@ -32,6 +33,9 @@ Project access tokens:
For examples of how you can use a project access token to authenticate with the API, see the
[relevant section from our API Docs](../../../api/index.md#personalproject-access-tokens).
NOTE:
For GitLab.com and new self-managed instances, the default prefix is `glpat-`.
## Creating a project access token
1. Log in to GitLab.

View file

@ -220,7 +220,6 @@ geo_repository_deleted_events: :gitlab_main
geo_repository_renamed_events: :gitlab_main
geo_repository_updated_events: :gitlab_main
geo_reset_checksum_events: :gitlab_main
geo_upload_deleted_events: :gitlab_main
gitlab_subscription_histories: :gitlab_main
gitlab_subscriptions: :gitlab_main
gpg_keys: :gitlab_main

View file

@ -10095,6 +10095,9 @@ msgstr ""
msgid "Crm|No contacts found"
msgstr ""
msgid "Crm|No organizations found"
msgstr ""
msgid "Cron Timezone"
msgstr ""
@ -15337,9 +15340,6 @@ msgstr ""
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr ""
msgid "Geo|Could not remove tracking entry for an existing upload."
msgstr ""
msgid "Geo|Data replication lag"
msgstr ""
@ -15613,9 +15613,6 @@ msgstr ""
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr ""
msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr ""
msgid "Geo|URL can't be blank"
msgstr ""
@ -28891,9 +28888,6 @@ msgstr ""
msgid "Removed %{reviewer_text} %{reviewer_references}."
msgstr ""
msgid "Removed %{type} with id %{id}"
msgstr ""
msgid "Removed all labels."
msgstr ""
@ -28918,6 +28912,9 @@ msgstr ""
msgid "Removed time estimate."
msgstr ""
msgid "Removed upload with id %{id}"
msgstr ""
msgid "RemovedProjects|Projects which are removed and are yet to be permanently removed are visible here."
msgstr ""
@ -37206,9 +37203,6 @@ msgstr ""
msgid "Uploading changes to terminal"
msgstr ""
msgid "Uploads"
msgstr ""
msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently deleted after %{deletion_adjourned_period} days on %{date}. Until that time:"
msgstr ""

View file

@ -209,7 +209,6 @@
"@testing-library/dom": "^7.16.2",
"@vue/test-utils": "1.2.0",
"acorn": "^6.3.0",
"apollo": "^2.33.8",
"axios-mock-adapter": "^1.15.0",
"babel-jest": "^26.5.2",
"babel-plugin-dynamic-import-node": "^2.3.3",
@ -264,8 +263,7 @@
},
"resolutions": {
"chokidar": "^3.4.0",
"@types/node": "14.17.5",
"graphql": "^15.4.0"
"@types/node": "14.17.5"
},
"engines": {
"node": ">=12.22.1",

View file

@ -7,6 +7,17 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < GroupBase
class << self
# Force top level group creation via UI if test is executed on dot_com environment
def fabricate!(*args, &prepare_block)
return fabricate_via_browser_ui!(*args, &prepare_block) if Specs::Helpers::ContextSelector.dot_com?
fabricate_via_api!(*args, &prepare_block)
rescue NotImplementedError
fabricate_via_browser_ui!(*args, &prepare_block)
end
end
def initialize
@path = Runtime::Namespace.sandbox_name
end

View file

@ -22,9 +22,7 @@ module QA
end
let(:source_group) do
# top level group can't be created on staging via api, create via UI
fabricate_method = staging? ? :fabricate_via_browser_ui! : :fabricate_via_api!
Resource::Sandbox.send(fabricate_method) do |group|
Resource::Sandbox.fabricate! do |group|
group.api_client = api_client
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
end

View file

@ -48,7 +48,6 @@ RSpec.describe 'Database schema' do
geo_node_statuses: %w[last_event_id cursor_last_event_id],
geo_nodes: %w[oauth_application_id],
geo_repository_deleted_events: %w[project_id],
geo_upload_deleted_events: %w[upload_id model_id],
gitlab_subscription_histories: %w[gitlab_subscription_id hosted_plan_id namespace_id],
identities: %w[user_id],
import_failures: %w[project_id],

View file

@ -45,3 +45,37 @@ export const getGroupContactsQueryResponse = {
},
},
};
export const getGroupOrganizationsQueryResponse = {
data: {
group: {
__typename: 'Group',
id: 'gid://gitlab/Group/26',
organizations: {
nodes: [
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/1',
name: 'Test Inc',
defaultRate: 100,
description: null,
},
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/2',
name: 'ABC Company',
defaultRate: 110,
description: 'VIP',
},
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/3',
name: 'GitLab',
defaultRate: 120,
description: null,
},
],
},
},
},
};

View file

@ -0,0 +1,60 @@
import { GlLoadingIcon } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import OrganizationsRoot from '~/crm/components/organizations_root.vue';
import getGroupOrganizationsQuery from '~/crm/components/queries/get_group_organizations.query.graphql';
import { getGroupOrganizationsQueryResponse } from './mock_data';
jest.mock('~/flash');
describe('Customer relations organizations root app', () => {
Vue.use(VueApollo);
let wrapper;
let fakeApollo;
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findRowByName = (rowName) => wrapper.findAllByRole('row', { name: rowName });
const successQueryHandler = jest.fn().mockResolvedValue(getGroupOrganizationsQueryResponse);
const mountComponent = ({
queryHandler = successQueryHandler,
mountFunction = shallowMountExtended,
} = {}) => {
fakeApollo = createMockApollo([[getGroupOrganizationsQuery, queryHandler]]);
wrapper = mountFunction(OrganizationsRoot, {
provide: { groupFullPath: 'flightjs' },
apolloProvider: fakeApollo,
});
};
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
it('should render loading spinner', () => {
mountComponent();
expect(findLoadingIcon().exists()).toBe(true);
});
it('should render error message on reject', async () => {
mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
await waitForPromises();
expect(createFlash).toHaveBeenCalled();
});
it('renders correct results', async () => {
mountComponent({ mountFunction: mountExtended });
await waitForPromises();
expect(findRowByName(/Test Inc/i)).toHaveLength(1);
expect(findRowByName(/VIP/i)).toHaveLength(1);
expect(findRowByName(/120/i)).toHaveLength(1);
});
});

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Preloaders::GroupPolicyPreloader do
let_it_be(:user) { create(:user) }
let_it_be(:root_parent) { create(:group, :private, name: 'root-1', path: 'root-1') }
let_it_be(:guest_group) { create(:group, name: 'public guest', path: 'public-guest') }
let_it_be(:private_maintainer_group) { create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer', parent: root_parent) }
let_it_be(:private_developer_group) { create(:group, :private, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') }
let_it_be(:public_maintainer_group) { create(:group, :private, name: 'a public maintainer', path: 'a-public-maintainer') }
let(:base_groups) { [guest_group, private_maintainer_group, private_developer_group, public_maintainer_group] }
before_all do
guest_group.add_guest(user)
private_maintainer_group.add_maintainer(user)
private_developer_group.add_developer(user)
public_maintainer_group.add_maintainer(user)
end
it 'avoids N+1 queries when authorizing a list of groups', :request_store do
preload_groups_for_policy(user)
control = ActiveRecord::QueryRecorder.new { authorize_all_groups(user) }
new_group1 = create(:group, :private).tap { |group| group.add_maintainer(user) }
new_group2 = create(:group, :private, parent: private_maintainer_group)
another_root = create(:group, :private, name: 'root-3', path: 'root-3')
new_group3 = create(:group, :private, parent: another_root).tap { |group| group.add_maintainer(user) }
pristine_groups = Group.where(id: base_groups + [new_group1, new_group2, new_group3])
preload_groups_for_policy(user, pristine_groups)
expect { authorize_all_groups(user, pristine_groups) }.not_to exceed_query_limit(control)
end
def authorize_all_groups(current_user, group_list = base_groups)
group_list.each { |group| current_user.can?(:read_group, group) }
end
def preload_groups_for_policy(current_user, group_list = base_groups)
described_class.new(group_list, current_user).execute
end
end

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Preloaders::GroupRootAncestorPreloader do
let_it_be(:user) { create(:user) }
let_it_be(:root_parent1) { create(:group, :private, name: 'root-1', path: 'root-1') }
let_it_be(:root_parent2) { create(:group, :private, name: 'root-2', path: 'root-2') }
let_it_be(:guest_group) { create(:group, name: 'public guest', path: 'public-guest') }
let_it_be(:private_maintainer_group) { create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer', parent: root_parent1) }
let_it_be(:private_developer_group) { create(:group, :private, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') }
let_it_be(:public_maintainer_group) { create(:group, :private, name: 'a public maintainer', path: 'a-public-maintainer', parent: root_parent2) }
let(:root_query_regex) { /\ASELECT.+FROM "namespaces" WHERE "namespaces"."id" = \d+/ }
let(:additional_preloads) { [] }
let(:groups) { [guest_group, private_maintainer_group, private_developer_group, public_maintainer_group] }
let(:pristine_groups) { Group.where(id: groups) }
shared_examples 'executes N matching DB queries' do |expected_query_count, query_method = nil|
it 'executes the specified root_ancestor queries' do
expect do
pristine_groups.each do |group|
root_ancestor = group.root_ancestor
root_ancestor.public_send(query_method) if query_method.present?
end
end.to make_queries_matching(root_query_regex, expected_query_count)
end
it 'strong_memoizes the correct root_ancestor' do
pristine_groups.each do |group|
expected_parent_id = group.root_ancestor.id == group.id ? nil : group.root_ancestor.id
expect(group.parent_id).to eq(expected_parent_id)
end
end
end
context 'when the preloader is used' do
before do
preload_ancestors
end
context 'when no additional preloads are provided' do
it_behaves_like 'executes N matching DB queries', 0
end
context 'when additional preloads are provided' do
let(:additional_preloads) { [:route] }
let(:root_query_regex) { /\ASELECT.+FROM "routes" WHERE "routes"."source_id" = \d+/ }
it_behaves_like 'executes N matching DB queries', 0, :full_path
end
end
context 'when the preloader is not used' do
it_behaves_like 'executes N matching DB queries', 2
end
def preload_ancestors
described_class.new(pristine_groups, additional_preloads).execute
end
end

View file

@ -13,32 +13,47 @@ RSpec.describe Preloaders::UserMaxAccessLevelInGroupsPreloader do
shared_examples 'executes N max member permission queries to the DB' do
it 'executes the specified max membership queries' do
queries = ActiveRecord::QueryRecorder.new do
groups.each { |group| user.can?(:read_group, group) }
expect { groups.each { |group| user.can?(:read_group, group) } }.to make_queries_matching(max_query_regex, expected_query_count)
end
it 'caches the correct access_level for each group' do
groups.each do |group|
access_level_from_db = group.members_with_parents.where(user_id: user.id).group(:user_id).maximum(:access_level)[user.id] || Gitlab::Access::NO_ACCESS
cached_access_level = group.max_member_access_for_user(user)
expect(cached_access_level).to eq(access_level_from_db)
end
max_queries = queries.log.grep(max_query_regex)
expect(max_queries.count).to eq(expected_query_count)
end
end
context 'when the preloader is used', :request_store do
before do
described_class.new(groups, user).execute
end
context 'when user has indirect access to groups' do
let_it_be(:child_maintainer) { create(:group, :private, parent: group1).tap {|g| g.add_maintainer(user)} }
let_it_be(:child_indirect_access) { create(:group, :private, parent: group1) }
it_behaves_like 'executes N max member permission queries to the DB' do
# Will query all groups where the user is not already a member
let(:expected_query_count) { 1 }
end
let(:groups) { [group1, group2, group3, child_maintainer, child_indirect_access] }
context 'when user has access but is not a direct member of the group' do
let(:groups) { [group1, group2, group3, create(:group, :private, parent: group1)] }
context 'when traversal_ids feature flag is disabled' do
it_behaves_like 'executes N max member permission queries to the DB' do
before do
stub_feature_flags(use_traversal_ids: false)
described_class.new(groups, user).execute
end
it_behaves_like 'executes N max member permission queries to the DB' do
# One query for group with no access and another one where the user is not a direct member
let(:expected_query_count) { 2 }
# One query for group with no access and another one per group where the user is not a direct member
let(:expected_query_count) { 2 }
end
end
context 'when traversal_ids feature flag is enabled' do
it_behaves_like 'executes N max member permission queries to the DB' do
before do
stub_feature_flags(use_traversal_ids: true)
described_class.new(groups, user).execute
end
let(:expected_query_count) { 0 }
end
end
end
end

1841
yarn.lock

File diff suppressed because it is too large Load diff