Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1847ddf46c
commit
0d1ffbe0b6
|
|
@ -18,6 +18,10 @@ stages:
|
|||
- GIT_DEPTH
|
||||
- GIT_STRATEGY
|
||||
|
||||
workflow:
|
||||
auto_cancel:
|
||||
on_new_commit: none
|
||||
|
||||
variables:
|
||||
GIT_DEPTH: 20
|
||||
GIT_STRATEGY: fetch
|
||||
|
|
@ -62,11 +66,16 @@ release-environments-deploy:
|
|||
variables:
|
||||
VERSIONS: "${VERSIONS}"
|
||||
ENVIRONMENT: "${ENVIRONMENT}"
|
||||
before_script:
|
||||
# Make sure pipelines run in order
|
||||
# See https://docs.gitlab.com/ee/ci/resource_groups/index.html#change-the-process-mode
|
||||
- curl --request PUT --data "process_mode=oldest_first" --header "PRIVATE-TOKEN:${ENVIRONMENT_API_TOKEN}" "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/resource_groups/release-environment-${CI_COMMIT_REF_SLUG}"
|
||||
trigger:
|
||||
project: gitlab-com/gl-infra/release-environments
|
||||
branch: main
|
||||
strategy: depend
|
||||
needs: ["release-environments-deploy-env"]
|
||||
resource_group: release-environment-${CI_COMMIT_REF_SLUG}
|
||||
|
||||
release-environments-qa:
|
||||
stage: qa
|
||||
|
|
@ -80,6 +89,7 @@ release-environments-qa:
|
|||
GITLAB_INITIAL_ROOT_PASSWORD: "${RELEASE_ENVIRONMENTS_ROOT_PASSWORD}"
|
||||
QA_PRAEFECT_REPOSITORY_STORAGE: "default"
|
||||
SIGNUP_DISABLED: "true"
|
||||
resource_group: release-environment-${CI_COMMIT_REF_SLUG}
|
||||
|
||||
release-environments-notification-failure:
|
||||
stage: finish
|
||||
|
|
|
|||
|
|
@ -341,7 +341,6 @@
|
|||
- "{,ee/,jh/}{,spec/}app/models/ci/build_trace_chunks/redis{,_base,_trace_chunks}{,_spec}.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/usage_data_counters/{hll_redis_counter,redis_counter}{,_spec}.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/usage/metrics/instrumentations/redis{_metric,hll_metric}{,_spec}.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/usage/metrics/aggregates/sources/redis_hll{,_spec}.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/merge_requests/mergeability/redis_interface{,_spec}.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/markdown_cache/redis/*.rb"
|
||||
- "{,ee/,jh/}{,spec/}lib/{,ee/,jh/}gitlab/redis/**/*.rb"
|
||||
|
|
|
|||
|
|
@ -3,18 +3,4 @@
|
|||
Layout/LineContinuationLeadingSpace:
|
||||
Exclude:
|
||||
- 'ee/lib/tasks/gitlab/geo.rake'
|
||||
- 'lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations.rb'
|
||||
- 'lib/gitlab/ci/parsers/security/validators/schema_validator.rb'
|
||||
- 'lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb'
|
||||
- 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb'
|
||||
- 'lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb'
|
||||
- 'lib/gitlab/github_import/importer/events/changed_reviewer.rb'
|
||||
- 'lib/gitlab/import_export/project/import_task.rb'
|
||||
- 'lib/gitlab/reference_counter.rb'
|
||||
- 'lib/gitlab/tracking/standard_context.rb'
|
||||
- 'qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb'
|
||||
- 'rubocop/cop/graphql/descriptions.rb'
|
||||
- 'rubocop/cop/migration/add_columns_to_wide_tables.rb'
|
||||
- 'rubocop/cop/migration/background_migrations.rb'
|
||||
- 'scripts/lib/glfm/parse_examples.rb'
|
||||
- 'scripts/qa/testcases-check'
|
||||
|
|
|
|||
|
|
@ -2645,7 +2645,6 @@ Layout/LineLength:
|
|||
- 'lib/gitlab/tracking/destinations/snowplow.rb'
|
||||
- 'lib/gitlab/tracking/event_definition.rb'
|
||||
- 'lib/gitlab/usage/metric_definition.rb'
|
||||
- 'lib/gitlab/usage/metrics/aggregates/aggregate.rb'
|
||||
- 'lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb'
|
||||
- 'lib/gitlab/usage/service_ping_report.rb'
|
||||
- 'lib/gitlab/usage_data.rb'
|
||||
|
|
@ -3767,9 +3766,7 @@ Layout/LineLength:
|
|||
- 'spec/lib/gitlab/url_builder_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_definition_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
Layout/SpaceInsidePercentLiteralDelimiters:
|
||||
Exclude:
|
||||
- 'spec/deprecation_toolkit_env.rb'
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
Lint/DeprecatedConstants:
|
||||
Exclude:
|
||||
- 'scripts/pipeline_test_report_builder.rb'
|
||||
- 'spec/scripts/pipeline_test_report_builder_spec.rb'
|
||||
|
|
@ -506,7 +506,6 @@ Lint/UnusedMethodArgument:
|
|||
- 'lib/gitlab/testing/robots_blocker_middleware.rb'
|
||||
- 'lib/gitlab/tracking.rb'
|
||||
- 'lib/gitlab/untrusted_regexp/ruby_syntax.rb'
|
||||
- 'lib/gitlab/usage/metrics/aggregates/sources/redis_hll.rb'
|
||||
- 'lib/gitlab/usage_data.rb'
|
||||
- 'lib/gitlab/usage_data_non_sql_metrics.rb'
|
||||
- 'lib/gitlab/usage_data_queries.rb'
|
||||
|
|
|
|||
|
|
@ -617,7 +617,6 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/lib/ee/gitlab/snippet_search_results_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/template/gitlab_ci_yml_template_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/url_builder_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/usage/metrics/aggregates/aggregate_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/usage/service_ping/payload_keys_processor_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb'
|
||||
|
|
@ -3921,11 +3920,8 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/gitlab/url_sanitizer_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_definition_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/active_user_count_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/aggregated_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/cert_based_clusters_ff_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/collected_data_categories_metric_spec.rb'
|
||||
- 'spec/lib/gitlab/usage/metrics/instrumentations/count_boards_metric_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
677e0ab263cf525c6e9557d33908cf55f0a13bd2
|
||||
7289808c830930bbb1009d7acc5eec458dcdf210
|
||||
|
|
|
|||
4
Gemfile
4
Gemfile
|
|
@ -203,9 +203,9 @@ gem 'seed-fu', '~> 2.3.7' # rubocop:todo Gemfile/MissingFeatureCategory
|
|||
gem 'elasticsearch-model', '~> 7.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'elasticsearch-rails', '~> 7.2', require: 'elasticsearch/rails/instrumentation' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'elasticsearch-api', '7.13.3' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'aws-sdk-core', '~> 3.194.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'aws-sdk-core', '~> 3.196.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'aws-sdk-cloudformation', '~> 1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'aws-sdk-s3', '~> 1.149.1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'aws-sdk-s3', '~> 1.150.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'faraday_middleware-aws-sigv4', '~>0.3.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'typhoeus', '~> 1.4.0' # Used with Elasticsearch to support http keep-alive connections # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@
|
|||
{"name":"aws-eventstream","version":"1.3.0","platform":"ruby","checksum":"f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f"},
|
||||
{"name":"aws-partitions","version":"1.877.0","platform":"ruby","checksum":"9552ed7bbd3700ed1eeb0121c160ceaf64fa5dbaff5a1ff5fe6fd8481ecd9cfd"},
|
||||
{"name":"aws-sdk-cloudformation","version":"1.41.0","platform":"ruby","checksum":"31e47539719734413671edf9b1a31f8673fbf9688549f50c41affabbcb1c6b26"},
|
||||
{"name":"aws-sdk-core","version":"3.194.2","platform":"ruby","checksum":"f925fb739cd093e5834910aed85aba5ac8d1b210f26c2cf51f0daf932cc77567"},
|
||||
{"name":"aws-sdk-core","version":"3.196.0","platform":"ruby","checksum":"a9a8ce8cc133eb80ba6e78c4eb3f2a04b7c74d79962ad7b24e7f5d803ee717a1"},
|
||||
{"name":"aws-sdk-kms","version":"1.76.0","platform":"ruby","checksum":"e7f75013cba9ba357144f66bbc600631c192e2cda9dd572794be239654e2cf49"},
|
||||
{"name":"aws-sdk-s3","version":"1.149.1","platform":"ruby","checksum":"664e608190d42b486dc79b5dc65e7c2240923902a9833063327a9d831226a46a"},
|
||||
{"name":"aws-sdk-s3","version":"1.150.0","platform":"ruby","checksum":"1ce0d42f6c53de4244e457d92296da53bfbd12e716b50d2fa851255fe936c82c"},
|
||||
{"name":"aws-sigv4","version":"1.8.0","platform":"ruby","checksum":"84dd99768b91b93b63d1d8e53ee837cfd06ab402812772a7899a78f9f9117cbc"},
|
||||
{"name":"axe-core-api","version":"4.8.0","platform":"ruby","checksum":"88cf44fdbd5d501ae429f9ca6b37c4a46ba27ac673d478ab688eea3e353da62f"},
|
||||
{"name":"axe-core-rspec","version":"4.9.0","platform":"ruby","checksum":"e5f81fa55af0c421254c98476511c4511e193c5659996f184541f74a1359df3a"},
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ GEM
|
|||
aws-sdk-cloudformation (1.41.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-core (3.194.2)
|
||||
aws-sdk-core (3.196.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
|
|
@ -311,7 +311,7 @@ GEM
|
|||
aws-sdk-kms (1.76.0)
|
||||
aws-sdk-core (~> 3, >= 3.188.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.149.1)
|
||||
aws-sdk-s3 (1.150.0)
|
||||
aws-sdk-core (~> 3, >= 3.194.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
|
|
@ -1921,8 +1921,8 @@ DEPENDENCIES
|
|||
attr_encrypted (~> 3.2.4)!
|
||||
awesome_print
|
||||
aws-sdk-cloudformation (~> 1)
|
||||
aws-sdk-core (~> 3.194.2)
|
||||
aws-sdk-s3 (~> 1.149.1)
|
||||
aws-sdk-core (~> 3.196.0)
|
||||
aws-sdk-s3 (~> 1.150.0)
|
||||
axe-core-rspec (~> 4.9.0)
|
||||
babosa (~> 2.0)
|
||||
base32 (~> 0.3.0)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import FilteredSearchAndSort from '~/groups_projects/components/filtered_search_
|
|||
import { RECENT_SEARCHES_STORAGE_KEY_PROJECTS } from '~/filtered_search/recent_searches_storage_keys';
|
||||
import { queryToObject, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { ACCESS_LEVEL_OWNER_INTEGER } from '~/access_level/constants';
|
||||
import {
|
||||
SORT_OPTIONS,
|
||||
SORT_DIRECTION_ASC,
|
||||
|
|
@ -41,6 +42,21 @@ export default {
|
|||
title: name,
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: 'min_access_level',
|
||||
icon: 'user',
|
||||
title: __('Role'),
|
||||
token: GlFilteredSearchToken,
|
||||
unique: true,
|
||||
operators: OPERATORS_IS,
|
||||
options: [
|
||||
{
|
||||
// Cast to string so it matches value from query string
|
||||
value: ACCESS_LEVEL_OWNER_INTEGER.toString(),
|
||||
title: __('Owner'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
queryAsObject() {
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import StateContainer from '../state_container.vue';
|
||||
import { DETAILED_MERGE_STATUS } from '../../constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
approvalNeeded: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} all required approvals must be given.',
|
||||
),
|
||||
blockingMergeRequests: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} you can only merge after the above items are resolved.',
|
||||
),
|
||||
externalStatusChecksFailed: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} all status checks must pass.',
|
||||
),
|
||||
},
|
||||
components: {
|
||||
BoldText,
|
||||
StateContainer,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
failedText() {
|
||||
if (this.mr.approvals && !this.mr.isApproved) {
|
||||
return this.$options.i18n.approvalNeeded;
|
||||
}
|
||||
if (this.mr.detailedMergeStatus === DETAILED_MERGE_STATUS.BLOCKED_STATUS) {
|
||||
return this.$options.i18n.blockingMergeRequests;
|
||||
}
|
||||
if (this.mr.detailedMergeStatus === DETAILED_MERGE_STATUS.EXTERNAL_STATUS_CHECKS) {
|
||||
return this.$options.i18n.externalStatusChecksFailed;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<span class="gl-ml-3 gl-gl-w-full gl-flex-grow-1 gl-md-mr-3 gl-ml-0! gl-text-body!">
|
||||
<bold-text :message="failedText" />
|
||||
</span>
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
@ -24,12 +24,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="failed" is-collapsible>
|
||||
<bold-text :message="$options.message" />
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -130,14 +130,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
status="scheduled"
|
||||
:is-loading="loading"
|
||||
:actions="actions"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="scheduled" :is-loading="loading" :actions="actions" is-collapsible>
|
||||
<template #loading>
|
||||
<gl-skeleton-loader :width="334" :height="24">
|
||||
<rect x="0" y="0" width="24" height="24" rx="4" />
|
||||
|
|
|
|||
|
|
@ -54,13 +54,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
:actions="actions"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="failed" :actions="actions" is-collapsible>
|
||||
<span class="gl-font-weight-bold">
|
||||
<template v-if="mergeError">{{ mergeError }}</template>
|
||||
{{ s__('mrWidget|This merge request failed to be merged automatically') }}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
status="loading"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="loading" is-collapsible>
|
||||
{{ s__('mrWidget|Checking if merge request can be merged…') }}
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -79,13 +79,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
status="closed"
|
||||
:actions="actions"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="closed" :actions="actions" is-collapsible>
|
||||
<mr-widget-author-time
|
||||
:action-text="s__('mrWidget|Closed by')"
|
||||
:author="mr.metrics.closedBy"
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
<script>
|
||||
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
import userPermissionsQuery from '../../queries/permissions.query.graphql';
|
||||
import conflictsStateQuery from '../../queries/states/conflicts.query.graphql';
|
||||
import StateContainer from '../state_container.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetConflicts',
|
||||
components: {
|
||||
BoldText,
|
||||
GlSkeletonLoader,
|
||||
GlButton,
|
||||
StateContainer,
|
||||
},
|
||||
mixins: [mergeRequestQueryVariablesMixin],
|
||||
apollo: {
|
||||
userPermissions: {
|
||||
query: userPermissionsQuery,
|
||||
variables() {
|
||||
return this.mergeRequestQueryVariables;
|
||||
},
|
||||
update: (data) => data.project?.mergeRequest?.userPermissions || {},
|
||||
},
|
||||
state: {
|
||||
query: conflictsStateQuery,
|
||||
variables() {
|
||||
return this.mergeRequestQueryVariables;
|
||||
},
|
||||
update: (data) => data.project?.mergeRequest || {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
/* TODO: This is providing all store and service down when it
|
||||
only needs a few props */
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userPermissions: {},
|
||||
state: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.userPermissions.loading && this.$apollo.queries.state.loading;
|
||||
},
|
||||
showResolveButton() {
|
||||
return (
|
||||
this.mr.conflictResolutionPath &&
|
||||
this.userPermissions.pushToSourceBranch &&
|
||||
!this.state.sourceBranchProtected
|
||||
);
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
shouldBeRebased: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} fast-forward merge is not possible. To merge this request, first rebase locally.',
|
||||
),
|
||||
shouldBeResolved: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} merge conflicts must be resolved.',
|
||||
),
|
||||
usersWriteBranches: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} Users who can write to the source or target branches can resolve the conflicts.',
|
||||
),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
:is-loading="isLoading"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<template #loading>
|
||||
<gl-skeleton-loader :width="334" :height="24">
|
||||
<rect x="0" y="0" width="24" height="24" rx="4" />
|
||||
<rect x="32" y="2" width="150" height="20" rx="4" />
|
||||
<rect x="190" y="2" width="144" height="20" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</template>
|
||||
<template v-if="!isLoading">
|
||||
<span v-if="state.shouldBeRebased" class="gl-ml-0! gl-text-body!">
|
||||
<bold-text :message="$options.i18n.shouldBeRebased" />
|
||||
</span>
|
||||
<template v-else>
|
||||
<span class="gl-ml-0! gl-text-body! gl-flex-grow-1 gl-w-full gl-md-w-auto gl-mr-2">
|
||||
<bold-text v-if="userPermissions.canMerge" :message="$options.i18n.shouldBeResolved" />
|
||||
<bold-text v-else :message="$options.i18n.usersWriteBranches" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="!isLoading && !state.shouldBeRebased" #actions>
|
||||
<gl-button
|
||||
v-if="showResolveButton"
|
||||
:href="mr.conflictResolutionPath"
|
||||
size="small"
|
||||
variant="confirm"
|
||||
class="gl-align-self-start"
|
||||
data-testid="resolve-conflicts-button"
|
||||
>
|
||||
{{ s__('mrWidget|Resolve conflicts') }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-if="userPermissions.canMerge"
|
||||
size="small"
|
||||
variant="confirm"
|
||||
category="tertiary"
|
||||
data-testid="merge-locally-button"
|
||||
class="js-check-out-modal-trigger gl-align-self-start"
|
||||
>
|
||||
{{ s__('mrWidget|Resolve locally') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
@ -150,13 +150,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<state-container
|
||||
:actions="actions"
|
||||
status="merged"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container :actions="actions" status="merged" is-collapsible>
|
||||
<mr-widget-author-time
|
||||
:action-text="s__('mrWidget|Merged by')"
|
||||
:author="mr.metrics.mergedBy"
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import StatusIcon from '../mr_widget_status_icon.vue';
|
||||
|
||||
const message = s__(
|
||||
'mrWidget|%{boldStart}Ready to be merged automatically.%{boldEnd} Ask someone with write access to this repository to merge this request.',
|
||||
);
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetNotAllowed',
|
||||
message,
|
||||
components: {
|
||||
BoldText,
|
||||
StatusIcon,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="success" />
|
||||
<div class="media-body space-children">
|
||||
<bold-text :message="$options.message" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import StatusIcon from '../mr_widget_status_icon.vue';
|
||||
|
||||
const message = s__(
|
||||
"mrWidget|%{boldStart}Merge blocked:%{boldEnd} pipeline must succeed. It's waiting for a manual action to continue.",
|
||||
);
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetPipelineBlocked',
|
||||
message,
|
||||
components: {
|
||||
BoldText,
|
||||
StatusIcon,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="failed" />
|
||||
<div class="media-body space-children">
|
||||
<bold-text :message="$options.message" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
<script>
|
||||
import { GlButton, GlLink, GlModal, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __, s__ } from '~/locale';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import simplePoll from '~/lib/utils/simple_poll';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
import rebaseQuery from '../../queries/states/rebase.query.graphql';
|
||||
import StateContainer from '../state_container.vue';
|
||||
|
||||
const i18n = {
|
||||
rebaseError: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} the source branch must be rebased onto the target branch.',
|
||||
),
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetRebase',
|
||||
i18n,
|
||||
modal: {
|
||||
id: 'rebase-security-risk-modal',
|
||||
title: s__('mrWidget|Are you sure you want to rebase?'),
|
||||
actionPrimary: {
|
||||
text: s__('mrWidget|Rebase'),
|
||||
attributes: {
|
||||
variant: 'danger',
|
||||
},
|
||||
},
|
||||
actionCancel: {
|
||||
text: __('Cancel'),
|
||||
attributes: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
},
|
||||
runPipelinesInTheParentProjectHelpPath: helpPagePath(
|
||||
'/ci/pipelines/merge_request_pipelines.html',
|
||||
{
|
||||
anchor: 'run-pipelines-in-the-parent-project',
|
||||
},
|
||||
),
|
||||
apollo: {
|
||||
state: {
|
||||
query: rebaseQuery,
|
||||
variables() {
|
||||
return this.mergeRequestQueryVariables;
|
||||
},
|
||||
update: (data) => data.project?.mergeRequest || {},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
BoldText,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlModal,
|
||||
GlSkeletonLoader,
|
||||
StateContainer,
|
||||
},
|
||||
mixins: [mergeRequestQueryVariablesMixin],
|
||||
inject: {
|
||||
canCreatePipelineInTargetProject: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
service: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
state: {},
|
||||
isMakingRequest: false,
|
||||
rebasingError: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.state.loading;
|
||||
},
|
||||
rebaseInProgress() {
|
||||
return this.state.rebaseInProgress;
|
||||
},
|
||||
canPushToSourceBranch() {
|
||||
return this.state.userPermissions?.pushToSourceBranch || false;
|
||||
},
|
||||
targetBranch() {
|
||||
return this.state.targetBranch;
|
||||
},
|
||||
status() {
|
||||
if (this.isLoading) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.rebaseInProgress || this.isMakingRequest) {
|
||||
return 'loading';
|
||||
}
|
||||
if (!this.canPushToSourceBranch && !this.rebaseInProgress) {
|
||||
return 'failed';
|
||||
}
|
||||
return 'success';
|
||||
},
|
||||
showRebaseWithoutPipeline() {
|
||||
return (
|
||||
!this.mr.onlyAllowMergeIfPipelineSucceeds ||
|
||||
(this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.allowMergeOnSkippedPipeline)
|
||||
);
|
||||
},
|
||||
isForkMergeRequest() {
|
||||
return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath;
|
||||
},
|
||||
isLatestPipelineCreatedInTargetProject() {
|
||||
const latestPipeline = this.state.pipelines.nodes[0];
|
||||
|
||||
return latestPipeline?.project?.fullPath === this.mr.targetProjectFullPath;
|
||||
},
|
||||
shouldShowSecurityWarning() {
|
||||
return (
|
||||
this.canCreatePipelineInTargetProject &&
|
||||
this.isForkMergeRequest &&
|
||||
!this.isLatestPipelineCreatedInTargetProject
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
rebase({ skipCi = false } = {}) {
|
||||
this.isMakingRequest = true;
|
||||
this.rebasingError = null;
|
||||
|
||||
this.service
|
||||
.rebase({ skipCi })
|
||||
.then(() => {
|
||||
simplePoll(this.checkRebaseStatus);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.isMakingRequest = false;
|
||||
|
||||
if (error.response && error.response.data && error.response.data.merge_error) {
|
||||
this.rebasingError = error.response.data.merge_error;
|
||||
} else {
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
rebaseWithoutCi() {
|
||||
return this.rebase({ skipCi: true });
|
||||
},
|
||||
tryRebase() {
|
||||
if (this.shouldShowSecurityWarning) {
|
||||
this.$refs.modal.show();
|
||||
} else {
|
||||
this.rebase();
|
||||
}
|
||||
},
|
||||
checkRebaseStatus(continuePolling, stopPolling) {
|
||||
this.service
|
||||
.poll()
|
||||
.then((res) => res.data)
|
||||
.then((res) => {
|
||||
if (res.rebase_in_progress || res.should_be_rebased) {
|
||||
continuePolling();
|
||||
} else {
|
||||
this.isMakingRequest = false;
|
||||
|
||||
if (res.merge_error && res.merge_error.length) {
|
||||
this.rebasingError = res.merge_error;
|
||||
} else {
|
||||
toast(__('Rebase completed'));
|
||||
}
|
||||
|
||||
eventHub.$emit('MRWidgetRebaseSuccess');
|
||||
stopPolling();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.isMakingRequest = false;
|
||||
createAlert({
|
||||
message: __('Something went wrong. Please try again.'),
|
||||
});
|
||||
stopPolling();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<state-container
|
||||
:status="status"
|
||||
:is-loading="isLoading"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<template #loading>
|
||||
<gl-skeleton-loader :width="334" :height="24">
|
||||
<rect x="0" y="0" width="24" height="24" rx="4" />
|
||||
<rect x="32" y="2" width="302" height="20" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</template>
|
||||
<template v-if="!isLoading">
|
||||
<span
|
||||
v-if="rebaseInProgress || isMakingRequest"
|
||||
class="gl-ml-0! gl-text-body!"
|
||||
data-testid="rebase-message"
|
||||
>{{ s__('mrWidget|Rebase in progress') }}</span
|
||||
>
|
||||
<span
|
||||
v-if="!rebaseInProgress && !canPushToSourceBranch"
|
||||
class="gl-text-body! gl-ml-0!"
|
||||
data-testid="rebase-message"
|
||||
>
|
||||
<bold-text :message="$options.i18n.rebaseError" />
|
||||
</span>
|
||||
<div
|
||||
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
|
||||
class="accept-merge-holder clearfix js-toggle-container media gl-md-display-flex gl-flex-wrap gl-flex-grow-1"
|
||||
>
|
||||
<span
|
||||
v-if="!rebasingError"
|
||||
class="gl-w-full gl-md-w-auto gl-flex-grow-1 gl-ml-0! gl-text-body! gl-md-mr-3"
|
||||
data-testid="rebase-message"
|
||||
>
|
||||
<bold-text :message="$options.i18n.rebaseError" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="gl-font-weight-bold danger gl-w-full gl-md-w-auto gl-flex-grow-1 gl-md-mr-3"
|
||||
data-testid="rebase-message"
|
||||
>{{ rebasingError }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!isLoading" #actions>
|
||||
<gl-button
|
||||
:loading="isMakingRequest"
|
||||
variant="confirm"
|
||||
size="small"
|
||||
data-testid="standard-rebase-button"
|
||||
class="gl-align-self-start"
|
||||
@click="tryRebase"
|
||||
>
|
||||
{{ s__('mrWidget|Rebase') }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-if="showRebaseWithoutPipeline"
|
||||
:loading="isMakingRequest"
|
||||
variant="confirm"
|
||||
size="small"
|
||||
category="secondary"
|
||||
data-testid="rebase-without-ci-button"
|
||||
class="gl-align-self-start gl-mr-2"
|
||||
@click="rebaseWithoutCi"
|
||||
>
|
||||
{{ s__('mrWidget|Rebase without pipeline') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</state-container>
|
||||
|
||||
<gl-modal
|
||||
ref="modal"
|
||||
:modal-id="$options.modal.id"
|
||||
:title="$options.modal.title"
|
||||
:action-primary="$options.modal.actionPrimary"
|
||||
:action-cancel="$options.modal.actionCancel"
|
||||
@primary="rebase"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Pipelines|Rebasing creates a pipeline that runs code originating from a forked project merge request. Consequently there are potential security implications, such as the exposure of CI variables.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
"Pipelines|You should review the code thoroughly before running this pipeline with the parent project's CI/CD resources.",
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{ s__('Pipelines|If you are unsure, ask a project maintainer to review it for you.') }}
|
||||
</p>
|
||||
<gl-link :href="$options.runPipelinesInTheParentProjectHelpPath" target="_blank">
|
||||
{{ s__('Pipelines|More Information') }}
|
||||
</gl-link>
|
||||
</gl-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<script>
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { s__ } from '~/locale';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import StatusIcon from '../mr_widget_status_icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'PipelineFailed',
|
||||
components: {
|
||||
BoldText,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
StatusIcon,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
troubleshootingDocsPath() {
|
||||
return helpPagePath('ci/troubleshooting', { anchor: 'merge-request-status-messages' });
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
failedMessage: s__(
|
||||
`mrWidget|%{boldStart}Merge blocked:%{boldEnd} pipeline must succeed. Push a commit that fixes the failure or %{linkStart}learn about other solutions.%{linkEnd}`,
|
||||
),
|
||||
blockedMessage: s__(
|
||||
"mrWidget|%{boldStart}Merge blocked:%{boldEnd} pipeline must succeed. It's waiting for a manual action to continue.",
|
||||
),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="failed" />
|
||||
<div class="media-body space-children">
|
||||
<span>
|
||||
<bold-text v-if="mr.isPipelineBlocked" :message="$options.i18n.blockedMessage" />
|
||||
<gl-sprintf v-else :message="$options.i18n.failedMessage">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="troubleshootingDocsPath" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
<template #bold="{ content }">
|
||||
<span class="gl-font-weight-bold">{{ content }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -317,7 +317,11 @@ export default {
|
|||
return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed;
|
||||
},
|
||||
shouldShowMergeControls() {
|
||||
return this.state.userPermissions?.canMerge && this.mr.state === 'readyToMerge';
|
||||
return (
|
||||
this.state.userPermissions?.canMerge &&
|
||||
!this.mr.autoMergeEnabled &&
|
||||
this.mr.state === 'readyToMerge'
|
||||
);
|
||||
},
|
||||
sourceBranchDeletedText() {
|
||||
const isPreMerge = this.mr.state !== STATUS_MERGED;
|
||||
|
|
@ -354,6 +358,9 @@ export default {
|
|||
'mr.state': function mrStateWatcher() {
|
||||
this.isMakingRequest = false;
|
||||
},
|
||||
'state.autoMergeEnabled': function mrAutoMergeEnabledWatcher() {
|
||||
this.isMakingRequest = false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('ApprovalUpdated', this.updateGraphqlState);
|
||||
|
|
|
|||
|
|
@ -24,12 +24,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<state-container status="failed" is-collapsible>
|
||||
<span
|
||||
class="gl-md-mr-3 gl-flex-grow-1 gl-ml-0! gl-text-body!"
|
||||
data-testid="head-mismatch-content"
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import notesEventHub from '~/notes/event_hub';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import StateContainer from '../state_container.vue';
|
||||
|
||||
const message = s__('mrWidget|%{boldStart}Merge blocked:%{boldEnd} all threads must be resolved.');
|
||||
|
||||
export default {
|
||||
name: 'UnresolvedDiscussions',
|
||||
message,
|
||||
components: {
|
||||
BoldText,
|
||||
GlButton,
|
||||
StateContainer,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
jumpToFirstUnresolvedDiscussion() {
|
||||
notesEventHub.$emit('jumpToFirstUnresolvedDiscussion');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<span class="gl-ml-3 gl-w-full gl-flex-grow-1 gl-md-mr-3 gl-ml-0! gl-text-body!">
|
||||
<bold-text :message="$options.message" />
|
||||
</span>
|
||||
<template #actions>
|
||||
<gl-button
|
||||
data-testid="jump-to-first"
|
||||
class="gl-align-self-start gl-align-top"
|
||||
size="small"
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
@click="jumpToFirstUnresolvedDiscussion"
|
||||
>
|
||||
{{ s__('mrWidget|Go to first unresolved thread') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __, s__ } from '~/locale';
|
||||
import MergeRequest from '~/merge_request';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
|
||||
import getStateQuery from '../../queries/get_state.query.graphql';
|
||||
import draftQuery from '../../queries/states/draft.query.graphql';
|
||||
import removeDraftMutation from '../../queries/toggle_draft.mutation.graphql';
|
||||
import StateContainer from '../state_container.vue';
|
||||
|
||||
// Export for testing
|
||||
export const MSG_SOMETHING_WENT_WRONG = __('Something went wrong. Please try again.');
|
||||
export const MSG_MARK_READY = s__('mrWidget|Mark as ready');
|
||||
|
||||
export default {
|
||||
name: 'WorkInProgress',
|
||||
components: {
|
||||
BoldText,
|
||||
GlButton,
|
||||
StateContainer,
|
||||
},
|
||||
mixins: [mergeRequestQueryVariablesMixin],
|
||||
apollo: {
|
||||
userPermissions: {
|
||||
query: draftQuery,
|
||||
variables() {
|
||||
return this.mergeRequestQueryVariables;
|
||||
},
|
||||
update: (data) => data.project?.mergeRequest?.userPermissions || {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userPermissions: {},
|
||||
isMakingRequest: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleRemoveDraft() {
|
||||
const { mergeRequestQueryVariables } = this;
|
||||
|
||||
this.isMakingRequest = true;
|
||||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: removeDraftMutation,
|
||||
variables: {
|
||||
...mergeRequestQueryVariables,
|
||||
draft: false,
|
||||
},
|
||||
update(
|
||||
store,
|
||||
{
|
||||
data: {
|
||||
mergeRequestSetDraft: {
|
||||
errors,
|
||||
mergeRequest: { mergeableDiscussionsState, draft, title },
|
||||
},
|
||||
},
|
||||
},
|
||||
) {
|
||||
if (errors?.length) {
|
||||
createAlert({
|
||||
message: MSG_SOMETHING_WENT_WRONG,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceData = store.readQuery({
|
||||
query: getStateQuery,
|
||||
variables: mergeRequestQueryVariables,
|
||||
});
|
||||
|
||||
const data = produce(sourceData, (draftState) => {
|
||||
draftState.project.mergeRequest.mergeableDiscussionsState = mergeableDiscussionsState;
|
||||
draftState.project.mergeRequest.draft = draft;
|
||||
draftState.project.mergeRequest.title = title;
|
||||
});
|
||||
|
||||
store.writeQuery({
|
||||
query: getStateQuery,
|
||||
data,
|
||||
variables: mergeRequestQueryVariables,
|
||||
});
|
||||
},
|
||||
optimisticResponse: {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
__typename: 'Mutation',
|
||||
mergeRequestSetDraft: {
|
||||
__typename: 'MergeRequestSetWipPayload',
|
||||
errors: [],
|
||||
mergeRequest: {
|
||||
__typename: 'MergeRequest',
|
||||
id: this.mr.issuableId,
|
||||
mergeableDiscussionsState: true,
|
||||
title: this.mr.title,
|
||||
draft: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(
|
||||
({
|
||||
data: {
|
||||
mergeRequestSetDraft: {
|
||||
mergeRequest: { title },
|
||||
},
|
||||
},
|
||||
}) => {
|
||||
MergeRequest.toggleDraftStatus(title, true);
|
||||
},
|
||||
)
|
||||
.catch(() =>
|
||||
createAlert({
|
||||
message: MSG_SOMETHING_WENT_WRONG,
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
this.isMakingRequest = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
removeDraftStatus: s__(
|
||||
'mrWidget|%{boldStart}Merge blocked:%{boldEnd} Select %{boldStart}Mark as ready%{boldEnd} to remove it from Draft status.',
|
||||
),
|
||||
},
|
||||
MSG_MARK_READY,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<state-container
|
||||
status="failed"
|
||||
is-collapsible
|
||||
:collapsed="mr.mergeDetailsCollapsed"
|
||||
@toggle="() => mr.toggleMergeDetails()"
|
||||
>
|
||||
<span
|
||||
class="gl-display-inline-flex gl-align-self-start gl-pt-2 gl-ml-0! gl-text-body! gl-flex-grow-1"
|
||||
>
|
||||
<bold-text :message="$options.i18n.removeDraftStatus" />
|
||||
</span>
|
||||
<template #actions>
|
||||
<gl-button
|
||||
v-if="userPermissions.updateMergeRequest"
|
||||
size="small"
|
||||
:disabled="isMakingRequest"
|
||||
:loading="isMakingRequest"
|
||||
variant="confirm"
|
||||
class="js-remove-draft gl-md-ml-3 gl-align-self-start"
|
||||
data-testid="removeWipButton"
|
||||
@click="handleRemoveDraft"
|
||||
>
|
||||
{{ $options.MSG_MARK_READY }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</state-container>
|
||||
</template>
|
||||
|
|
@ -24,20 +24,13 @@ import AutoMergeFailed from './components/states/mr_widget_auto_merge_failed.vue
|
|||
import CheckingState from './components/states/mr_widget_checking.vue';
|
||||
import PreparingState from './components/states/mr_widget_preparing.vue';
|
||||
import ClosedState from './components/states/mr_widget_closed.vue';
|
||||
import ConflictsState from './components/states/mr_widget_conflicts.vue';
|
||||
import FailedToMerge from './components/states/mr_widget_failed_to_merge.vue';
|
||||
import MergedState from './components/states/mr_widget_merged.vue';
|
||||
import MergingState from './components/states/mr_widget_merging.vue';
|
||||
import MissingBranchState from './components/states/mr_widget_missing_branch.vue';
|
||||
import NotAllowedState from './components/states/mr_widget_not_allowed.vue';
|
||||
import PipelineBlockedState from './components/states/mr_widget_pipeline_blocked.vue';
|
||||
import RebaseState from './components/states/mr_widget_rebase.vue';
|
||||
import NothingToMergeState from './components/states/nothing_to_merge.vue';
|
||||
import PipelineFailedState from './components/states/pipeline_failed.vue';
|
||||
import ReadyToMergeState from './components/states/ready_to_merge.vue';
|
||||
import ShaMismatch from './components/states/sha_mismatch.vue';
|
||||
import UnresolvedDiscussionsState from './components/states/unresolved_discussions.vue';
|
||||
import WorkInProgressState from './components/states/work_in_progress.vue';
|
||||
import WidgetContainer from './components/widget/app.vue';
|
||||
import {
|
||||
STATE_MACHINE,
|
||||
|
|
@ -71,25 +64,17 @@ export default {
|
|||
MrWidgetClosed: ClosedState,
|
||||
MrWidgetMerging: MergingState,
|
||||
MrWidgetFailedToMerge: FailedToMerge,
|
||||
MrWidgetWip: WorkInProgressState,
|
||||
MrWidgetArchived: ArchivedState,
|
||||
MrWidgetConflicts: ConflictsState,
|
||||
MrWidgetNothingToMerge: NothingToMergeState,
|
||||
MrWidgetNotAllowed: NotAllowedState,
|
||||
MrWidgetMissingBranch: MissingBranchState,
|
||||
MrWidgetReadyToMerge,
|
||||
ShaMismatch,
|
||||
MrWidgetChecking: CheckingState,
|
||||
MrWidgetPreparing: PreparingState,
|
||||
MrWidgetUnresolvedDiscussions: UnresolvedDiscussionsState,
|
||||
MrWidgetPipelineBlocked: PipelineBlockedState,
|
||||
MrWidgetPipelineFailed: PipelineFailedState,
|
||||
MrWidgetAutoMergeEnabled,
|
||||
MrWidgetAutoMergeFailed: AutoMergeFailed,
|
||||
MrWidgetRebase: RebaseState,
|
||||
SourceBranchRemovalStatus,
|
||||
MrWidgetApprovals,
|
||||
MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'),
|
||||
ReadyToMerge: ReadyToMergeState,
|
||||
ReportWidgetContainer,
|
||||
MergeChecks,
|
||||
|
|
@ -243,29 +228,24 @@ export default {
|
|||
hasAlerts() {
|
||||
return this.hasMergeError || this.showMergePipelineForkWarning;
|
||||
},
|
||||
shouldShowMergeDetails() {
|
||||
if (this.mr.state === 'readyToMerge') return true;
|
||||
|
||||
return !this.mr.mergeDetailsCollapsed;
|
||||
},
|
||||
mergeBlockedComponentEnabled() {
|
||||
return (
|
||||
window.gon?.features?.mergeBlockedComponent &&
|
||||
!(
|
||||
[
|
||||
'checking',
|
||||
'preparing',
|
||||
'nothingToMerge',
|
||||
'archived',
|
||||
'missingBranch',
|
||||
'merged',
|
||||
'closed',
|
||||
'merging',
|
||||
'shaMismatch',
|
||||
].includes(this.mr.state) || this.mr.machineValue === 'MERGING'
|
||||
)
|
||||
mergeBlockedComponentVisible() {
|
||||
return !(
|
||||
[
|
||||
'checking',
|
||||
'preparing',
|
||||
'nothingToMerge',
|
||||
'archived',
|
||||
'missingBranch',
|
||||
'merged',
|
||||
'closed',
|
||||
'merging',
|
||||
'shaMismatch',
|
||||
].includes(this.mr.state) || this.mr.machineValue === 'MERGING'
|
||||
);
|
||||
},
|
||||
autoMergeEnabled() {
|
||||
return this.mr.autoMergeEnabled;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'mr.machineValue': {
|
||||
|
|
@ -338,12 +318,6 @@ export default {
|
|||
|
||||
this.bindEventHubListeners();
|
||||
eventHub.$on('mr.discussion.updated', this.checkStatus);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= 768) {
|
||||
this.mr.toggleMergeDetails(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
getServiceEndpoints(store) {
|
||||
return {
|
||||
|
|
@ -581,9 +555,9 @@ export default {
|
|||
</div>
|
||||
|
||||
<div class="mr-widget-section" data-testid="mr-widget-content">
|
||||
<template v-if="mergeBlockedComponentEnabled">
|
||||
<template v-if="mergeBlockedComponentVisible">
|
||||
<mr-widget-auto-merge-enabled
|
||||
v-if="mr.autoMergeEnabled"
|
||||
v-if="autoMergeEnabled"
|
||||
:mr="mr"
|
||||
:service="service"
|
||||
class="gl-border-b-1 gl-border-b-solid gl-border-gray-100"
|
||||
|
|
@ -591,12 +565,7 @@ export default {
|
|||
<merge-checks :mr="mr" :service="service" />
|
||||
</template>
|
||||
<component :is="componentName" v-else :mr="mr" :service="service" />
|
||||
<ready-to-merge
|
||||
v-if="mr.commitsCount"
|
||||
v-show="shouldShowMergeDetails"
|
||||
:mr="mr"
|
||||
:service="service"
|
||||
/>
|
||||
<ready-to-merge v-if="mr.commitsCount" :mr="mr" :service="service" />
|
||||
</div>
|
||||
</div>
|
||||
<mr-widget-pipeline-container
|
||||
|
|
|
|||
|
|
@ -17,35 +17,14 @@ export default function deviseState() {
|
|||
if (this.detailedMergeStatus === DETAILED_MERGE_STATUS.CHECKING) {
|
||||
return stateKey.checking;
|
||||
}
|
||||
if (this.hasConflicts) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.conflicts;
|
||||
}
|
||||
if (this.shouldBeRebased) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.rebase;
|
||||
}
|
||||
if (this.hasMergeChecksFailed && !this.autoMergeEnabled) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.mergeChecksFailed;
|
||||
}
|
||||
if (this.detailedMergeStatus === DETAILED_MERGE_STATUS.CI_MUST_PASS) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.pipelineFailed;
|
||||
}
|
||||
if (this.detailedMergeStatus === DETAILED_MERGE_STATUS.DRAFT_STATUS) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.draft;
|
||||
}
|
||||
if (this.detailedMergeStatus === DETAILED_MERGE_STATUS.DISCUSSIONS_NOT_RESOLVED) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.unresolvedDiscussions;
|
||||
}
|
||||
if (this.canMerge && this.isSHAMismatch) {
|
||||
return stateKey.shaMismatch;
|
||||
}
|
||||
if (this.autoMergeEnabled && !this.mergeError) {
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.autoMergeEnabled;
|
||||
}
|
||||
if (
|
||||
this.detailedMergeStatus === DETAILED_MERGE_STATUS.MERGEABLE ||
|
||||
this.detailedMergeStatus === DETAILED_MERGE_STATUS.CI_STILL_RUNNING
|
||||
) {
|
||||
return stateKey.readyToMerge;
|
||||
}
|
||||
return window.gon?.features?.mergeBlockedComponent ? null : stateKey.checking;
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,10 @@ export default class MergeRequestStore {
|
|||
|
||||
this.stateMachine = machine(STATE_MACHINE.definition);
|
||||
this.machineValue = this.stateMachine.value;
|
||||
this.mergeDetailsCollapsed =
|
||||
!window.gon?.features?.mergeBlockedComponent && window.innerWidth < 768;
|
||||
this.mergeError = data.mergeError;
|
||||
this.multipleApprovalRulesAvailable = data.multiple_approval_rules_available || false;
|
||||
this.id = data.id;
|
||||
this.autoMergeEnabled = false;
|
||||
|
||||
this.setPaths(data);
|
||||
|
||||
|
|
@ -420,12 +419,4 @@ export default class MergeRequestStore {
|
|||
|
||||
this.transitionStateMachine(transitionOptions);
|
||||
}
|
||||
|
||||
toggleMergeDetails(val = !this.mergeDetailsCollapsed) {
|
||||
if (window.gon?.features?.mergeBlockedComponent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mergeDetailsCollapsed = val;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,56 +2,27 @@ export const stateToComponentMap = {
|
|||
merged: 'mr-widget-merged',
|
||||
closed: 'mr-widget-closed',
|
||||
merging: 'mr-widget-merging',
|
||||
conflicts: 'mr-widget-conflicts',
|
||||
missingBranch: 'mr-widget-missing-branch',
|
||||
draft: 'mr-widget-wip',
|
||||
readyToMerge: 'mr-widget-ready-to-merge',
|
||||
nothingToMerge: 'mr-widget-nothing-to-merge',
|
||||
notAllowedToMerge: 'mr-widget-not-allowed',
|
||||
archived: 'mr-widget-archived',
|
||||
checking: 'mr-widget-checking',
|
||||
preparing: 'mr-widget-preparing',
|
||||
unresolvedDiscussions: 'mr-widget-unresolved-discussions',
|
||||
pipelineBlocked: 'mr-widget-pipeline-blocked',
|
||||
pipelineFailed: 'mr-widget-pipeline-failed',
|
||||
autoMergeEnabled: 'mr-widget-auto-merge-enabled',
|
||||
failedToMerge: 'mr-widget-failed-to-merge',
|
||||
autoMergeFailed: 'mr-widget-auto-merge-failed',
|
||||
shaMismatch: 'sha-mismatch',
|
||||
rebase: 'mr-widget-rebase',
|
||||
mergeChecksFailed: 'mergeChecksFailed',
|
||||
};
|
||||
|
||||
export const statesToShowHelpWidget = [
|
||||
'merging',
|
||||
'conflicts',
|
||||
'draft',
|
||||
'readyToMerge',
|
||||
'checking',
|
||||
'unresolvedDiscussions',
|
||||
'pipelineFailed',
|
||||
'pipelineBlocked',
|
||||
'autoMergeFailed',
|
||||
'rebase',
|
||||
];
|
||||
|
||||
export const stateKey = {
|
||||
archived: 'archived',
|
||||
missingBranch: 'missingBranch',
|
||||
nothingToMerge: 'nothingToMerge',
|
||||
preparing: 'preparing',
|
||||
checking: 'checking',
|
||||
conflicts: 'conflicts',
|
||||
draft: 'draft',
|
||||
pipelineFailed: 'pipelineFailed',
|
||||
unresolvedDiscussions: 'unresolvedDiscussions',
|
||||
pipelineBlocked: 'pipelineBlocked',
|
||||
shaMismatch: 'shaMismatch',
|
||||
autoMergeFailed: 'autoMergeFailed',
|
||||
autoMergeEnabled: 'autoMergeEnabled',
|
||||
notAllowedToMerge: 'notAllowedToMerge',
|
||||
readyToMerge: 'readyToMerge',
|
||||
rebase: 'rebase',
|
||||
merging: 'merging',
|
||||
merged: 'merged',
|
||||
mergeChecksFailed: 'mergeChecksFailed',
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
|
||||
push_frontend_feature_flag(:mr_pipelines_graphql, project)
|
||||
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
|
||||
push_frontend_feature_flag(:merge_blocked_component, current_user)
|
||||
push_frontend_feature_flag(:auto_merge_when_incomplete_pipeline_succeeds, project)
|
||||
push_frontend_feature_flag(:pinned_file, project)
|
||||
push_frontend_feature_flag(:reviewer_assign_drawer, current_user)
|
||||
|
|
|
|||
|
|
@ -33,21 +33,20 @@ module Ci
|
|||
|
||||
def above_threshold?(threshold)
|
||||
with_ci_connection do
|
||||
Ci::Partitionable.registered_models.any? do |model|
|
||||
database_partition = model.partitioning_strategy.partition_for_id(id)
|
||||
database_partition && database_partition.data_size > threshold
|
||||
end
|
||||
Gitlab::Database::PostgresPartition
|
||||
.with_parent_tables(parent_table_names)
|
||||
.with_list_constraint(id)
|
||||
.above_threshold(threshold)
|
||||
.exists?
|
||||
end
|
||||
end
|
||||
|
||||
def all_partitions_exist?
|
||||
with_ci_connection do
|
||||
Ci::Partitionable.registered_models.all? do |model|
|
||||
model
|
||||
.partitioning_strategy
|
||||
.partition_for_id(id)
|
||||
.present?
|
||||
end
|
||||
Gitlab::Database::PostgresPartition
|
||||
.with_parent_tables(parent_table_names)
|
||||
.with_list_constraint(id)
|
||||
.count == parent_table_names.size
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -56,5 +55,9 @@ module Ci
|
|||
def with_ci_connection(&block)
|
||||
Gitlab::Database::SharedModel.using_connection(connection, &block)
|
||||
end
|
||||
|
||||
def parent_table_names
|
||||
Ci::Partitionable.registered_models.map(&:table_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ module ServicePing
|
|||
|
||||
SubmissionError = Class.new(StandardError)
|
||||
|
||||
def initialize(skip_db_write: false, payload: nil)
|
||||
@skip_db_write = skip_db_write
|
||||
def initialize(payload: nil)
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ module ServicePing
|
|||
|
||||
private
|
||||
|
||||
attr_reader :payload, :skip_db_write
|
||||
attr_reader :payload
|
||||
|
||||
def metadata(service_ping_payload)
|
||||
{
|
||||
|
|
@ -83,8 +82,6 @@ module ServicePing
|
|||
raise SubmissionError, "Invalid usage_data_id in response: #{version_usage_data_id}"
|
||||
end
|
||||
|
||||
return if skip_db_write
|
||||
|
||||
raw_usage_data = save_raw_usage_data(payload)
|
||||
raw_usage_data.update_version_metadata!(usage_data_id: version_usage_data_id)
|
||||
ServicePing::DevopsReport.new(response).execute
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.top-area
|
||||
.gl-flex
|
||||
= gl_tabs_nav({ class: 'gl-display-flex gl-flex-grow-1 gl-border-none'}) do
|
||||
= gl_tab_link_to _('Most starred'), starred_explore_projects_path, { item_active: current_page?(starred_explore_projects_path) || current_page?(explore_root_path) }
|
||||
= gl_tab_link_to _('Trending'), trending_explore_projects_path
|
||||
|
|
@ -6,4 +6,6 @@
|
|||
= gl_tab_link_to _('Inactive'), explore_projects_path({ archived: 'only' }), { item_active: current_page?(explore_projects_path) && params['archived'] == 'only' }
|
||||
= gl_tab_link_to _('All'), explore_projects_path({ archived: true }), { item_active: current_page?(explore_projects_path) && params['archived'] == 'true' }
|
||||
|
||||
#js-projects-explore-filtered-search-and-sort{ data: { app_data: projects_explore_filtered_search_and_sort_app_data } }
|
||||
#js-projects-explore-filtered-search-and-sort.gl-py-5.gl-border-t.gl-border-b{ data: { app_data: projects_explore_filtered_search_and_sort_app_data } }
|
||||
-# This element takes up space while Vue is rendering to avoid page jump
|
||||
.gl-h-7
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class GitlabServicePingWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
#
|
||||
# See https://github.com/mperham/sidekiq/issues/2372
|
||||
triggered_from_cron = options.fetch('triggered_from_cron', true)
|
||||
skip_db_write = options.fetch('skip_db_write', false)
|
||||
|
||||
# Disable service ping for GitLab.com unless called manually
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/292929 for details
|
||||
|
|
@ -32,7 +31,7 @@ class GitlabServicePingWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
# Splay the request over a minute to avoid thundering herd problems.
|
||||
sleep(rand(0.0..60.0).round(3))
|
||||
|
||||
ServicePing::SubmitService.new(payload: usage_data, skip_db_write: skip_db_write).execute
|
||||
ServicePing::SubmitService.new(payload: usage_data).execute
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: merge_blocked_component
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136454
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432033
|
||||
milestone: '16.6'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: true
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: rate_limit_oauth_api
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133109
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427874
|
||||
milestone: '16.5'
|
||||
type: development
|
||||
group: group::authentication
|
||||
default_enabled: false
|
||||
|
|
@ -33,48 +33,6 @@
|
|||
"instrumentation_class"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"instrumentation_class": {
|
||||
"const": "AggregatedMetric"
|
||||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aggregate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"enum": [
|
||||
"user.id",
|
||||
"project.id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"attribute"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"aggregate",
|
||||
"events"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"instrumentation_class",
|
||||
"options"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"key_path": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropIndexAlertManagementHttpIntegrationsOnProjectId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.1'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE_NAME = :alert_management_http_integrations
|
||||
INDEX_NAME = :index_alert_management_http_integrations_on_project_id
|
||||
COLUMN_NAMES = [:project_id]
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index(TABLE_NAME, COLUMN_NAMES, name: INDEX_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
e59b68e02cc30c3881e9904514b57301dc0bb47ec47a32941165e3f3760e2e61
|
||||
|
|
@ -24487,8 +24487,6 @@ CREATE UNIQUE INDEX index_alert_management_alerts_on_project_id_and_iid ON alert
|
|||
|
||||
CREATE INDEX index_alert_management_alerts_on_prometheus_alert_id ON alert_management_alerts USING btree (prometheus_alert_id) WHERE (prometheus_alert_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_alert_management_http_integrations_on_project_id ON alert_management_http_integrations USING btree (project_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id ON alert_management_alert_user_mentions USING btree (alert_management_alert_id) WHERE (note_id IS NULL);
|
||||
|
||||
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id_and_note_id ON alert_management_alert_user_mentions USING btree (alert_management_alert_id, note_id);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,10 @@ The following example shows a sequence of requests and responses between:
|
|||
- The content delivery network.
|
||||
|
||||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
sequenceDiagram
|
||||
accTitle: Request and response flow
|
||||
accDescr: Describes how requests and responses flow from the user, GitLab, and a CDN.
|
||||
User->>GitLab: GET /project/-/archive/master.zip
|
||||
GitLab->>User: 302 Found
|
||||
Note over User,GitLab: Location: https://cdn.com/project/-/archive/master.zip?token=secure-user-token
|
||||
|
|
|
|||
|
|
@ -111,10 +111,10 @@ Monitored events: i_code_review_user_create_mr
|
|||
+-----------------------------------------------------------------------------+------------------------------+-----------------------+---------------+---------------+
|
||||
| Key Path | Monitored Events | Instrumentation Class | Initial Value | Current Value |
|
||||
+-----------------------------------------------------------------------------+------------------------------+-----------------------+---------------+---------------+
|
||||
| counts_monthly.aggregated_metrics.code_review_category_monthly_active_users | i_code_review_user_create_mr | AggregatedMetric | 13 | 14 |
|
||||
| counts_monthly.aggregated_metrics.code_review_group_monthly_active_users | i_code_review_user_create_mr | AggregatedMetric | 13 | 14 |
|
||||
| counts_weekly.aggregated_metrics.code_review_category_monthly_active_users | i_code_review_user_create_mr | AggregatedMetric | 0 | 1 |
|
||||
| counts_weekly.aggregated_metrics.code_review_group_monthly_active_users | i_code_review_user_create_mr | AggregatedMetric | 0 | 1 |
|
||||
| counts_monthly.aggregated_metrics.code_review_category_monthly_active_users | i_code_review_user_create_mr | RedisHLLMetric | 13 | 14 |
|
||||
| counts_monthly.aggregated_metrics.code_review_group_monthly_active_users | i_code_review_user_create_mr | RedisHLLMetric | 13 | 14 |
|
||||
| counts_weekly.aggregated_metrics.code_review_category_monthly_active_users | i_code_review_user_create_mr | RedisHLLMetric | 0 | 1 |
|
||||
| counts_weekly.aggregated_metrics.code_review_group_monthly_active_users | i_code_review_user_create_mr | RedisHLLMetric | 0 | 1 |
|
||||
| redis_hll_counters.code_review.i_code_review_user_create_mr_monthly | i_code_review_user_create_mr | RedisHLLMetric | 8 | 9 |
|
||||
| redis_hll_counters.code_review.i_code_review_user_create_mr_weekly | i_code_review_user_create_mr | RedisHLLMetric | 0 | 1 |
|
||||
+-----------------------------------------------------------------------------+------------------------------+-----------------------+---------------+---------------+
|
||||
|
|
|
|||
|
|
@ -292,7 +292,6 @@ To declare an aggregate of metrics based on events collected from database, foll
|
|||
these steps:
|
||||
|
||||
1. [Persist the metrics for aggregation](#persist-metrics-for-aggregation).
|
||||
1. [Add new aggregated metric definition](#add-new-aggregated-metric-definition).
|
||||
|
||||
#### Persist metrics for aggregation
|
||||
|
||||
|
|
@ -337,51 +336,6 @@ class UsageData
|
|||
end
|
||||
```
|
||||
|
||||
#### Add new aggregated metric definition
|
||||
|
||||
After all metrics are persisted, you can add an aggregated metric definition.
|
||||
To declare the aggregate of metrics collected with [Estimated Batch Counters](#estimated-batch-counters),
|
||||
you must fulfill the following requirements:
|
||||
|
||||
- Metrics names listed in the `events:` attribute, have to use the same names you passed in the `metric_name` argument while persisting metrics in previous step.
|
||||
- Every metric listed in the `events:` attribute, has to be persisted for **every** selected `time_frame:` value.
|
||||
|
||||
### Availability-restrained Aggregated metrics
|
||||
|
||||
If the Aggregated metric should only be available in the report under specific conditions, then you must specify these conditions in a new class that is a child of the `AggregatedMetric` class.
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Metrics
|
||||
module Instrumentations
|
||||
class MergeUsageCountAggregatedMetric < AggregatedMetric
|
||||
available? { Feature.enabled?(:merge_usage_data_missing_key_paths) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You must also use the class's name in the YAML setup.
|
||||
|
||||
```yaml
|
||||
time_frame: 28d
|
||||
instrumentation_class: MergeUsageCountAggregatedMetric
|
||||
data_source: redis_hll
|
||||
options:
|
||||
aggregate:
|
||||
attribute: user.id
|
||||
events:
|
||||
- `incident_management_alert_status_changed`
|
||||
- `incident_management_alert_assigned`
|
||||
- `incident_management_alert_todo`
|
||||
- `incident_management_alert_create_incident`
|
||||
```
|
||||
|
||||
## Numbers metrics
|
||||
|
||||
- `operation`: Operations for the given `data` block. Currently we only support `add` operation.
|
||||
|
|
|
|||
|
|
@ -45,10 +45,8 @@ with the _upstream_ project after merge.
|
|||
|
||||
- The Git LFS original v1 API is unsupported.
|
||||
- Even when Git communicates with the repository over SSH, Git LFS objects still use HTTPS.
|
||||
- Git LFS requests use HTTPS credentials, which means:
|
||||
- You should use a good Git [credentials store](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
- If your GitLab server uses HTTP instead, you must
|
||||
[add the URL to Git configuration manually](troubleshooting.md#getsockopt-connection-refused).
|
||||
- Git LFS requests use HTTPS credentials, which means you should use a good Git
|
||||
[credentials store](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
- [Group wikis](../../../user/project/wiki/group.md) do not support Git LFS.
|
||||
|
||||
## Add a file with Git LFS
|
||||
|
|
|
|||
|
|
@ -8,14 +8,126 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
|||
|
||||
When working with Git LFS, you might encounter the following issues.
|
||||
|
||||
## Error: repository or object not found
|
||||
|
||||
This error can occur for a few reasons, including:
|
||||
|
||||
- **You don't have permissions to access certain LFS object.** Confirm you have
|
||||
permission to push to the project, or fetch from the project.
|
||||
- **The project isn't allowed to access the LFS object.** The LFS object you want
|
||||
to push (or fetch) is no longer available to the project. In most cases, the object
|
||||
has been removed from the server.
|
||||
- **The local Git repository is using deprecated version of the Git LFS API.** Update
|
||||
your local copy of Git LFS and try again.
|
||||
|
||||
## Invalid status for `<url>` : 501
|
||||
|
||||
Git LFS logs the failures into a log file. To view this log file:
|
||||
|
||||
1. In your terminal window, go to your project's directory.
|
||||
1. Run this command to see recent log files:
|
||||
|
||||
```shell
|
||||
git lfs logs last
|
||||
```
|
||||
|
||||
These problems can cause `501` errors:
|
||||
|
||||
- Git LFS is not enabled in your project's settings. Check your project settings and
|
||||
enable Git LFS.
|
||||
|
||||
- Git LFS support is not enabled on the GitLab server. Check with your GitLab
|
||||
administrator why Git LFS is not enabled on the server. See
|
||||
[LFS administration documentation](../../../administration/lfs/index.md) for instructions
|
||||
on how to enable Git LFS support.
|
||||
|
||||
- The Git LFS client version is not supported by GitLab server. You should:
|
||||
1. Check your Git LFS version with `git lfs version`.
|
||||
1. Check the Git configuration of your project for traces of the deprecated API
|
||||
with `git lfs -l`. If your configuration sets `batch = false`,
|
||||
remove the line, then update your Git LFS client. GitLab supports only
|
||||
versions 1.0.1 and newer.
|
||||
|
||||
## Credentials are always required when pushing an object
|
||||
|
||||
Git LFS authenticates the user with HTTP Basic Authentication on every push for
|
||||
every object, so it requires user HTTPS credentials. By default, Git supports
|
||||
remembering the credentials for each repository you use. For more information, see
|
||||
the [official Git documentation](https://git-scm.com/docs/gitcredentials).
|
||||
|
||||
For example, you can tell Git to remember your password for a period of time in
|
||||
which you expect to push objects. This example remembers your credentials for an hour
|
||||
(3600 seconds), and you must authenticate again in an hour:
|
||||
|
||||
```shell
|
||||
git config --global credential.helper 'cache --timeout=3600'
|
||||
```
|
||||
|
||||
To store and encrypt credentials, see:
|
||||
|
||||
- MacOS: use `osxkeychain`.
|
||||
- Windows: use `wincred` or Microsoft's
|
||||
[Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
|
||||
|
||||
To learn more about storing your user credentials, see the
|
||||
[Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
|
||||
## LFS objects are missing on push
|
||||
|
||||
GitLab checks files on push to detect LFS pointers. If it detects LFS pointers,
|
||||
GitLab tries to verify that those files already exist in LFS. If you use a separate
|
||||
server for Git LFS, and you encounter this problem:
|
||||
|
||||
1. Verify you have installed Git LFS locally.
|
||||
1. Consider a manual push with `git lfs push --all`.
|
||||
|
||||
If you store Git LFS files outside of GitLab, you can
|
||||
[disable Git LFS](index.md#enable-or-disable-git-lfs-for-a-project) on your project.
|
||||
|
||||
## Hosting LFS objects externally
|
||||
|
||||
You can host LFS objects externally by setting a custom LFS URL:
|
||||
|
||||
```shell
|
||||
git config -f .lfsconfig lfs.url https://example.com/<project>.git/info/lfs
|
||||
```
|
||||
|
||||
You might do this if you store LFS data on an appliance, like a Nexus Repository.
|
||||
If you use an external LFS store, GitLab can't verify the LFS objects. Pushes then
|
||||
fail if you have GitLab LFS support enabled.
|
||||
|
||||
To stop push failures, you can disable Git LFS support in your
|
||||
[Project settings](index.md#enable-or-disable-git-lfs-for-a-project). However, this approach
|
||||
might not be desirable, because it also disables GitLab LFS features like:
|
||||
|
||||
- Verifying LFS objects.
|
||||
- GitLab UI integration for LFS.
|
||||
|
||||
## I/O timeout when pushing LFS objects
|
||||
|
||||
If your network conditions are unstable, the Git LFS client might time out when trying to upload files.
|
||||
You might see errors like:
|
||||
|
||||
```shell
|
||||
LFS: Put "http://example.com/root/project.git/gitlab-lfs/objects/<OBJECT-ID>/15":
|
||||
read tcp your-instance-ip:54544->your-instance-ip:443: i/o timeout
|
||||
error: failed to push some refs to 'ssh://example.com:2222/root/project.git'
|
||||
```
|
||||
|
||||
To fix this problem, set the client activity timeout a higher value. For example,
|
||||
to set the timeout to 60 seconds:
|
||||
|
||||
```shell
|
||||
git config lfs.activitytimeout 60
|
||||
```
|
||||
|
||||
## Encountered `n` files that should have been pointers, but weren't
|
||||
|
||||
This error indicates the files are expected to be tracked by LFS, but
|
||||
the repository is not tracking them as LFS. This issue can be one
|
||||
potential reason for this error:
|
||||
[Files not tracked with LFS when uploaded through the web interface](https://gitlab.com/gitlab-org/gitlab/-/issues/326342#note_586820485)
|
||||
This error indicates the repository should be tracking a file with Git LFS, but
|
||||
isn't. [Issue 326342](https://gitlab.com/gitlab-org/gitlab/-/issues/326342#note_586820485),
|
||||
fixed in GitLab 16.10, was one cause of this problem.
|
||||
|
||||
To resolve the problem, migrate the affected file (or files) and push back to the repository:
|
||||
To fix the problem, migrate the affected files, and push them up to the repository:
|
||||
|
||||
1. Migrate the file to LFS:
|
||||
|
||||
|
|
@ -35,128 +147,3 @@ To resolve the problem, migrate the affected file (or files) and push back to th
|
|||
git reflog expire --expire-unreachable=now --all
|
||||
git gc --prune=now
|
||||
```
|
||||
|
||||
## error: Repository or object not found
|
||||
|
||||
This error can occur for a few reasons, including:
|
||||
|
||||
- You don't have permissions to access certain LFS object
|
||||
|
||||
Check if you have permissions to push to the project or fetch from the project.
|
||||
|
||||
- Project is not allowed to access the LFS object
|
||||
|
||||
LFS object you are trying to push to the project or fetch from the project is not
|
||||
available to the project anymore. Probably the object was removed from the server.
|
||||
|
||||
- Local Git repository is using deprecated LFS API
|
||||
|
||||
## Invalid status for `<url>` : 501
|
||||
|
||||
Git LFS logs the failures into a log file.
|
||||
To view this log file, while in project directory:
|
||||
|
||||
```shell
|
||||
git lfs logs last
|
||||
```
|
||||
|
||||
If the status `error 501` is shown, it is because:
|
||||
|
||||
- Git LFS is not enabled in project settings. Check your project settings and
|
||||
enable Git LFS.
|
||||
|
||||
- Git LFS support is not enabled on the GitLab server. Check with your GitLab
|
||||
administrator why Git LFS is not enabled on the server. See
|
||||
[LFS administration documentation](../../../administration/lfs/index.md) for instructions
|
||||
on how to enable LFS support.
|
||||
|
||||
- Git LFS client version is not supported by GitLab server. Check your Git LFS
|
||||
version with `git lfs version`. Check the Git configuration of the project for traces
|
||||
of deprecated API with `git lfs -l`. If `batch = false` is set in the configuration,
|
||||
remove the line and try to update your Git LFS client. Only version 1.0.1 and
|
||||
newer are supported.
|
||||
|
||||
## `getsockopt: connection refused`
|
||||
|
||||
If you push an LFS object to a project and receive an error like this,
|
||||
the LFS client is trying to reach GitLab through HTTPS. However, your GitLab
|
||||
instance is being served on HTTP:
|
||||
|
||||
```plaintext
|
||||
Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused
|
||||
```
|
||||
|
||||
This behavior is caused by Git LFS using HTTPS connections by default when a
|
||||
`lfsurl` is not set in the Git configuration.
|
||||
|
||||
To prevent this from happening, set the LFS URL in project Git configuration:
|
||||
|
||||
```shell
|
||||
git config --add lfs.url "http://gitlab.example.com/group/my-sample-project.git/info/lfs"
|
||||
```
|
||||
|
||||
## Credentials are always required when pushing an object
|
||||
|
||||
NOTE:
|
||||
With 8.12 GitLab added LFS support to SSH. The Git LFS communication
|
||||
still goes over HTTP, but now the SSH client passes the correct credentials
|
||||
to the Git LFS client. No action is required by the user.
|
||||
|
||||
Git LFS authenticates the user with HTTP Basic Authentication on every push for
|
||||
every object, so user HTTPS credentials are required.
|
||||
|
||||
By default, Git has support for remembering the credentials for each repository
|
||||
you use. For more information, see the [official Git documentation](https://git-scm.com/docs/gitcredentials).
|
||||
|
||||
For example, you can tell Git to remember the password for a period of time in
|
||||
which you expect to push the objects:
|
||||
|
||||
```shell
|
||||
git config --global credential.helper 'cache --timeout=3600'
|
||||
```
|
||||
|
||||
This remembers the credentials for an hour, after which Git operations
|
||||
require re-authentication.
|
||||
|
||||
If you are using OS X you can use `osxkeychain` to store and encrypt your credentials.
|
||||
For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
|
||||
|
||||
More details about various methods of storing the user credentials can be found
|
||||
on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
|
||||
## LFS objects are missing on push
|
||||
|
||||
GitLab checks files to detect LFS pointers on push. If LFS pointers are detected, GitLab tries to verify that those files already exist in LFS on GitLab.
|
||||
|
||||
Verify that LFS is installed locally and consider a manual push with `git lfs push --all`.
|
||||
|
||||
If you are storing LFS files outside of GitLab you can disable LFS on the project by setting `lfs_enabled: false` with the [projects API](../../../api/projects.md#edit-project).
|
||||
|
||||
## Hosting LFS objects externally
|
||||
|
||||
It is possible to host LFS objects externally by setting a custom LFS URL with `git config -f .lfsconfig lfs.url https://example.com/<project>.git/info/lfs`.
|
||||
|
||||
You might choose to do this if you are using an appliance like a Nexus Repository to store LFS data. If you choose to use an external LFS store,
|
||||
GitLab can't verify LFS objects. Pushes then fail if you have GitLab LFS support enabled.
|
||||
|
||||
To stop push failure, LFS support can be disabled in the [Project settings](index.md#enable-or-disable-git-lfs-for-a-project), which also disables GitLab LFS value-adds (Verifying LFS objects, UI integration for LFS).
|
||||
|
||||
## I/O timeout when pushing LFS objects
|
||||
|
||||
You might get an error that states:
|
||||
|
||||
```shell
|
||||
LFS: Put "http://your-instance.com/root/project.git/gitlab-lfs/objects/cc29e205d04a4062d0fb131700e8bfc8e54c44d0176a8dca22f40b24ef26d325/15": read tcp your-instance-ip:54544->your-instance-ip:443: i/o timeout
|
||||
error: failed to push some refs to 'ssh://your-instance.com:2222/root/project.git'
|
||||
```
|
||||
|
||||
When network conditions are unstable, the Git LFS client might time out when trying to upload files
|
||||
if network conditions are unstable.
|
||||
|
||||
The workaround is to set the client activity timeout a higher value.
|
||||
|
||||
For example, to set the timeout to 60 seconds:
|
||||
|
||||
```shell
|
||||
git config lfs.activitytimeout 60
|
||||
```
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ DETAILS:
|
|||
To use this feature:
|
||||
|
||||
- The parent group of the project must:
|
||||
- Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features).
|
||||
- Enable the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
- You must:
|
||||
- Belong to at least one group with the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled.
|
||||
- Belong to at least one group with the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features) enabled.
|
||||
- Have sufficient permissions to view the project.
|
||||
|
||||
GitLab can help you get up to speed faster if you:
|
||||
|
|
@ -74,9 +74,9 @@ DETAILS:
|
|||
To use this feature:
|
||||
|
||||
- The parent group of the issue must:
|
||||
- Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features).
|
||||
- Enable the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
- You must:
|
||||
- Belong to at least one group with the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled.
|
||||
- Belong to at least one group with the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features) enabled.
|
||||
- Have sufficient permissions to view the issue.
|
||||
|
||||
You can generate a summary of discussions on an issue:
|
||||
|
|
@ -104,9 +104,9 @@ DETAILS:
|
|||
To use this feature:
|
||||
|
||||
- The parent group of the project must:
|
||||
- Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features).
|
||||
- Enable the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
- You must:
|
||||
- Belong to at least one group with the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled.
|
||||
- Belong to at least one group with the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features) enabled.
|
||||
- Have sufficient permissions to view the CI/CD analytics.
|
||||
|
||||
In CI/CD Analytics, you can view a forecast of deployment frequency:
|
||||
|
|
@ -136,9 +136,9 @@ DETAILS:
|
|||
To use this feature:
|
||||
|
||||
- The parent group of the project must:
|
||||
- Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features).
|
||||
- Enable the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
- You must:
|
||||
- Belong to at least one group with the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled.
|
||||
- Belong to at least one group with the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features) enabled.
|
||||
- Have sufficient permissions to view the CI/CD job.
|
||||
|
||||
When the feature is available, the "Root cause analysis" button will appears on
|
||||
|
|
@ -157,9 +157,9 @@ DETAILS:
|
|||
To use this feature:
|
||||
|
||||
- The parent group of the project must:
|
||||
- Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features).
|
||||
- Enable the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
- You must:
|
||||
- Belong to at least one group with the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled.
|
||||
- Belong to at least one group with the [experiment and beta features setting](ai_features_enable.md#turn-on-beta-and-experimental-features) enabled.
|
||||
- Have sufficient permissions to view the issue.
|
||||
|
||||
You can generate the description for an issue from a short summary.
|
||||
|
|
|
|||
|
|
@ -134,19 +134,37 @@ to override the setting for specific groups or projects.
|
|||
|
||||
## Turn on Beta and Experimental features
|
||||
|
||||
Features listed as Experiment and Beta are turned off by default.
|
||||
GitLab Duo features that are Experiment and Beta are turned off by default.
|
||||
These features are subject to the [Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/).
|
||||
|
||||
### On GitLab.com
|
||||
|
||||
You can turn on Experiment and Beta features for your group on GitLab.com.
|
||||
DETAILS:
|
||||
**Tier:** Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) in GitLab 16.0.
|
||||
> - [Added to GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147833) in GitLab 16.11.
|
||||
|
||||
You can turn on GitLab Duo Experiment and Beta features for your group on GitLab.com.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role in the top-level group.
|
||||
|
||||
To turn on Beta and Experimental GitLab Duo features, use the [Experiment and Beta features checkbox](group/manage.md#enable-experiment-and-beta-features).
|
||||
To turn on GitLab Duo Experiment and Beta features for a top-level group:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. Under **Experiment and Beta features**, select the **Use Experiment and Beta features** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
This setting [cascades to all projects](../user/project/merge_requests/approvals/settings.md#cascade-settings-from-the-instance-or-top-level-group)
|
||||
that belong to the group.
|
||||
|
||||
### On self-managed
|
||||
|
||||
To enable Beta and Experimental GitLab Duo features for GitLab versions where GitLab Duo Chat is not yet generally available, see the [GitLab Duo Chat documentation](gitlab_duo_chat_enable.md#for-self-managed).
|
||||
To enable GitLab Duo Beta and Experimental features for GitLab versions
|
||||
where GitLab Duo Chat is not yet generally available, see the
|
||||
[GitLab Duo Chat documentation](gitlab_duo_chat_enable.md#for-self-managed).
|
||||
|
|
|
|||
|
|
@ -299,7 +299,8 @@ DETAILS:
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- The parent group of the project must have [experiment and beta features enabled](../group/manage.md#enable-experiment-and-beta-features).
|
||||
- The top-level group of the project must have GitLab Duo
|
||||
[experiment and beta features enabled](../ai_features_enable.md#turn-on-beta-and-experimental-features).
|
||||
|
||||
To generate a custom visualization with GitLab Duo using a natural language query:
|
||||
|
||||
|
|
|
|||
|
|
@ -426,32 +426,6 @@ Approval settings should not be confused with [approval rules](../project/merge_
|
|||
for the ability to set merge request approval rules for groups is tracked in
|
||||
[epic 4367](https://gitlab.com/groups/gitlab-org/-/epics/4367).
|
||||
|
||||
## Enable Experiment and Beta features
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) in GitLab 16.0.
|
||||
> - [Added to GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147833) in GitLab 16.11.
|
||||
|
||||
WARNING:
|
||||
[Experiment and Beta features](../../policy/experiment-beta-support.md) may produce unexpected results
|
||||
(for example, the results might be low-quality, incomplete, incoherent, offensive, or insensitive,
|
||||
and might include insecure code or failed pipelines).
|
||||
|
||||
You can give all users in a top-level group access to Experiment and Beta features.
|
||||
This setting [cascades to all projects](../project/merge_requests/approvals/settings.md#cascade-settings-from-the-instance-or-top-level-group)
|
||||
that belong to the group.
|
||||
|
||||
To enable Experiment features for a top-level group:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Permissions and group features**.
|
||||
1. Under **Experiment and Beta features**, select the **Use Experiment and Beta features** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Group activity analytics
|
||||
|
||||
DETAILS:
|
||||
|
|
|
|||
|
|
@ -359,6 +359,8 @@ commit C has no other trailer, only commit A is added to the changelog:
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
graph LR
|
||||
accTitle: Flowchart of 3 commits
|
||||
accDescr: Shows the flow of 3 commits, where commit C reverts commit B, but it contains no trailer
|
||||
A[Commit A<br>Changelog: changed] --> B[Commit B<br>Changelog: changed]
|
||||
B --> C[Commit C<br>Reverts commit B]
|
||||
```
|
||||
|
|
@ -369,6 +371,8 @@ both commits A and C are included in the changelog:
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
graph LR
|
||||
accTitle: Flowchart of 3 commits
|
||||
accDescr: Shows the flow of 3 commits, where commit C reverts commit B, but both commits A and C contain trailers
|
||||
A[Commit A<br><br>Changelog: changed] --> B[Commit B<br><br>Changelog: changed]
|
||||
B --> C[Commit C<br>Reverts commit B<br>Changelog: changed]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ file.md @group-x @group-x/subgroup-y
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
graph TD
|
||||
accTitle: Diagram of group inheritance
|
||||
accDescr: If a subgroup owns a project, the parent group inherits ownership.
|
||||
A[Parent group X] -->|owns| B[Project A]
|
||||
A -->|contains| C[Subgroup Y]
|
||||
C -->|owns| D[Project B]
|
||||
|
|
@ -141,6 +143,8 @@ so that their members also become eligible Code Owners.
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
graph LR
|
||||
accTitle: Diagram of subgroup inheritance
|
||||
accDescr: Inviting a subgroup directly to a project affects whether their approvals can be made required.
|
||||
A[Parent group X] -->|owns| B[Project A]
|
||||
A -->|also contains| C[Subgroup Y]
|
||||
C -.->D{Invite Subgroup Y<br/>to Project A?} -.->|yes| E[Members of Subgroup Y<br/>can submit Approvals]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ The examples on this page assume a `main` branch with commits A, C, and E, and a
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
gitGraph
|
||||
accTitle: Diagram of a merge
|
||||
accDescr: A Git graph of five commits on two branches, which will be expanded on in other graphs in this page.
|
||||
commit id: "A"
|
||||
branch feature
|
||||
commit id: "B"
|
||||
|
|
@ -60,6 +62,8 @@ The merge strategy:
|
|||
```mermaid
|
||||
%%{init: { 'gitGraph': {'logLevel': 'debug', 'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main', 'fontFamily': 'GitLab Sans'}} }%%
|
||||
gitGraph
|
||||
accTitle: Diagram of a merge commit
|
||||
accDescr: A Git graph showing how merge commits are created in GitLab when a feature branch is merged.
|
||||
commit id: "A"
|
||||
branch feature
|
||||
commit id: "B"
|
||||
|
|
@ -76,6 +80,8 @@ looks like this:
|
|||
```mermaid
|
||||
%%{init: { 'gitGraph': {'logLevel': 'debug', 'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main', 'fontFamily': 'GitLab Sans'}} }%%
|
||||
gitGraph
|
||||
accTitle: Diagram of the Merge Commit method
|
||||
accDescr: A Git graph showing the structure of a Git repository after a feature branch is merged.
|
||||
commit id: "A"
|
||||
commit id: "C"
|
||||
commit id: "E"
|
||||
|
|
@ -90,6 +96,8 @@ on the `feature` branch, and the squash commit is placed on the `main` branch:
|
|||
```mermaid
|
||||
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main', 'fontFamily': 'GitLab Sans'}} }%%
|
||||
gitGraph
|
||||
accTitle: Diagram of of a squash merge
|
||||
accDescr: A Git graph showing repository and branch structure after a squash commit is added to the main branch.
|
||||
commit id:"A"
|
||||
branch feature
|
||||
checkout main
|
||||
|
|
@ -129,6 +137,8 @@ commit graph generated using this merge method:
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
gitGraph
|
||||
accTitle: Diagram of a merge commit
|
||||
accDescr: Shows the flow of commits when a branch merges with a merge commit.
|
||||
commit id: "Init"
|
||||
branch mr-branch-1
|
||||
commit
|
||||
|
|
@ -169,6 +179,8 @@ generated using this merge method:
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
gitGraph
|
||||
accTitle: Diagram of a fast-forward merge
|
||||
accDescr: Shows how a fast-forwarded merge request maintains a linear Git history, but does not add a merge commit.
|
||||
commit id: "Init"
|
||||
commit id: "Merge mr-branch-1"
|
||||
commit id: "Merge mr-branch-2"
|
||||
|
|
|
|||
|
|
@ -460,6 +460,8 @@ on branch `A`:
|
|||
```mermaid
|
||||
%%{init: { "fontFamily": "GitLab Sans" }}%%
|
||||
gitGraph
|
||||
accTitle: Diagram of multiple branches with the same commit
|
||||
accDescr: Branches A and B contain the same commit, but branch B also contains other commits. Merging branch B makes branch A appear as merged, because all its commits are merged.
|
||||
commit id:"a"
|
||||
branch "branch A"
|
||||
commit id:"b"
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ module Gitlab
|
|||
def perform
|
||||
each_sub_batch do |batch|
|
||||
batch.update_all('monitor_access_level=operations_access_level,' \
|
||||
'infrastructure_access_level=operations_access_level,' \
|
||||
' feature_flags_access_level=operations_access_level,'\
|
||||
' environments_access_level=operations_access_level')
|
||||
'infrastructure_access_level=operations_access_level, ' \
|
||||
'feature_flags_access_level=operations_access_level, '\
|
||||
'environments_access_level=operations_access_level')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -111,16 +111,16 @@ module Gitlab
|
|||
|
||||
def add_schema_version_errors
|
||||
if report_version.nil?
|
||||
template = _("Report version not provided,"\
|
||||
" %{report_type} report type supports versions: %{supported_schema_versions}."\
|
||||
" GitLab will attempt to validate this report against the earliest supported versions of this report"\
|
||||
" type, to show all the errors but will not ingest the report")
|
||||
template = _("Report version not provided, "\
|
||||
"%{report_type} report type supports versions: %{supported_schema_versions}. "\
|
||||
"GitLab will attempt to validate this report against the earliest supported versions of this report "\
|
||||
"type, to show all the errors but will not ingest the report")
|
||||
message = format(template, report_type: report_type, supported_schema_versions: supported_schema_versions)
|
||||
else
|
||||
template = _("Version %{report_version} for report type %{report_type} is unsupported, supported versions"\
|
||||
" for this report type are: %{supported_schema_versions}."\
|
||||
" GitLab will attempt to validate this report against the earliest supported versions of this report"\
|
||||
" type, to show all the errors but will not ingest the report")
|
||||
template = _("Version %{report_version} for report type %{report_type} is unsupported, supported versions "\
|
||||
"for this report type are: %{supported_schema_versions}. "\
|
||||
"GitLab will attempt to validate this report against the earliest supported versions of this report "\
|
||||
"type, to show all the errors but will not ingest the report")
|
||||
message = format(template, report_version: report_version, report_type: report_type, supported_schema_versions: supported_schema_versions)
|
||||
end
|
||||
|
||||
|
|
@ -165,9 +165,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def add_supported_major_minor_behavior_warning
|
||||
template = _("This report uses a supported MAJOR.MINOR schema version but the PATCH version doesn't match"\
|
||||
" any vendored schema version. Validation will be attempted against version"\
|
||||
" %{find_latest_patch_version}")
|
||||
template = _("This report uses a supported MAJOR.MINOR schema version but the PATCH version doesn't match "\
|
||||
"any vendored schema version. Validation will be attempted against version "\
|
||||
"%{find_latest_patch_version}")
|
||||
|
||||
message = format(template, find_latest_patch_version: find_latest_patch_version)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ module Gitlab
|
|||
if use_primary?(strategy)
|
||||
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
|
||||
elsif strategy == :retry
|
||||
raise JobReplicaNotUpToDate, "Sidekiq job #{resolved_class} JID-#{job['jid']} couldn't use the replica."\
|
||||
" Replica was not up to date."
|
||||
raise JobReplicaNotUpToDate, "Sidekiq job #{resolved_class} JID-#{job['jid']} couldn't use the replica. "\
|
||||
"Replica was not up to date."
|
||||
else
|
||||
# this means we selected an up-to-date replica, but there is nothing to do in this case.
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,12 +38,6 @@ module Gitlab
|
|||
super || initial_partition
|
||||
end
|
||||
|
||||
def partition_for_id(partition_id)
|
||||
current_partitions.find do |partition|
|
||||
partition_id.in?(partition.values)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def desired_partitions
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ module Gitlab
|
|||
partitioned_table = find_partitioned_table(table_name)
|
||||
|
||||
if index_name_exists?(table_name, options[:name])
|
||||
Gitlab::AppLogger.warn "Index not created because it already exists (this may be due to an aborted" \
|
||||
" migration or similar): table_name: #{table_name}, index_name: #{options[:name]}"
|
||||
Gitlab::AppLogger.warn "Index not created because it already exists (this may be due to an aborted " \
|
||||
"migration or similar): table_name: #{table_name}, index_name: #{options[:name]}"
|
||||
|
||||
return
|
||||
end
|
||||
|
|
|
|||
|
|
@ -389,8 +389,8 @@ module Gitlab
|
|||
|
||||
def create_range_id_partitioned_copy(source_table_name, partitioned_table_name, partition_column, primary_keys)
|
||||
if table_exists?(partitioned_table_name)
|
||||
Gitlab::AppLogger.warn "Partitioned table not created because it already exists" \
|
||||
" (this may be due to an aborted migration or similar): table_name: #{partitioned_table_name} "
|
||||
Gitlab::AppLogger.warn "Partitioned table not created because it already exists " \
|
||||
"(this may be due to an aborted migration or similar): table_name: #{partitioned_table_name} "
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -417,8 +417,8 @@ module Gitlab
|
|||
|
||||
def create_range_partitioned_copy(source_table_name, partitioned_table_name, partition_column, primary_key)
|
||||
if table_exists?(partitioned_table_name)
|
||||
Gitlab::AppLogger.warn "Partitioned table not created because it already exists" \
|
||||
" (this may be due to an aborted migration or similar): table_name: #{partitioned_table_name} "
|
||||
Gitlab::AppLogger.warn "Partitioned table not created because it already exists " \
|
||||
"(this may be due to an aborted migration or similar): table_name: #{partitioned_table_name} "
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -485,8 +485,8 @@ module Gitlab
|
|||
|
||||
def create_range_partition_safely(partition_name, table_name, lower_bound, upper_bound)
|
||||
if table_exists?(table_for_range_partition(partition_name))
|
||||
Gitlab::AppLogger.warn "Partition not created because it already exists" \
|
||||
" (this may be due to an aborted migration or similar): partition_name: #{partition_name}"
|
||||
Gitlab::AppLogger.warn "Partition not created because it already exists " \
|
||||
"(this may be due to an aborted migration or similar): partition_name: #{partition_name}"
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -503,8 +503,8 @@ module Gitlab
|
|||
|
||||
def create_sync_function(name, partitioned_table_name, unique_key)
|
||||
if function_exists?(name)
|
||||
Gitlab::AppLogger.warn "Partitioning sync function not created because it already exists" \
|
||||
" (this may be due to an aborted migration or similar): function name: #{name}"
|
||||
Gitlab::AppLogger.warn "Partitioning sync function not created because it already exists " \
|
||||
"(this may be due to an aborted migration or similar): function name: #{name}"
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -549,8 +549,8 @@ module Gitlab
|
|||
|
||||
def create_sync_trigger(table_name, trigger_name, function_name)
|
||||
if trigger_exists?(table_name, trigger_name)
|
||||
Gitlab::AppLogger.warn "Partitioning sync trigger not created because it already exists" \
|
||||
" (this may be due to an aborted migration or similar): trigger name: #{trigger_name}"
|
||||
Gitlab::AppLogger.warn "Partitioning sync trigger not created because it already exists " \
|
||||
"(this may be due to an aborted migration or similar): trigger name: #{trigger_name}"
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,20 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
scope :with_parent_tables, ->(parent_tables) do
|
||||
parent_identifiers = parent_tables.map { |name| "#{connection.current_schema}.#{name}" }
|
||||
|
||||
where(parent_identifier: parent_identifiers).order(:name)
|
||||
end
|
||||
|
||||
scope :with_list_constraint, ->(condition) do
|
||||
where(sanitize_sql_for_conditions(['condition LIKE ?', "FOR VALUES IN (%'#{condition.to_i}'%)"]))
|
||||
end
|
||||
|
||||
scope :above_threshold, ->(threshold) do
|
||||
where('pg_table_size(identifier) > ?', threshold)
|
||||
end
|
||||
|
||||
def self.partition_exists?(table_name)
|
||||
where("identifier = concat(current_schema(), '.', ?)", table_name).exists?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ module Gitlab
|
|||
requested_reviewer = User.find(requested_reviewer_id).to_reference
|
||||
|
||||
if issue_event.event == 'review_request_removed'
|
||||
"#{SystemNotes::IssuablesService.issuable_events[:review_request_removed]}" \
|
||||
" #{requested_reviewer}"
|
||||
"#{SystemNotes::IssuablesService.issuable_events[:review_request_removed]} " \
|
||||
"#{requested_reviewer}"
|
||||
else
|
||||
"#{SystemNotes::IssuablesService.issuable_events[:review_requested]}" \
|
||||
" #{requested_reviewer}"
|
||||
"#{SystemNotes::IssuablesService.issuable_events[:review_requested]} " \
|
||||
"#{requested_reviewer}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def show_import_start_message
|
||||
logger.info "Importing GitLab export: #{file_path} into GitLab" \
|
||||
" #{full_path}" \
|
||||
" as #{current_user.name}"
|
||||
logger.info "Importing GitLab export: #{file_path} into GitLab " \
|
||||
"#{full_path} " \
|
||||
"as #{current_user.name}"
|
||||
end
|
||||
|
||||
def import_params
|
||||
|
|
|
|||
|
|
@ -33,11 +33,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def api_request?
|
||||
if ::Feature.enabled?(:rate_limit_oauth_api, ::Feature.current_request)
|
||||
matches?(API_PATH_REGEX)
|
||||
else
|
||||
logical_path.start_with?('/api')
|
||||
end
|
||||
matches?(API_PATH_REGEX)
|
||||
end
|
||||
|
||||
def logical_path
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ module Gitlab
|
|||
redis_cmd do |redis|
|
||||
current_value = redis.decr(key)
|
||||
if current_value < 0
|
||||
Gitlab::AppLogger.warn("Reference counter for #{gl_repository} decreased" \
|
||||
" when its value was less than 1. Resetting the counter.")
|
||||
Gitlab::AppLogger.warn("Reference counter for #{gl_repository} decreased " \
|
||||
"when its value was less than 1. Resetting the counter.")
|
||||
redis.del(key)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ module Gitlab
|
|||
def check_argument_type(argument_name, argument_value, allowed_classes)
|
||||
return if argument_value.nil? || allowed_classes.any? { |allowed_class| argument_value.is_a?(allowed_class) }
|
||||
|
||||
exception = "Invalid argument type passed for #{argument_name}." \
|
||||
" Should be one of #{allowed_classes.map(&:to_s)}"
|
||||
exception = "Invalid argument type passed for #{argument_name}. " \
|
||||
"Should be one of #{allowed_classes.map(&:to_s)}"
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new(exception))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,9 @@ module Gitlab
|
|||
UndefinedEvents = Class.new(AggregatedMetricError)
|
||||
|
||||
DATABASE_SOURCE = 'database'
|
||||
REDIS_SOURCE = 'redis_hll'
|
||||
INTERNAL_EVENTS_SOURCE = 'internal_events'
|
||||
|
||||
SOURCES = {
|
||||
DATABASE_SOURCE => Sources::PostgresHll,
|
||||
REDIS_SOURCE => Sources::RedisHll,
|
||||
# Same strategy as RedisHLL, since they are a part of internal events
|
||||
# and should get counted together with other RedisHLL-based aggregations
|
||||
INTERNAL_EVENTS_SOURCE => Sources::RedisHll
|
||||
DATABASE_SOURCE => Sources::PostgresHll
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Metrics
|
||||
module Aggregates
|
||||
class Aggregate
|
||||
include Gitlab::Usage::TimeFrame
|
||||
|
||||
def initialize(recorded_at)
|
||||
@recorded_at = recorded_at
|
||||
end
|
||||
|
||||
def calculate_count_for_aggregation(aggregation:, time_frame:)
|
||||
with_validate_configuration(aggregation, time_frame) do
|
||||
source = SOURCES[aggregation[:source]]
|
||||
events = select_defined_events(aggregation[:events], aggregation[:source])
|
||||
property_name = aggregation[:attribute]
|
||||
|
||||
source.calculate_metrics_union(**time_constraints(time_frame)
|
||||
.merge(metric_names: events, property_name: property_name, recorded_at: recorded_at))
|
||||
end
|
||||
rescue Gitlab::UsageDataCounters::HLLRedisCounter::EventError, AggregatedMetricError => error
|
||||
failure(error)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :recorded_at
|
||||
|
||||
def with_validate_configuration(aggregation, time_frame)
|
||||
source = aggregation[:source]
|
||||
|
||||
unless SOURCES[source]
|
||||
return failure(
|
||||
UnknownAggregationSource
|
||||
.new("Aggregation source: '#{source}' must be included in #{SOURCES.keys}")
|
||||
)
|
||||
end
|
||||
|
||||
if time_frame == Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME && source == REDIS_SOURCE
|
||||
return failure(
|
||||
DisallowedAggregationTimeFrame
|
||||
.new("Aggregation time frame: 'all' is not allowed for aggregation with source: '#{REDIS_SOURCE}'")
|
||||
)
|
||||
end
|
||||
|
||||
yield
|
||||
end
|
||||
|
||||
def failure(error)
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
|
||||
Gitlab::Utils::UsageData::FALLBACK
|
||||
end
|
||||
|
||||
def time_constraints(time_frame)
|
||||
case time_frame
|
||||
when Gitlab::Usage::TimeFrame::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME
|
||||
monthly_time_range
|
||||
when Gitlab::Usage::TimeFrame::SEVEN_DAYS_TIME_FRAME_NAME
|
||||
weekly_time_range
|
||||
when Gitlab::Usage::TimeFrame::ALL_TIME_TIME_FRAME_NAME
|
||||
{ start_date: nil, end_date: nil }
|
||||
end
|
||||
end
|
||||
|
||||
def select_defined_events(events, source)
|
||||
# Database source metrics get validated inside the PostgresHll class:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll.rb#L16
|
||||
return events if source != ::Gitlab::Usage::Metrics::Aggregates::REDIS_SOURCE
|
||||
|
||||
events.select do |event|
|
||||
::Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Usage::Metrics::Aggregates::Aggregate.prepend_mod
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Metrics
|
||||
module Aggregates
|
||||
module Sources
|
||||
class RedisHll
|
||||
def self.calculate_metrics_union(metric_names:, start_date:, end_date:, property_name:, recorded_at: nil)
|
||||
union = Gitlab::UsageDataCounters::HLLRedisCounter.calculate_events_union(
|
||||
event_names: metric_names,
|
||||
property_name: property_name,
|
||||
start_date: start_date,
|
||||
end_date: end_date
|
||||
)
|
||||
|
||||
return union if union >= 0
|
||||
|
||||
raise UnionNotAvailable, "Union data not available for #{metric_names}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Usage
|
||||
module Metrics
|
||||
module Instrumentations
|
||||
# Usage example
|
||||
#
|
||||
# In metric YAML definition:
|
||||
#
|
||||
# instrumentation_class: AggregatedMetric
|
||||
# data_source: redis_hll
|
||||
# options:
|
||||
# aggregate:
|
||||
# attribute: user.id
|
||||
# events:
|
||||
# - 'incident_management_alert_status_changed'
|
||||
# - 'incident_management_alert_assigned'
|
||||
# - 'incident_management_alert_todo'
|
||||
# - 'incident_management_alert_create_incident'
|
||||
|
||||
class AggregatedMetric < BaseMetric
|
||||
FALLBACK = -1
|
||||
|
||||
def initialize(metric_definition)
|
||||
super
|
||||
@source = metric_definition[:data_source]
|
||||
@aggregate = options.fetch(:aggregate, {})
|
||||
end
|
||||
|
||||
def value
|
||||
alt_usage_data(fallback: FALLBACK) do
|
||||
Aggregates::Aggregate
|
||||
.new(Time.current)
|
||||
.calculate_count_for_aggregation(
|
||||
aggregation: aggregate_config,
|
||||
time_frame: time_frame
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :source, :aggregate
|
||||
|
||||
def aggregate_config
|
||||
{
|
||||
source: source,
|
||||
events: options[:events],
|
||||
attribute: aggregate[:attribute]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -61690,48 +61690,15 @@ msgstr ""
|
|||
msgid "mrWidget|%{boldHeaderStart}Looks like there's no pipeline here.%{boldHeaderEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} Select %{boldStart}Mark as ready%{boldEnd} to remove it from Draft status."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} Users who can write to the source or target branches can resolve the conflicts."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} a Jira issue key must be mentioned in the title or description."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} all required approvals must be given."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} all status checks must pass."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} all threads must be resolved."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} denied licenses must be removed."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} fast-forward merge is not possible. To merge this request, first rebase locally."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} merge conflicts must be resolved."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} new changes were just added."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} pipeline must succeed. It's waiting for a manual action to continue."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} pipeline must succeed. Push a commit that fixes the failure or %{linkStart}learn about other solutions.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} the source branch must be rebased onto the target branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge blocked:%{boldEnd} you can only merge after the above items are resolved."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Merge unavailable:%{boldEnd} merge requests are read-only in a secondary Geo node."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61765,9 +61732,6 @@ msgstr ""
|
|||
msgid "mrWidget|%{boldStart}Merging!%{boldEnd} We're almost there…"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{boldStart}Ready to be merged automatically.%{boldEnd} Ask someone with write access to this repository to merge this request."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|%{dangerStart}%{rules} rule can't be approved%{dangerEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61911,9 +61875,6 @@ msgstr ""
|
|||
msgid "mrWidget|Loading deployment statistics"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Mark as ready"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Members who can merge are allowed to add commits."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61943,9 +61904,6 @@ msgstr ""
|
|||
msgid "mrWidget|Rebase"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Rebase in progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Rebase without pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -61961,12 +61919,6 @@ msgstr ""
|
|||
msgid "mrWidget|Remove from merge train"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Resolve conflicts"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Resolve locally"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Revert"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -71,9 +71,8 @@ module QA
|
|||
element 'revert-button'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue' do
|
||||
view 'app/assets/javascripts/vue_merge_request_widget/components/checks/rebase.vue' do
|
||||
element 'standard-rebase-button'
|
||||
element 'rebase-message'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
|
||||
|
|
@ -224,22 +223,6 @@ module QA
|
|||
click_by_javascript(find_element('edit-title-button', skip_finished_loading_check: true))
|
||||
end
|
||||
|
||||
def fast_forward_not_possible?
|
||||
has_element?('rebase-message')
|
||||
end
|
||||
|
||||
def merge_blocked_component_ff_enabled?
|
||||
element = within_element('.mr-widget-section') do
|
||||
feature_flag_controlled_element(
|
||||
:merge_blocked_component,
|
||||
'chevron-lg-down-icon',
|
||||
'standard-rebase-button'
|
||||
)
|
||||
end
|
||||
|
||||
!(element == 'standard-rebase-button')
|
||||
end
|
||||
|
||||
def expand_merge_checks
|
||||
within_element('.mr-widget-section') do
|
||||
click_element('chevron-lg-down-icon')
|
||||
|
|
|
|||
|
|
@ -23,15 +23,9 @@ module QA
|
|||
|
||||
merge_request.visit!
|
||||
Page::MergeRequest::Show.perform do |mr_page|
|
||||
if mr_page.merge_blocked_component_ff_enabled?
|
||||
expect(mr_page).to have_content('Merge blocked: 1 check failed', wait: 20)
|
||||
mr_page.expand_merge_checks
|
||||
expect(mr_page).to have_content('Merge request must be rebased, because a fast-forward merge is not possible.')
|
||||
else
|
||||
expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.', wait: 20)
|
||||
expect(mr_page).to be_fast_forward_not_possible
|
||||
page.refresh
|
||||
end
|
||||
expect(mr_page).to have_content('Merge blocked: 1 check failed', wait: 20)
|
||||
mr_page.expand_merge_checks
|
||||
expect(mr_page).to have_content('Merge request must be rebased, because a fast-forward merge is not possible.')
|
||||
|
||||
expect(mr_page).not_to have_merge_button
|
||||
expect(merge_request.project.commits.size).to eq(2)
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ module RuboCop
|
|||
MSG_STYLE_GUIDE_LINK = 'See the description style guide: https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#description-style-guide'
|
||||
MSG_NO_DESCRIPTION = "Please add a `description` property. #{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
MSG_NO_PERIOD = "`description` strings must end with a `.`. #{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
MSG_BAD_START = "`description` strings should not start with \"A...\" or \"The...\"."\
|
||||
" #{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
MSG_CONTAINS_THIS = "`description` strings should not contain the demonstrative \"this\"."\
|
||||
" #{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
MSG_BAD_START = "`description` strings should not start with \"A...\" or \"The...\". "\
|
||||
"#{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
MSG_CONTAINS_THIS = "`description` strings should not contain the demonstrative \"this\". "\
|
||||
"#{MSG_STYLE_GUIDE_LINK}".freeze
|
||||
|
||||
def_node_matcher :graphql_describable?, <<~PATTERN
|
||||
(send nil? {:field :argument :value} ...)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ module RuboCop
|
|||
class AddColumnsToWideTables < RuboCop::Cop::Base
|
||||
include MigrationHelpers
|
||||
|
||||
MSG = '`%s` is a wide table with several columns, adding more should be avoided unless absolutely necessary.' \
|
||||
' Consider storing the column in a different table or creating a new one.'
|
||||
MSG = '`%s` is a wide table with several columns, adding more should be avoided unless absolutely necessary. ' \
|
||||
'Consider storing the column in a different table or creating a new one.'
|
||||
|
||||
DENYLISTED_METHODS = %i[
|
||||
add_column
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ module RuboCop
|
|||
class BackgroundMigrations < RuboCop::Cop::Base
|
||||
include MigrationHelpers
|
||||
|
||||
MSG = 'Background migrations are deprecated. Please use a Batched Background Migration instead.'\
|
||||
' More info: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html'
|
||||
MSG = 'Background migrations are deprecated. Please use a Batched Background Migration instead. '\
|
||||
'More info: https://docs.gitlab.com/ee/development/database/batched_background_migrations.html'
|
||||
|
||||
def on_send(node)
|
||||
name = node.children[1]
|
||||
|
|
|
|||
|
|
@ -113,5 +113,4 @@ UsageData/InstrumentationSuperclass:
|
|||
- :RedisHLLMetric
|
||||
- :RedisMetric
|
||||
- :NumbersMetric
|
||||
- :AggregatedMetric
|
||||
- :PrometheusMetric
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ module Glfm
|
|||
# headers should be size 3 or less [<H1_headertext>, <H2_headertext>, <H3_headertext>]
|
||||
|
||||
if headers.length == 1 && line =~ h3_regex
|
||||
errmsg = "Error: The H3 '#{headertext}' may not be nested directly within the H1 '#{headers[0]}'. " \
|
||||
" Add an H2 header before the H3 header."
|
||||
errmsg = "Error: The H3 '#{headertext}' may not be nested directly within the H1 '#{headers[0]}'. " \
|
||||
"Add an H2 header before the H3 header."
|
||||
raise errmsg
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class PipelineTestReportBuilder
|
|||
fetch("#{pipeline_url}/tests/suite.json?build_ids[]=#{build_id}").tap do |suite|
|
||||
suite['job_url'] = job_url(pipeline_url, build_id)
|
||||
end
|
||||
rescue Net::HTTPServerException => e
|
||||
rescue Net::HTTPClientException => e
|
||||
raise e unless e.response.code.to_i == 404
|
||||
|
||||
puts "[PipelineTestReportBuilder] Artifacts not found. They may have expired. Skipping this build."
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ else
|
|||
puts missing_message % missing_testcases.join("\n") unless missing_testcases.empty?
|
||||
puts format_message % testcase_format_errors.join("\n") unless testcase_format_errors.empty?
|
||||
puts "\n*** Please link a unique test case from the GitLab project for the errors listed above.\n"
|
||||
puts " See: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/best_practices.html#link-a-test-to-its-test-case"\
|
||||
" for further details on how to create test cases"
|
||||
puts " See: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/" \
|
||||
"best_practices.html#link-a-test-to-its-test-case " \
|
||||
"for further details on how to create test cases"
|
||||
exit 1
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ module DeprecationToolkitEnv
|
|||
# the dependency causing the problem.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/commit/aea37f506bbe036378998916d374966c031bf347#note_647515736
|
||||
def self.allowed_kwarg_warning_paths
|
||||
%w[
|
||||
]
|
||||
%w[]
|
||||
end
|
||||
|
||||
def self.configure!
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ RSpec.describe 'Merge request > User resolves Draft', :js, feature_category: :co
|
|||
let(:feature_flags_state) { true }
|
||||
|
||||
before do
|
||||
stub_feature_flags(merge_when_checks_pass: feature_flags_state, merge_blocked_component: feature_flags_state)
|
||||
stub_feature_flags(merge_when_checks_pass: feature_flags_state)
|
||||
|
||||
create(:ci_build, pipeline: pipeline)
|
||||
|
||||
|
|
@ -73,7 +73,8 @@ RSpec.describe 'Merge request > User resolves Draft', :js, feature_category: :co
|
|||
|
||||
it 'retains merge request data after clicking Resolve WIP status' do
|
||||
expect(page.find('.ci-widget-content')).to have_content("Pipeline ##{pipeline.id}")
|
||||
expect(page).to have_content "Merge blocked: Select Mark as ready to remove it from Draft status."
|
||||
expect(page).to have_content "Merge blocked: 1 check failed"
|
||||
expect(page).to have_content "Merge request must not be draft"
|
||||
|
||||
page.within('.mr-state-widget') do
|
||||
click_button('Mark as ready')
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import ProjectsExploreFilteredSearchAndSort from '~/projects/explore/components/
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
...jest.requireActual('~/lib/utils/url_utility'),
|
||||
|
|
@ -74,6 +75,20 @@ describe('ProjectsExploreFilteredSearchAndSort', () => {
|
|||
{ value: '11', title: 'Shell' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'min_access_level',
|
||||
icon: 'user',
|
||||
title: 'Role',
|
||||
token: GlFilteredSearchToken,
|
||||
unique: true,
|
||||
operators: OPERATORS_IS,
|
||||
options: [
|
||||
{
|
||||
value: '50',
|
||||
title: 'Owner',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
filteredSearchQuery: { [FILTERED_SEARCH_TERM_KEY]: 'foo' },
|
||||
filteredSearchTermKey: FILTERED_SEARCH_TERM_KEY,
|
||||
|
|
|
|||
|
|
@ -1,417 +0,0 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
|
||||
import rebaseQuery from '~/vue_merge_request_widget/queries/states/rebase.query.graphql';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
||||
jest.mock('~/vue_shared/plugins/global_toast');
|
||||
|
||||
let wrapper;
|
||||
const showMock = jest.fn();
|
||||
|
||||
const mockPipelineNodes = [
|
||||
{
|
||||
id: '1',
|
||||
project: {
|
||||
id: '2',
|
||||
fullPath: 'user/forked',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockQueryHandler = ({
|
||||
rebaseInProgress = false,
|
||||
targetBranch = '',
|
||||
pushToSourceBranch = false,
|
||||
nodes = mockPipelineNodes,
|
||||
} = {}) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
project: {
|
||||
id: '1',
|
||||
mergeRequest: {
|
||||
id: '2',
|
||||
rebaseInProgress,
|
||||
targetBranch,
|
||||
userPermissions: {
|
||||
pushToSourceBranch,
|
||||
},
|
||||
pipelines: {
|
||||
nodes,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createMockApolloProvider = (handler) => {
|
||||
Vue.use(VueApollo);
|
||||
|
||||
return createMockApollo([[rebaseQuery, handler]]);
|
||||
};
|
||||
|
||||
function createWrapper({ propsData = {}, provideData = {}, handler = mockQueryHandler() } = {}) {
|
||||
wrapper = shallowMountExtended(WidgetRebase, {
|
||||
apolloProvider: createMockApolloProvider(handler),
|
||||
provide: {
|
||||
...provideData,
|
||||
},
|
||||
propsData: {
|
||||
mr: {},
|
||||
service: {},
|
||||
...propsData,
|
||||
},
|
||||
stubs: {
|
||||
StateContainer,
|
||||
GlModal: stubComponent(GlModal, {
|
||||
methods: {
|
||||
show: showMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Merge request widget rebase component', () => {
|
||||
const findRebaseMessage = () => wrapper.findByTestId('rebase-message');
|
||||
const findBoldText = () => wrapper.findComponent(BoldText);
|
||||
const findRebaseMessageText = () => findRebaseMessage().text();
|
||||
const findStandardRebaseButton = () => wrapper.findByTestId('standard-rebase-button');
|
||||
const findRebaseWithoutCiButton = () => wrapper.findByTestId('rebase-without-ci-button');
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
describe('while rebasing', () => {
|
||||
it('should show progress message', async () => {
|
||||
createWrapper({
|
||||
handler: mockQueryHandler({ rebaseInProgress: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRebaseMessageText()).toContain('Rebase in progress');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with permissions', () => {
|
||||
const rebaseMock = jest.fn().mockResolvedValue();
|
||||
const pollMock = jest.fn().mockResolvedValue({});
|
||||
|
||||
it('renders the warning message', async () => {
|
||||
createWrapper({
|
||||
handler: mockQueryHandler({
|
||||
rebaseInProgress: false,
|
||||
pushToSourceBranch: false,
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findBoldText().props('message')).toContain('Merge blocked');
|
||||
expect(findBoldText().props('message').replace(/\s\s+/g, ' ')).toContain(
|
||||
'the source branch must be rebased onto the target branch',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders an error message when rebasing has failed', async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
service: {
|
||||
rebase: jest.fn().mockRejectedValue({
|
||||
response: {
|
||||
data: {
|
||||
merge_error: 'Something went wrong!',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
expect(findRebaseMessageText()).toContain('Something went wrong!');
|
||||
});
|
||||
|
||||
describe('Rebase buttons', () => {
|
||||
it('renders both buttons', async () => {
|
||||
createWrapper({
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRebaseWithoutCiButton().exists()).toBe(true);
|
||||
expect(findStandardRebaseButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('starts the rebase when clicking', async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
|
||||
});
|
||||
|
||||
it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findRebaseWithoutCiButton().vm.$emit('click');
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rebase when pipelines must succeed is enabled', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
mr: {
|
||||
onlyAllowMergeIfPipelineSucceeds: true,
|
||||
},
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders only the rebase button', () => {
|
||||
expect(findRebaseWithoutCiButton().exists()).toBe(false);
|
||||
expect(findStandardRebaseButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('starts the rebase when clicking', async () => {
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
mr: {
|
||||
onlyAllowMergeIfPipelineSucceeds: true,
|
||||
allowMergeOnSkippedPipeline: true,
|
||||
},
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders both rebase buttons', () => {
|
||||
expect(findRebaseWithoutCiButton().exists()).toBe(true);
|
||||
expect(findStandardRebaseButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('starts the rebase when clicking', async () => {
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
|
||||
});
|
||||
|
||||
it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
|
||||
findRebaseWithoutCiButton().vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('security modal', () => {
|
||||
it('displays modal and rebases after confirming', async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
mr: {
|
||||
sourceProjectFullPath: 'user/forked',
|
||||
targetProjectFullPath: 'root/original',
|
||||
},
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
provideData: { canCreatePipelineInTargetProject: true },
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
expect(showMock).toHaveBeenCalled();
|
||||
|
||||
findModal().vm.$emit('primary');
|
||||
|
||||
expect(rebaseMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not display modal', async () => {
|
||||
createWrapper({
|
||||
propsData: {
|
||||
mr: {
|
||||
sourceProjectFullPath: 'user/forked',
|
||||
targetProjectFullPath: 'root/original',
|
||||
},
|
||||
service: {
|
||||
rebase: rebaseMock,
|
||||
poll: pollMock,
|
||||
},
|
||||
},
|
||||
provideData: { canCreatePipelineInTargetProject: false },
|
||||
handler: mockQueryHandler({ pushToSourceBranch: true }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findStandardRebaseButton().vm.$emit('click');
|
||||
|
||||
expect(showMock).not.toHaveBeenCalled();
|
||||
expect(rebaseMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without permissions', () => {
|
||||
const exampleTargetBranch = 'fake-branch-to-test-with';
|
||||
|
||||
describe('UI text', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper({
|
||||
handler: mockQueryHandler({
|
||||
pushToSourceBranch: false,
|
||||
targetBranch: exampleTargetBranch,
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders a message explaining user does not have permissions', () => {
|
||||
expect(findBoldText().props('message')).toContain('Merge blocked');
|
||||
expect(findBoldText().props('message')).toContain('the source branch must be rebased');
|
||||
});
|
||||
|
||||
it('renders the correct target branch name', () => {
|
||||
expect(findBoldText().props('message')).toContain('Merge blocked:');
|
||||
expect(findBoldText().props('message')).toContain(
|
||||
'the source branch must be rebased onto the target branch.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does render the "Rebase without pipeline" button', async () => {
|
||||
createWrapper({
|
||||
handler: mockQueryHandler({
|
||||
rebaseInProgress: false,
|
||||
pushToSourceBranch: false,
|
||||
targetBranch: exampleTargetBranch,
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findRebaseWithoutCiButton().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
it('checkRebaseStatus', async () => {
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
createWrapper({
|
||||
propsData: {
|
||||
service: {
|
||||
rebase() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
poll() {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
rebase_in_progress: false,
|
||||
should_be_rebased: false,
|
||||
merge_error: null,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findRebaseWithoutCiButton().vm.$emit('click');
|
||||
|
||||
// Wait for the rebase request
|
||||
await nextTick();
|
||||
// Wait for the polling request
|
||||
await nextTick();
|
||||
// Wait for the eventHub to be called
|
||||
await nextTick();
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
|
||||
expect(toast).toHaveBeenCalledWith('Rebase completed');
|
||||
});
|
||||
});
|
||||
|
||||
// This may happen when the session of a user is expired.
|
||||
// see https://gitlab.com/gitlab-org/gitlab/-/issues/413627
|
||||
describe('with empty project', () => {
|
||||
it('does not throw any error', async () => {
|
||||
const fn = async () => {
|
||||
createWrapper({
|
||||
handler: jest.fn().mockResolvedValue({ data: { project: null } }),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
await expect(fn()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MergeChecksFailed from '~/vue_merge_request_widget/components/states/merge_checks_failed.vue';
|
||||
import { DETAILED_MERGE_STATUS } from '~/vue_merge_request_widget/constants';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
|
||||
let wrapper;
|
||||
|
||||
function factory(propsData = {}) {
|
||||
wrapper = shallowMount(MergeChecksFailed, {
|
||||
propsData,
|
||||
});
|
||||
}
|
||||
|
||||
describe('Merge request widget merge checks failed state component', () => {
|
||||
it.each`
|
||||
mrState | displayText
|
||||
${{ approvals: true, isApproved: false }} | ${'approvalNeeded'}
|
||||
${{ detailedMergeStatus: DETAILED_MERGE_STATUS.BLOCKED_STATUS }} | ${'blockingMergeRequests'}
|
||||
${{ detailedMergeStatus: DETAILED_MERGE_STATUS.EXTERNAL_STATUS_CHECKS }} | ${'externalStatusChecksFailed'}
|
||||
`('display $displayText text for $mrState', ({ mrState, displayText }) => {
|
||||
factory({ mr: mrState });
|
||||
|
||||
const message = wrapper.findComponent(BoldText).props('message');
|
||||
expect(message).toContain(MergeChecksFailed.i18n[displayText]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,315 +0,0 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Vue from 'vue';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
|
||||
import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
|
||||
import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('MRWidgetConflicts', () => {
|
||||
let wrapper;
|
||||
const path = '/conflicts';
|
||||
|
||||
const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button');
|
||||
const findMergeLocalButton = () => wrapper.findByTestId('merge-locally-button');
|
||||
|
||||
const mergeConflictsText = 'merge conflicts must be resolved.';
|
||||
const fastForwardMergeText =
|
||||
'fast-forward merge is not possible. To merge this request, first rebase locally.';
|
||||
const userCannotMergeText =
|
||||
'Users who can write to the source or target branches can resolve the conflicts.';
|
||||
const resolveConflictsBtnText = 'Resolve conflicts';
|
||||
const mergeLocallyBtnText = 'Resolve locally';
|
||||
|
||||
const defaultApolloProvider = (mockData = {}) => {
|
||||
const userData = {
|
||||
data: {
|
||||
project: {
|
||||
id: 234,
|
||||
mergeRequest: {
|
||||
id: 234,
|
||||
userPermissions: {
|
||||
canMerge: mockData.canMerge || false,
|
||||
pushToSourceBranch: mockData.canPushToSourceBranch || false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mrData = {
|
||||
data: {
|
||||
project: {
|
||||
id: 234,
|
||||
mergeRequest: {
|
||||
id: 234,
|
||||
shouldBeRebased: mockData.shouldBeRebased || false,
|
||||
sourceBranchProtected: mockData.sourceBranchProtected || false,
|
||||
userPermissions: {
|
||||
pushToSourceBranch: mockData.canPushToSourceBranch || false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return createMockApollo([
|
||||
[userPermissionsQuery, jest.fn().mockResolvedValue(userData)],
|
||||
[conflictsStateQuery, jest.fn().mockResolvedValue(mrData)],
|
||||
]);
|
||||
};
|
||||
|
||||
async function createComponent({
|
||||
propsData,
|
||||
queryData,
|
||||
apolloProvider = defaultApolloProvider(queryData),
|
||||
} = {}) {
|
||||
wrapper = extendedWrapper(
|
||||
mount(ConflictsComponent, {
|
||||
apolloProvider,
|
||||
propsData,
|
||||
}),
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
}
|
||||
|
||||
// There are two permissions we need to consider:
|
||||
//
|
||||
// 1. Is the user allowed to merge to the target branch?
|
||||
// 2. Is the user allowed to push to the source branch?
|
||||
//
|
||||
// This yields 4 possible permutations that we need to test, and
|
||||
// we test them below. A user who can push to the source
|
||||
// branch should be allowed to resolve conflicts. This is
|
||||
// consistent with what the backend does.
|
||||
describe('when allowed to merge but not allowed to push to source branch', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canMerge: true,
|
||||
canPushToSourceBranch: false,
|
||||
conflictResolutionPath: path,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should tell you about conflicts without bothering other people', () => {
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain(mergeConflictsText);
|
||||
expect(text).not.toContain(userCannotMergeText);
|
||||
});
|
||||
|
||||
it('should not allow you to resolve the conflicts', () => {
|
||||
expect(wrapper.text()).not.toContain(resolveConflictsBtnText);
|
||||
});
|
||||
|
||||
it('should have merge buttons', () => {
|
||||
expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when not allowed to merge but allowed to push to source branch', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictResolutionPath: path,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canMerge: false,
|
||||
canPushToSourceBranch: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should tell you about conflicts', () => {
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain(userCannotMergeText);
|
||||
});
|
||||
|
||||
it('should allow you to resolve the conflicts', () => {
|
||||
expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
|
||||
expect(findResolveButton().attributes('href')).toEqual(path);
|
||||
});
|
||||
|
||||
it('should not have merge buttons', () => {
|
||||
expect(wrapper.text()).not.toContain(mergeLocallyBtnText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when allowed to merge and push to source branch', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
queryData: {
|
||||
canMerge: true,
|
||||
canPushToSourceBranch: true,
|
||||
},
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictResolutionPath: path,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should tell you about conflicts without bothering other people', () => {
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain(mergeConflictsText);
|
||||
expect(text).not.toContain(userCannotMergeText);
|
||||
});
|
||||
|
||||
it('should allow you to resolve the conflicts', () => {
|
||||
expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
|
||||
expect(findResolveButton().attributes('href')).toEqual(path);
|
||||
});
|
||||
|
||||
it('should have merge buttons', () => {
|
||||
expect(findMergeLocalButton().text()).toContain(mergeLocallyBtnText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user does not have permission to push to source branch', () => {
|
||||
it('should show proper message', async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canMerge: false,
|
||||
canPushToSourceBranch: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text().trim().replace(/\s\s+/g, ' ')).toContain(userCannotMergeText);
|
||||
});
|
||||
|
||||
it('should not have action buttons', async () => {
|
||||
await createComponent({
|
||||
queryData: {
|
||||
canMerge: false,
|
||||
canPushToSourceBranch: false,
|
||||
},
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(findResolveButton().exists()).toBe(false);
|
||||
expect(findMergeLocalButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not have resolve button when no conflict resolution path', async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictResolutionPath: null,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canMerge: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findResolveButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fast-forward or semi-linear merge enabled', () => {
|
||||
it('should tell you to rebase locally', async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
shouldBeRebased: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(removeBreakLine(wrapper.text()).trim()).toContain(fastForwardMergeText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when source branch protected', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictResolutionPath: TEST_HOST,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canMerge: true,
|
||||
sourceBranchProtected: true,
|
||||
canPushToSourceBranch: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow you to resolve the conflicts', () => {
|
||||
expect(findResolveButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when source branch not protected', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({
|
||||
propsData: {
|
||||
mr: {
|
||||
conflictResolutionPath: TEST_HOST,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
},
|
||||
queryData: {
|
||||
canPushToSourceBranch: true,
|
||||
canMerge: true,
|
||||
sourceBranchProtected: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow you to resolve the conflicts', () => {
|
||||
expect(findResolveButton().text()).toContain(resolveConflictsBtnText);
|
||||
expect(findResolveButton().attributes('href')).toEqual(TEST_HOST);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error states', () => {
|
||||
it('when project is null due to expired session it does not throw', async () => {
|
||||
const fn = async () => {
|
||||
await createComponent({
|
||||
propsData: { mr: {} },
|
||||
apolloProvider: createMockApollo([
|
||||
[conflictsStateQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
|
||||
[userPermissionsQuery, jest.fn().mockResolvedValue({ data: { project: null } })],
|
||||
]),
|
||||
});
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
await expect(fn()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue';
|
||||
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
|
||||
describe('MRWidgetNotAllowed', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(notAllowedComponent);
|
||||
});
|
||||
|
||||
it('renders success icon', () => {
|
||||
expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(StatusIcon).props().status).toBe('success');
|
||||
});
|
||||
|
||||
it('renders informative text', () => {
|
||||
const message = wrapper.findComponent(BoldText).props('message');
|
||||
expect(message).toContain('Ready to be merged automatically.');
|
||||
expect(message).toContain(
|
||||
'Ask someone with write access to this repository to merge this request',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
|
||||
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
|
||||
describe('MRWidgetPipelineBlocked', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(PipelineBlockedComponent);
|
||||
});
|
||||
|
||||
it('renders error icon', () => {
|
||||
expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('renders information text', () => {
|
||||
const message = wrapper.findComponent(BoldText).props('message');
|
||||
expect(message).toContain('Merge blocked:');
|
||||
expect(message).toContain(
|
||||
"pipeline must succeed. It's waiting for a manual action to continue.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import { GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
|
||||
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
|
||||
import BoldText from '~/vue_merge_request_widget/components/bold_text.vue';
|
||||
|
||||
describe('PipelineFailed', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (mr = {}) => {
|
||||
wrapper = shallowMount(PipelineFailed, {
|
||||
propsData: {
|
||||
mr,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
it('should render error status icon', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('should render error message with a disabled merge button', () => {
|
||||
createComponent();
|
||||
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain('Merge blocked:');
|
||||
expect(text).toContain('pipeline must succeed');
|
||||
expect(text).toContain('Push a commit that fixes the failure');
|
||||
expect(wrapper.findComponent(GlLink).text()).toContain('learn about other solutions');
|
||||
});
|
||||
|
||||
it('should render pipeline blocked message', () => {
|
||||
createComponent({ isPipelineBlocked: true });
|
||||
|
||||
const message = wrapper.findComponent(BoldText).props('message');
|
||||
expect(message).toContain('Merge blocked:');
|
||||
expect(message).toContain(
|
||||
"pipeline must succeed. It's waiting for a manual action to continue.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import notesEventHub from '~/notes/event_hub';
|
||||
import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
|
||||
|
||||
function createComponent({ path = '' } = {}) {
|
||||
return mount(UnresolvedDiscussions, {
|
||||
propsData: {
|
||||
mr: {
|
||||
createIssueToResolveDiscussionsPath: path,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('UnresolvedDiscussions', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
it('triggers the correct notes event when the go to first unresolved discussion button is clicked', () => {
|
||||
jest.spyOn(notesEventHub, '$emit');
|
||||
|
||||
wrapper.find('[data-testid="jump-to-first"]').trigger('click');
|
||||
|
||||
expect(notesEventHub.$emit).toHaveBeenCalledWith('jumpToFirstUnresolvedDiscussion');
|
||||
});
|
||||
|
||||
describe('with threads path', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({ path: TEST_HOST });
|
||||
});
|
||||
|
||||
it('should have correct elements', () => {
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain('Merge blocked:');
|
||||
expect(text).toContain('all threads must be resolved.');
|
||||
|
||||
expect(wrapper.element.innerText).toContain('Go to first unresolved thread');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without threads path', () => {
|
||||
it('should not show create issue link if user cannot create issue', () => {
|
||||
const text = removeBreakLine(wrapper.text()).trim();
|
||||
expect(text).toContain('Merge blocked:');
|
||||
expect(text).toContain('all threads must be resolved.');
|
||||
|
||||
expect(wrapper.element.innerText).toContain('Go to first unresolved thread');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
|
||||
import { createAlert } from '~/alert';
|
||||
import WorkInProgress, {
|
||||
MSG_SOMETHING_WENT_WRONG,
|
||||
MSG_MARK_READY,
|
||||
} from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
|
||||
import draftQuery from '~/vue_merge_request_widget/queries/states/draft.query.graphql';
|
||||
import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
|
||||
import removeDraftMutation from '~/vue_merge_request_widget/queries/toggle_draft.mutation.graphql';
|
||||
import MergeRequest from '~/merge_request';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const TEST_PROJECT_ID = getStateQueryResponse.data.project.id;
|
||||
const TEST_MR_ID = getStateQueryResponse.data.project.mergeRequest.id;
|
||||
const TEST_MR_IID = '23';
|
||||
const TEST_MR_TITLE = 'Test MR Title';
|
||||
const TEST_PROJECT_PATH = 'lorem/ipsum';
|
||||
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/merge_request', () => ({ toggleDraftStatus: jest.fn() }));
|
||||
|
||||
describe('~/vue_merge_request_widget/components/states/work_in_progress.vue', () => {
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
|
||||
let draftQuerySpy;
|
||||
let removeDraftMutationSpy;
|
||||
|
||||
const findWIPButton = () => wrapper.findByTestId('removeWipButton');
|
||||
|
||||
const createDraftQueryResponse = (canUpdateMergeRequest) => ({
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
id: TEST_PROJECT_ID,
|
||||
mergeRequest: {
|
||||
__typename: 'MergeRequest',
|
||||
id: TEST_MR_ID,
|
||||
draft: true,
|
||||
title: TEST_MR_TITLE,
|
||||
mergeableDiscussionsState: false,
|
||||
userPermissions: {
|
||||
updateMergeRequest: canUpdateMergeRequest,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const createRemoveDraftMutationResponse = () => ({
|
||||
data: {
|
||||
mergeRequestSetDraft: {
|
||||
__typename: 'MergeRequestSetWipPayload',
|
||||
errors: [],
|
||||
mergeRequest: {
|
||||
__typename: 'MergeRequest',
|
||||
id: TEST_MR_ID,
|
||||
title: TEST_MR_TITLE,
|
||||
draft: false,
|
||||
mergeableDiscussionsState: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createComponent = async () => {
|
||||
wrapper = mountExtended(WorkInProgress, {
|
||||
apolloProvider,
|
||||
propsData: {
|
||||
mr: {
|
||||
issuableId: TEST_MR_ID,
|
||||
title: TEST_MR_TITLE,
|
||||
iid: TEST_MR_IID,
|
||||
targetProjectFullPath: TEST_PROJECT_PATH,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
// why: work_in_progress.vue has some coupling that this query has been read before
|
||||
// for some reason this has to happen **after** the component has mounted
|
||||
// or apollo throws errors.
|
||||
apolloProvider.defaultClient.cache.writeQuery({
|
||||
query: getStateQuery,
|
||||
variables: {
|
||||
projectPath: TEST_PROJECT_PATH,
|
||||
iid: TEST_MR_IID,
|
||||
},
|
||||
data: getStateQueryResponse.data,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
draftQuerySpy = jest.fn().mockResolvedValue(createDraftQueryResponse(true));
|
||||
removeDraftMutationSpy = jest.fn().mockResolvedValue(createRemoveDraftMutationResponse());
|
||||
|
||||
apolloProvider = createMockApollo([
|
||||
[draftQuery, draftQuerySpy],
|
||||
[removeDraftMutation, removeDraftMutationSpy],
|
||||
]);
|
||||
});
|
||||
|
||||
describe('when user can update MR', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('renders text', () => {
|
||||
const message = wrapper.text();
|
||||
expect(message).toContain('Merge blocked:');
|
||||
expect(message).toContain('Select Mark as ready to remove it from Draft status.');
|
||||
});
|
||||
|
||||
it('renders mark ready button', () => {
|
||||
expect(findWIPButton().text()).toBe(MSG_MARK_READY);
|
||||
});
|
||||
|
||||
it('does not call remove draft mutation', () => {
|
||||
expect(removeDraftMutationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when mark ready button is clicked', () => {
|
||||
beforeEach(async () => {
|
||||
findWIPButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('calls mutation spy', () => {
|
||||
expect(removeDraftMutationSpy).toHaveBeenCalledWith({
|
||||
draft: false,
|
||||
iid: TEST_MR_IID,
|
||||
projectPath: TEST_PROJECT_PATH,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not create alert', () => {
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls toggleDraftStatus', () => {
|
||||
expect(MergeRequest.toggleDraftStatus).toHaveBeenCalledWith(TEST_MR_TITLE, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mutation fails and ready button is clicked', () => {
|
||||
beforeEach(async () => {
|
||||
removeDraftMutationSpy.mockRejectedValue(new Error('TEST FAIL'));
|
||||
findWIPButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('creates alert', () => {
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: MSG_SOMETHING_WENT_WRONG,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call toggleDraftStatus', () => {
|
||||
expect(MergeRequest.toggleDraftStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user cannot update MR', () => {
|
||||
beforeEach(async () => {
|
||||
draftQuerySpy.mockResolvedValue(createDraftQueryResponse(false));
|
||||
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('does not render mark ready button', () => {
|
||||
expect(findWIPButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when project is null', () => {
|
||||
beforeEach(async () => {
|
||||
draftQuerySpy.mockResolvedValue({ data: { project: null } });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
// This is to mitigate https://gitlab.com/gitlab-org/gitlab/-/issues/413627
|
||||
it('does not throw any error', () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -19,7 +19,6 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta
|
|||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
|
||||
import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
|
||||
import ConflictsState from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
|
||||
import Preparing from '~/vue_merge_request_widget/components/states/mr_widget_preparing.vue';
|
||||
import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
|
||||
import MergedState from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
|
||||
|
|
@ -35,6 +34,8 @@ import approvalsQuery from 'ee_else_ce/vue_merge_request_widget/components/appro
|
|||
import approvedBySubscription from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql';
|
||||
import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
|
||||
import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
|
||||
import mergeChecksQuery from '~/vue_merge_request_widget/queries/merge_checks.query.graphql';
|
||||
import mergeChecksSubscription from '~/vue_merge_request_widget/queries/merge_checks.subscription.graphql';
|
||||
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
|
||||
|
||||
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
|
||||
|
|
@ -104,12 +105,24 @@ describe('MrWidgetOptions', () => {
|
|||
jest.fn().mockResolvedValue({ data: { project: { mergeRequest: {} } } }),
|
||||
],
|
||||
[securityReportMergeRequestDownloadPathsQuery, jest.fn().mockResolvedValue(null)],
|
||||
[
|
||||
mergeChecksQuery,
|
||||
jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
project: {
|
||||
id: 1,
|
||||
mergeRequest: { id: 1, userPermissions: { canMerge: true }, mergeabilityChecks: [] },
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
...(options.apolloMock || []),
|
||||
];
|
||||
const subscriptionHandlers = [
|
||||
[approvedBySubscription, () => mockedApprovalsSubscription],
|
||||
[getStateSubscription, stateSubscriptionHandler],
|
||||
[readyToMergeSubscription, () => createMockApolloSubscription()],
|
||||
[mergeChecksSubscription, () => createMockApolloSubscription()],
|
||||
];
|
||||
const apolloProvider = createMockApollo(queryHandlers);
|
||||
|
||||
|
|
@ -158,10 +171,9 @@ describe('MrWidgetOptions', () => {
|
|||
describe('computed', () => {
|
||||
describe('componentName', () => {
|
||||
it.each`
|
||||
state | componentName | component
|
||||
${STATUS_MERGED} | ${'MergedState'} | ${MergedState}
|
||||
${'conflicts'} | ${'ConflictsState'} | ${ConflictsState}
|
||||
${'shaMismatch'} | ${'ShaMismatch'} | ${ShaMismatch}
|
||||
state | componentName | component
|
||||
${STATUS_MERGED} | ${'MergedState'} | ${MergedState}
|
||||
${'shaMismatch'} | ${'ShaMismatch'} | ${ShaMismatch}
|
||||
`('should translate $state into $componentName component', async ({ state, component }) => {
|
||||
await createComponent();
|
||||
Vue.set(wrapper.vm.mr, 'state', state);
|
||||
|
|
|
|||
|
|
@ -22,45 +22,15 @@ describe('getStateKey', () => {
|
|||
|
||||
expect(bound()).toEqual('preparing');
|
||||
|
||||
context.detailedMergeStatus = null;
|
||||
|
||||
expect(bound()).toEqual('checking');
|
||||
|
||||
context.detailedMergeStatus = 'MERGEABLE';
|
||||
|
||||
expect(bound()).toEqual('readyToMerge');
|
||||
|
||||
context.autoMergeEnabled = true;
|
||||
context.hasMergeableDiscussionsState = true;
|
||||
|
||||
expect(bound()).toEqual('autoMergeEnabled');
|
||||
|
||||
context.canMerge = true;
|
||||
context.isSHAMismatch = true;
|
||||
|
||||
expect(bound()).toEqual('shaMismatch');
|
||||
|
||||
context.canMerge = false;
|
||||
context.detailedMergeStatus = 'DISCUSSIONS_NOT_RESOLVED';
|
||||
|
||||
expect(bound()).toEqual('unresolvedDiscussions');
|
||||
|
||||
context.detailedMergeStatus = 'DRAFT_STATUS';
|
||||
|
||||
expect(bound()).toEqual('draft');
|
||||
|
||||
context.detailedMergeStatus = 'CI_MUST_PASS';
|
||||
|
||||
expect(bound()).toEqual('pipelineFailed');
|
||||
|
||||
context.shouldBeRebased = true;
|
||||
|
||||
expect(bound()).toEqual('rebase');
|
||||
|
||||
context.hasConflicts = true;
|
||||
|
||||
expect(bound()).toEqual('conflicts');
|
||||
|
||||
context.detailedMergeStatus = 'CHECKING';
|
||||
|
||||
expect(bound()).toEqual('checking');
|
||||
|
|
@ -78,26 +48,4 @@ describe('getStateKey', () => {
|
|||
|
||||
expect(bound()).toEqual('archived');
|
||||
});
|
||||
|
||||
it('returns rebased state key', () => {
|
||||
const context = {
|
||||
mergeStatus: 'checked',
|
||||
autoMergeEnabled: false,
|
||||
canMerge: true,
|
||||
onlyAllowMergeIfPipelineSucceeds: true,
|
||||
isPipelineFailed: true,
|
||||
hasMergeableDiscussionsState: false,
|
||||
isPipelineBlocked: false,
|
||||
canBeMerged: false,
|
||||
shouldBeRebased: true,
|
||||
projectArchived: false,
|
||||
branchMissing: false,
|
||||
commitsCount: 2,
|
||||
hasConflicts: false,
|
||||
draft: false,
|
||||
};
|
||||
const bound = getStateKey.bind(context);
|
||||
|
||||
expect(bound()).toEqual('rebase');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -98,26 +98,6 @@ RSpec.describe Gitlab::Database::Partitioning::CiSlidingListStrategy, feature_ca
|
|||
end
|
||||
end
|
||||
|
||||
describe '#partition_for_id' do
|
||||
subject(:partition_for_id) { strategy.partition_for_id(id) }
|
||||
|
||||
context 'when partition_id matches any partition' do
|
||||
let(:id) { 101 }
|
||||
|
||||
it 'returns the partition' do
|
||||
expect(partition_for_id).to eq(strategy.active_partition)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when partition_id does not match any partition' do
|
||||
let(:id) { non_existing_record_id }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(partition_for_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#missing_partitions' do
|
||||
context 'when next_partition_if returns true' do
|
||||
let(:next_partition_if) { proc { |partition| partition.values.max < 102 } }
|
||||
|
|
|
|||
|
|
@ -31,6 +31,64 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model, feature_catego
|
|||
|
||||
it_behaves_like 'a postgres model'
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.with_parent_tables' do
|
||||
subject(:with_parent_tables) { described_class.with_parent_tables(parent_tables) }
|
||||
|
||||
let(:parent_tables) { ['_test_partitioned_table'] }
|
||||
|
||||
it 'returns all partitions with parent tables', :aggregate_failures do
|
||||
results = with_parent_tables
|
||||
|
||||
expect(results.size).to eq(1)
|
||||
expect(results.first.identifier).to eq(identifier)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_list_constraint' do
|
||||
subject(:with_list_constraint) { described_class.with_list_constraint(partition_id) }
|
||||
|
||||
context 'when condition matches' do
|
||||
let(:partition_id) { '102' }
|
||||
let(:expected_size) { Ci::Partitionable.registered_models.size }
|
||||
|
||||
it 'returns the partitions containing the match' do
|
||||
results = with_list_constraint
|
||||
|
||||
expect(results.size).to eq(expected_size)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when condition does not match' do
|
||||
let(:partition_id) { non_existing_record_id }
|
||||
|
||||
it 'returns an empty relation' do
|
||||
expect(with_list_constraint).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.above_threshold' do
|
||||
subject(:above_threshold) { described_class.above_threshold(threshold) }
|
||||
|
||||
context 'when the partition size is above a given threshold' do
|
||||
let(:threshold) { 1.byte }
|
||||
|
||||
it 'returns all partitions above the threshold' do
|
||||
expect(above_threshold.size).not_to be_zero
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the partition size is below a given threshold' do
|
||||
let(:threshold) { 100.megabytes }
|
||||
|
||||
it 'returns an empty relation' do
|
||||
expect(above_threshold).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.for_parent_table' do
|
||||
let(:second_name) { '_test_partition_02' }
|
||||
|
||||
|
|
|
|||
|
|
@ -57,36 +57,6 @@ RSpec.describe Gitlab::RackAttack::Request, feature_category: :rate_limiting do
|
|||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rate_limit_oauth_api feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(rate_limit_oauth_api: false)
|
||||
end
|
||||
|
||||
where(:path, :expected) do
|
||||
'/' | false
|
||||
'/groups' | false
|
||||
'/foo/api' | false
|
||||
|
||||
'/api' | true
|
||||
'/api/v4/groups/1' | true
|
||||
|
||||
'/oauth/tokens' | false
|
||||
'/oauth/userinfo' | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(expected) }
|
||||
|
||||
context 'when the application is mounted at a relative URL' do
|
||||
before do
|
||||
stub_config_setting(relative_url_root: '/gitlab/root')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#api_internal_request?' do
|
||||
|
|
|
|||
|
|
@ -318,18 +318,6 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping
|
|||
'RedisHLLMetric' | { events: [2] } | false
|
||||
'RedisHLLMetric' | { events: 'a' } | false
|
||||
'RedisHLLMetric' | { event: ['a'] } | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'user.id' }, events: ['a'] } | true
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'project.id' }, events: %w[b c] } | true
|
||||
'AggregatedMetric' | nil | false
|
||||
'AggregatedMetric' | {} | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'user.id' }, events: ['a'], event: 'a' } | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'user.id' } } | false
|
||||
'AggregatedMetric' | { events: ['a'] } | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'user.id' }, events: 'a' } | false
|
||||
'AggregatedMetric' | { aggregate: 'a', events: ['a'] } | false
|
||||
'AggregatedMetric' | { aggregate: {}, events: ['a'] } | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: 'user.id', a: 'b' }, events: ['a'] } | false
|
||||
'AggregatedMetric' | { aggregate: { attribute: ['user.id'] }, events: ['a'] } | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue