Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
48640cf76a
commit
8ed0a009f0
|
@ -747,3 +747,6 @@ Performance/ActiveRecordSubtransactionMethods:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/**/*.rb'
|
- 'spec/**/*.rb'
|
||||||
- 'ee/spec/**/*.rb'
|
- 'ee/spec/**/*.rb'
|
||||||
|
|
||||||
|
Migration/BackgroundMigrationBaseClass:
|
||||||
|
Enabled: false
|
||||||
|
|
|
@ -11,12 +11,6 @@ Gitlab/PolicyRuleBoolean:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'ee/app/policies/ee/identity_provider_policy.rb'
|
- 'ee/app/policies/ee/identity_provider_policy.rb'
|
||||||
|
|
||||||
# Offense count: 22
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: AllowComments.
|
|
||||||
Lint/UselessMethodDefinition:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 218
|
# Offense count: 218
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: PreferredName.
|
# Configuration parameters: PreferredName.
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
---
|
||||||
|
Migration/BackgroundMigrationBaseClass:
|
||||||
|
Exclude:
|
||||||
|
- 'lib/gitlab/background_migration/add_primary_email_to_emails_if_user_confirmed.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_artifact_expiry_date.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_ci_namespace_mirrors.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_ci_project_mirrors.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_ci_queuing_tables.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_draft_status_on_merge_requests.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_integrations_type_new.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_issue_search_data.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_jira_tracker_deployment_type2.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_children.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_projects_with_coverage.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_project_repositories.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_project_settings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_snippet_repositories.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_topics_title.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_upvotes_count_on_issues.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_user_namespace.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_work_item_type_id_for_issues.rb'
|
||||||
|
- 'lib/gitlab/background_migration/cleanup_draft_data_from_faulty_regex.rb'
|
||||||
|
- 'lib/gitlab/background_migration/cleanup_orphaned_lfs_objects_projects.rb'
|
||||||
|
- 'lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans.rb'
|
||||||
|
- 'lib/gitlab/background_migration/create_security_setting.rb'
|
||||||
|
- 'lib/gitlab/background_migration/delete_orphaned_deployments.rb'
|
||||||
|
- 'lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images.rb'
|
||||||
|
- 'lib/gitlab/background_migration/drop_invalid_remediations.rb'
|
||||||
|
- 'lib/gitlab/background_migration/drop_invalid_security_findings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/drop_invalid_vulnerabilities.rb'
|
||||||
|
- 'lib/gitlab/background_migration/encrypt_integration_properties.rb'
|
||||||
|
- 'lib/gitlab/background_migration/encrypt_static_object_token.rb'
|
||||||
|
- 'lib/gitlab/background_migration/extract_project_topics_into_separate_table.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_duplicate_project_name_and_path.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_incorrect_max_seats_used.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_projects_without_project_feature.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb'
|
||||||
|
- 'lib/gitlab/background_migration/fix_vulnerability_occurrences_with_hashes_as_raw_metadata.rb'
|
||||||
|
- 'lib/gitlab/background_migration/legacy_uploads_migrator.rb'
|
||||||
|
- 'lib/gitlab/background_migration/legacy_upload_mover.rb'
|
||||||
|
- 'lib/gitlab/background_migration/merge_topics_with_same_name.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_approver_to_approval_rules.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_approver_to_approval_rules_check_progress.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_approver_to_approval_rules_in_batch.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_job_artifact_registry_to_ssf.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_merge_request_diff_commit_users.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_pages_to_zip_storage.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_personal_namespace_project_maintainer_to_owner.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_requirements_to_work_items.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_stage_status.rb'
|
||||||
|
- 'lib/gitlab/background_migration/migrate_u2f_webauthn.rb'
|
||||||
|
- 'lib/gitlab/background_migration/move_container_registry_enabled_to_project_feature.rb'
|
||||||
|
- 'lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_container_repository_migration_plan.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_latest_pipeline_ids.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_namespace_statistics.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_status_column_of_security_scans.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_test_reports_issue_id.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_topics_non_private_projects_count.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_topics_total_projects_count_cache.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_uuids_for_security_findings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/populate_vulnerability_reads.rb'
|
||||||
|
- 'lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid.rb'
|
||||||
|
- 'lib/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/remove_all_trace_expiration_dates.rb'
|
||||||
|
- 'lib/gitlab/background_migration/remove_duplicate_vulnerabilities_findings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb'
|
||||||
|
- 'lib/gitlab/background_migration/remove_vulnerability_finding_links.rb'
|
||||||
|
- 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects.rb'
|
||||||
|
- 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects.rb'
|
||||||
|
- 'lib/gitlab/background_migration/steal_migrate_merge_request_diff_commit_users.rb'
|
||||||
|
- 'lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb'
|
||||||
|
- 'lib/gitlab/background_migration/update_timelogs_null_spent_at.rb'
|
||||||
|
- 'lib/gitlab/background_migration/update_timelogs_project_id.rb'
|
||||||
|
- 'lib/gitlab/background_migration/update_users_where_two_factor_auth_required_from_group.rb'
|
||||||
|
- 'lib/gitlab/background_migration/update_vulnerability_occurrences_location.rb'
|
||||||
|
- 'lib/gitlab/background_migration/mailers/unconfirm_mailer.rb'
|
||||||
|
- 'lib/gitlab/background_migration/project_namespaces/models/project.rb'
|
||||||
|
- 'lib/gitlab/background_migration/project_namespaces/models/namespace.rb'
|
||||||
|
- 'lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb'
|
||||||
|
- 'lib/gitlab/background_migration/backfill_integrations_enable_ssl_verification.rb'
|
|
@ -28,6 +28,22 @@ const fetchMetricsData = (reqs = [], path, params) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const extractMetricsGroupData = (keyList = [], data = []) => {
|
||||||
|
if (!keyList.length || !data.length) return [];
|
||||||
|
return data.filter(({ identifier = '' }) => identifier.length && keyList.includes(identifier));
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupRawMetrics = (groups = [], rawData = []) => {
|
||||||
|
return groups.map((curr) => {
|
||||||
|
const { keys, ...rest } = curr;
|
||||||
|
return {
|
||||||
|
data: extractMetricsGroupData(keys, rawData),
|
||||||
|
keys,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ValueStreamMetrics',
|
name: 'ValueStreamMetrics',
|
||||||
components: {
|
components: {
|
||||||
|
@ -52,13 +68,24 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
groupBy: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
metrics: [],
|
metrics: [],
|
||||||
|
groupedMetrics: [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
hasGroupedMetrics() {
|
||||||
|
return Boolean(this.groupBy.length);
|
||||||
|
},
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
requestParams(newVal, oldVal) {
|
requestParams(newVal, oldVal) {
|
||||||
if (!isEqual(newVal, oldVal)) {
|
if (!isEqual(newVal, oldVal)) {
|
||||||
|
@ -76,6 +103,11 @@ export default {
|
||||||
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
|
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.metrics = this.filterFn ? this.filterFn(data) : data;
|
this.metrics = this.filterFn ? this.filterFn(data) : data;
|
||||||
|
|
||||||
|
if (this.hasGroupedMetrics) {
|
||||||
|
this.groupedMetrics = groupRawMetrics(this.groupBy, this.metrics);
|
||||||
|
}
|
||||||
|
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -86,14 +118,35 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="gl-display-flex gl-flex-wrap" data-testid="vsa-metrics">
|
<div class="gl-display-flex gl-mt-6" data-testid="vsa-metrics">
|
||||||
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
|
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
|
||||||
<metric-tile
|
<template v-else>
|
||||||
v-for="metric in metrics"
|
<div v-if="hasGroupedMetrics" class="gl-flex-direction-column">
|
||||||
v-show="!isLoading"
|
<div
|
||||||
:key="metric.identifier"
|
v-for="group in groupedMetrics"
|
||||||
:metric="metric"
|
:key="group.key"
|
||||||
class="gl-my-6 gl-pr-9"
|
class="gl-mb-7"
|
||||||
/>
|
data-testid="vsa-metrics-group"
|
||||||
|
>
|
||||||
|
<h4 class="gl-my-0">{{ group.title }}</h4>
|
||||||
|
<div class="gl-display-flex gl-flex-wrap">
|
||||||
|
<metric-tile
|
||||||
|
v-for="metric in group.data"
|
||||||
|
:key="metric.identifier"
|
||||||
|
:metric="metric"
|
||||||
|
class="gl-mt-5 gl-pr-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="gl-display-flex gl-flex-wrap gl-mb-7">
|
||||||
|
<metric-tile
|
||||||
|
v-for="metric in metrics"
|
||||||
|
:key="metric.identifier"
|
||||||
|
:metric="metric"
|
||||||
|
class="gl-mt-5 gl-pr-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -56,3 +56,14 @@ export const METRICS_POPOVER_CONTENT = {
|
||||||
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
|
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const KEY_METRICS_TITLE = s__('ValueStreamAnalytics|Key metrics');
|
||||||
|
const KEY_METRICS_KEYS = ['lead_time', 'cycle_time', 'issues', 'commits', 'deploys'];
|
||||||
|
|
||||||
|
const DORA_METRICS_TITLE = s__('ValueStreamAnalytics|DORA metrics');
|
||||||
|
const DORA_METRICS_KEYS = ['deployment_frequency', 'lead_time_for_changes'];
|
||||||
|
|
||||||
|
export const VSA_METRICS_GROUPS = [
|
||||||
|
{ key: 'key_metrics', title: KEY_METRICS_TITLE, keys: KEY_METRICS_KEYS },
|
||||||
|
{ key: 'dora_metrics', title: DORA_METRICS_TITLE, keys: DORA_METRICS_KEYS },
|
||||||
|
];
|
||||||
|
|
|
@ -80,17 +80,14 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
|
class="board-add-new-list board gl-display-inline-block gl-h-full gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0 gl-rounded-base gl-px-3"
|
||||||
data-testid="board-add-new-column"
|
data-testid="board-add-new-column"
|
||||||
data-qa-selector="board_add_new_list"
|
data-qa-selector="board_add_new_list"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
|
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
|
||||||
>
|
>
|
||||||
<h3
|
<h3 class="gl-font-size-h2 gl-px-5 gl-py-5 gl-m-0" data-testid="board-add-column-form-title">
|
||||||
class="gl-font-size-h2 gl-px-5 gl-py-4 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
|
|
||||||
data-testid="board-add-column-form-title"
|
|
||||||
>
|
|
||||||
{{ $options.i18n.newList }}
|
{{ $options.i18n.newList }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
@ -98,7 +95,7 @@ export default {
|
||||||
class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-y-auto gl-align-items-flex-start"
|
class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-y-auto gl-align-items-flex-start"
|
||||||
>
|
>
|
||||||
<div class="gl-px-5">
|
<div class="gl-px-5">
|
||||||
<h3 class="gl-font-lg gl-mt-5 gl-mb-2">
|
<h3 class="gl-font-lg gl-mt-3 gl-mb-2">
|
||||||
{{ $options.i18n.scope }}
|
{{ $options.i18n.scope }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
|
<p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
|
||||||
|
@ -147,23 +144,18 @@ export default {
|
||||||
</gl-dropdown>
|
</gl-dropdown>
|
||||||
</gl-form-group>
|
</gl-form-group>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="gl-display-flex gl-mb-4">
|
||||||
class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
|
|
||||||
>
|
|
||||||
<gl-button
|
|
||||||
data-testid="cancelAddNewColumn"
|
|
||||||
class="gl-ml-auto gl-mr-3"
|
|
||||||
@click="setAddColumnFormVisibility(false)"
|
|
||||||
>{{ $options.i18n.cancel }}</gl-button
|
|
||||||
>
|
|
||||||
<gl-button
|
<gl-button
|
||||||
data-testid="addNewColumnButton"
|
data-testid="addNewColumnButton"
|
||||||
:disabled="!selectedId"
|
:disabled="!selectedId"
|
||||||
variant="confirm"
|
variant="confirm"
|
||||||
class="gl-mr-4"
|
class="gl-mr-3 gl-ml-4"
|
||||||
@click="$emit('add-list')"
|
@click="$emit('add-list')"
|
||||||
>{{ $options.i18n.add }}</gl-button
|
>{{ $options.i18n.add }}</gl-button
|
||||||
>
|
>
|
||||||
|
<gl-button data-testid="cancelAddNewColumn" @click="setAddColumnFormVisibility(false)">{{
|
||||||
|
$options.i18n.cancel
|
||||||
|
}}</gl-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -75,7 +75,7 @@ export default {
|
||||||
v-if="!isSwimlanesOn"
|
v-if="!isSwimlanesOn"
|
||||||
ref="list"
|
ref="list"
|
||||||
v-bind="draggableOptions"
|
v-bind="draggableOptions"
|
||||||
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
|
class="boards-list gl-w-full gl-py-5 gl-pr-3 gl-white-space-nowrap"
|
||||||
@end="moveList"
|
@end="moveList"
|
||||||
>
|
>
|
||||||
<board-column
|
<board-column
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
||||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||||
import { getCookie, setCookie } from '~/lib/utils/common_utils';
|
import { getCookie, setCookie } from '~/lib/utils/common_utils';
|
||||||
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
||||||
|
import { VSA_METRICS_GROUPS } from '~/analytics/shared/constants';
|
||||||
import { toYmd } from '~/analytics/shared/utils';
|
import { toYmd } from '~/analytics/shared/utils';
|
||||||
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
|
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
|
||||||
import StageTable from '~/cycle_analytics/components/stage_table.vue';
|
import StageTable from '~/cycle_analytics/components/stage_table.vue';
|
||||||
|
@ -150,6 +151,7 @@ export default {
|
||||||
pageTitle: __('Value Stream Analytics'),
|
pageTitle: __('Value Stream Analytics'),
|
||||||
recentActivity: __('Recent Project Activity'),
|
recentActivity: __('Recent Project Activity'),
|
||||||
},
|
},
|
||||||
|
VSA_METRICS_GROUPS,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -178,6 +180,7 @@ export default {
|
||||||
:request-path="endpoints.fullPath"
|
:request-path="endpoints.fullPath"
|
||||||
:request-params="filterParams"
|
:request-params="filterParams"
|
||||||
:requests="metricsRequests"
|
:requests="metricsRequests"
|
||||||
|
:group-by="$options.VSA_METRICS_GROUPS"
|
||||||
/>
|
/>
|
||||||
<gl-loading-icon v-if="isLoading" size="lg" />
|
<gl-loading-icon v-if="isLoading" size="lg" />
|
||||||
<stage-table
|
<stage-table
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlBadge, GlButton, GlLink, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
|
import {
|
||||||
|
GlBadge,
|
||||||
|
GlButton,
|
||||||
|
GlLink,
|
||||||
|
GlLoadingIcon,
|
||||||
|
GlTooltip,
|
||||||
|
GlTooltipDirective,
|
||||||
|
} from '@gitlab/ui';
|
||||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
|
@ -21,6 +28,7 @@ export default {
|
||||||
GlButton,
|
GlButton,
|
||||||
GlLink,
|
GlLink,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
GlTooltip,
|
||||||
},
|
},
|
||||||
actionSizeClasses: ['gl-h-7 gl-w-7'],
|
actionSizeClasses: ['gl-h-7 gl-w-7'],
|
||||||
mixins: [glFeatureFlagMixin()],
|
mixins: [glFeatureFlagMixin()],
|
||||||
|
@ -48,6 +56,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasActionTooltip: false,
|
||||||
isActionLoading: false,
|
isActionLoading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -139,13 +148,16 @@ export default {
|
||||||
showAction() {
|
showAction() {
|
||||||
return Boolean(this.action?.method && this.action?.icon && this.action?.ariaLabel);
|
return Boolean(this.action?.method && this.action?.icon && this.action?.ariaLabel);
|
||||||
},
|
},
|
||||||
|
showCardTooltip() {
|
||||||
|
return !this.hasActionTooltip;
|
||||||
|
},
|
||||||
sourceJobName() {
|
sourceJobName() {
|
||||||
return this.pipeline.sourceJob?.name ?? '';
|
return this.pipeline.sourceJob?.name ?? '';
|
||||||
},
|
},
|
||||||
sourceJobInfo() {
|
sourceJobInfo() {
|
||||||
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
|
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
|
||||||
},
|
},
|
||||||
tooltipText() {
|
cardTooltipText() {
|
||||||
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
|
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
|
||||||
${this.sourceJobInfo}`;
|
${this.sourceJobInfo}`;
|
||||||
},
|
},
|
||||||
|
@ -191,6 +203,9 @@ export default {
|
||||||
retryPipeline() {
|
retryPipeline() {
|
||||||
this.executePipelineAction(RetryPipelineMutation);
|
this.executePipelineAction(RetryPipelineMutation);
|
||||||
},
|
},
|
||||||
|
setActionTooltip(flag) {
|
||||||
|
this.hasActionTooltip = flag;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -198,14 +213,15 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="linkedPipeline"
|
ref="linkedPipeline"
|
||||||
v-gl-tooltip
|
|
||||||
class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
|
class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
|
||||||
:class="flexDirection"
|
:class="flexDirection"
|
||||||
:title="tooltipText"
|
|
||||||
data-qa-selector="linked_pipeline_container"
|
data-qa-selector="linked_pipeline_container"
|
||||||
@mouseover="onDownstreamHovered"
|
@mouseover="onDownstreamHovered"
|
||||||
@mouseleave="onDownstreamHoverLeave"
|
@mouseleave="onDownstreamHoverLeave"
|
||||||
>
|
>
|
||||||
|
<gl-tooltip v-if="showCardTooltip" :target="() => $refs.linkedPipeline">
|
||||||
|
{{ cardTooltipText }}
|
||||||
|
</gl-tooltip>
|
||||||
<div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass">
|
<div class="gl-w-full gl-bg-white gl-p-3" :class="cardSpacingClass">
|
||||||
<div class="gl-display-flex gl-pr-3">
|
<div class="gl-display-flex gl-pr-3">
|
||||||
<ci-status
|
<ci-status
|
||||||
|
@ -227,12 +243,16 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<gl-button
|
<gl-button
|
||||||
v-if="showAction"
|
v-if="showAction"
|
||||||
|
v-gl-tooltip
|
||||||
|
:title="action.ariaLabel"
|
||||||
:loading="isActionLoading"
|
:loading="isActionLoading"
|
||||||
:icon="action.icon"
|
:icon="action.icon"
|
||||||
class="gl-rounded-full!"
|
class="gl-rounded-full!"
|
||||||
:class="$options.actionSizeClasses"
|
:class="$options.actionSizeClasses"
|
||||||
:aria-label="action.ariaLabel"
|
:aria-label="action.ariaLabel"
|
||||||
@click="action.method"
|
@click="action.method"
|
||||||
|
@mouseover="setActionTooltip(true)"
|
||||||
|
@mouseout="setActionTooltip(false)"
|
||||||
/>
|
/>
|
||||||
<div v-else :class="$options.actionSizeClasses"></div>
|
<div v-else :class="$options.actionSizeClasses"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,6 +47,23 @@ export default {
|
||||||
|
|
||||||
return this.$options.i18n.noAttentionRequestedNoPermission;
|
return this.$options.i18n.noAttentionRequestedNoPermission;
|
||||||
},
|
},
|
||||||
|
request() {
|
||||||
|
const state = {
|
||||||
|
variant: 'default',
|
||||||
|
icon: 'attention',
|
||||||
|
direction: 'add',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.user.attention_requested) {
|
||||||
|
Object.assign(state, {
|
||||||
|
variant: 'warning',
|
||||||
|
icon: 'attention-solid',
|
||||||
|
direction: 'remove',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleAttentionRequired() {
|
toggleAttentionRequired() {
|
||||||
|
@ -57,6 +74,7 @@ export default {
|
||||||
this.$emit('toggle-attention-requested', {
|
this.$emit('toggle-attention-requested', {
|
||||||
user: this.user,
|
user: this.user,
|
||||||
callback: this.toggleAttentionRequiredComplete,
|
callback: this.toggleAttentionRequiredComplete,
|
||||||
|
direction: this.request.direction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleAttentionRequiredComplete() {
|
toggleAttentionRequiredComplete() {
|
||||||
|
@ -74,8 +92,8 @@ export default {
|
||||||
>
|
>
|
||||||
<gl-button
|
<gl-button
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:variant="user.attention_requested ? 'warning' : 'default'"
|
:variant="request.variant"
|
||||||
:icon="user.attention_requested ? 'attention-solid' : 'attention'"
|
:icon="request.icon"
|
||||||
:aria-label="tooltipTitle"
|
:aria-label="tooltipTitle"
|
||||||
:class="{ 'gl-pointer-events-none': !user.can_update_merge_request }"
|
:class="{ 'gl-pointer-events-none': !user.can_update_merge_request }"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation mergeRequestRemoveAttentionRequest($projectPath: ID!, $iid: String!, $userId: ID!) {
|
||||||
|
mergeRequestRemoveAttentionRequest(
|
||||||
|
input: { projectPath: $projectPath, iid: $iid, userId: $userId }
|
||||||
|
) {
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
mutation mergeRequestRequestAttention($projectPath: ID!, $iid: String!, $userId: ID!) {
|
||||||
|
mergeRequestRequestAttention(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
mutation mergeRequestToggleAttentionRequested($projectPath: ID!, $iid: String!, $userId: UserID!) {
|
|
||||||
mergeRequestToggleAttentionRequested(
|
|
||||||
input: { projectPath: $projectPath, iid: $iid, userId: $userId }
|
|
||||||
) {
|
|
||||||
errors
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,8 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
|
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
|
||||||
import sidebarDetailsMRQuery from '../queries/sidebar_details_mr.query.graphql';
|
import sidebarDetailsMRQuery from '../queries/sidebar_details_mr.query.graphql';
|
||||||
import toggleAttentionRequestedMutation from '../queries/toggle_attention_requested.mutation.graphql';
|
import requestAttentionMutation from '../queries/request_attention.mutation.graphql';
|
||||||
|
import removeAttentionRequestMutation from '../queries/remove_attention_request.mutation.graphql';
|
||||||
|
|
||||||
const queries = {
|
const queries = {
|
||||||
merge_request: sidebarDetailsMRQuery,
|
merge_request: sidebarDetailsMRQuery,
|
||||||
|
@ -92,9 +93,19 @@ export default class SidebarService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAttentionRequested(userId) {
|
requestAttention(userId) {
|
||||||
return gqClient.mutate({
|
return gqClient.mutate({
|
||||||
mutation: toggleAttentionRequestedMutation,
|
mutation: requestAttentionMutation,
|
||||||
|
variables: {
|
||||||
|
userId: convertToGraphQLId(TYPE_USER, `${userId}`),
|
||||||
|
projectPath: this.fullPath,
|
||||||
|
iid: this.iid.toString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
removeAttentionRequest(userId) {
|
||||||
|
return gqClient.mutate({
|
||||||
|
mutation: removeAttentionRequestMutation,
|
||||||
variables: {
|
variables: {
|
||||||
userId: convertToGraphQLId(TYPE_USER, `${userId}`),
|
userId: convertToGraphQLId(TYPE_USER, `${userId}`),
|
||||||
projectPath: this.fullPath,
|
projectPath: this.fullPath,
|
||||||
|
|
|
@ -98,14 +98,19 @@ export default class SidebarMediator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleAttentionRequested(type, { user, callback }) {
|
async toggleAttentionRequested(type, { user, callback, direction }) {
|
||||||
|
const mutations = {
|
||||||
|
add: (id) => this.service.requestAttention(id),
|
||||||
|
remove: (id) => this.service.removeAttentionRequest(id),
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isReviewer = type === 'reviewer';
|
const isReviewer = type === 'reviewer';
|
||||||
const reviewerOrAssignee = isReviewer
|
const reviewerOrAssignee = isReviewer
|
||||||
? this.store.findReviewer(user)
|
? this.store.findReviewer(user)
|
||||||
: this.store.findAssignee(user);
|
: this.store.findAssignee(user);
|
||||||
|
|
||||||
await this.service.toggleAttentionRequested(user.id);
|
await mutations[direction]?.(user.id);
|
||||||
|
|
||||||
if (reviewerOrAssignee.attention_requested) {
|
if (reviewerOrAssignee.attention_requested) {
|
||||||
toast(
|
toast(
|
||||||
|
@ -138,7 +143,7 @@ export default class SidebarMediator {
|
||||||
captureError: true,
|
captureError: true,
|
||||||
actionConfig: {
|
actionConfig: {
|
||||||
title: __('Try again'),
|
title: __('Try again'),
|
||||||
clickHandler: () => this.toggleAttentionRequired(type, { user, callback }),
|
clickHandler: () => this.toggleAttentionRequired(type, { user, callback, direction }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
height: calc(100vh - #{$issue-board-list-difference-xs});
|
height: calc(100vh - #{$issue-board-list-difference-xs});
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
border-left: 8px solid var(--gray-10, $white);
|
||||||
|
|
||||||
@include media-breakpoint-only(sm) {
|
@include media-breakpoint-only(sm) {
|
||||||
height: calc(100vh - #{$issue-board-list-difference-sm});
|
height: calc(100vh - #{$issue-board-list-difference-sm});
|
||||||
|
|
|
@ -29,13 +29,12 @@ class Import::BitbucketController < Import::BaseController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We need to re-expose controller's internal method 'status' as action.
|
||||||
|
# rubocop:disable Lint/UselessMethodDefinition
|
||||||
def status
|
def status
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Lint/UselessMethodDefinition
|
||||||
def realtime_changes
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
bitbucket_client = Bitbucket::Client.new(credentials)
|
bitbucket_client = Bitbucket::Client.new(credentials)
|
||||||
|
|
|
@ -52,13 +52,12 @@ class Import::BitbucketServerController < Import::BaseController
|
||||||
redirect_to status_import_bitbucket_server_path
|
redirect_to status_import_bitbucket_server_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We need to re-expose controller's internal method 'status' as action.
|
||||||
|
# rubocop:disable Lint/UselessMethodDefinition
|
||||||
def status
|
def status
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Lint/UselessMethodDefinition
|
||||||
def realtime_changes
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,6 @@ class Import::FogbugzController < Import::BaseController
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
def realtime_changes
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
repo = client.repo(params[:repo_id])
|
repo = client.repo(params[:repo_id])
|
||||||
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
|
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
|
||||||
|
|
|
@ -16,10 +16,12 @@ class Import::GiteaController < Import::GithubController
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Must be defined or it will 404
|
# We need to re-expose controller's internal method 'status' as action.
|
||||||
|
# rubocop:disable Lint/UselessMethodDefinition
|
||||||
def status
|
def status
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Lint/UselessMethodDefinition
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,12 @@ class Import::GitlabController < Import::BaseController
|
||||||
redirect_to status_import_gitlab_url
|
redirect_to status_import_gitlab_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We need to re-expose controller's internal method 'status' as action.
|
||||||
|
# rubocop:disable Lint/UselessMethodDefinition
|
||||||
def status
|
def status
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Lint/UselessMethodDefinition
|
||||||
|
|
||||||
def create
|
def create
|
||||||
repo = client.project(params[:repo_id].to_i)
|
repo = client.project(params[:repo_id].to_i)
|
||||||
|
|
|
@ -10,9 +10,12 @@ class Import::ManifestController < Import::BaseController
|
||||||
def new
|
def new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We need to re-expose controller's internal method 'status' as action.
|
||||||
|
# rubocop:disable Lint/UselessMethodDefinition
|
||||||
def status
|
def status
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Lint/UselessMethodDefinition
|
||||||
|
|
||||||
def upload
|
def upload
|
||||||
group = Group.find(params[:group_id])
|
group = Group.find(params[:group_id])
|
||||||
|
@ -36,10 +39,6 @@ class Import::ManifestController < Import::BaseController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def realtime_changes
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
repository = importable_repos.find do |project|
|
repository = importable_repos.find do |project|
|
||||||
project[:id] == params[:repo_id].to_i
|
project[:id] == params[:repo_id].to_i
|
||||||
|
|
|
@ -13,7 +13,9 @@ module Projects
|
||||||
prepend_before_action :repository, :project_without_auth
|
prepend_before_action :repository, :project_without_auth
|
||||||
|
|
||||||
feature_category :incident_management
|
feature_category :incident_management
|
||||||
urgency :medium, [:create]
|
# Goal is to increase the urgency to medium.
|
||||||
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/361310.
|
||||||
|
urgency :low, [:create]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
token = extract_alert_manager_token(request)
|
token = extract_alert_manager_token(request)
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TagsFinder < GitRefsFinder
|
class TagsFinder < GitRefsFinder
|
||||||
def initialize(repository, params)
|
|
||||||
super(repository, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute(gitaly_pagination: false)
|
def execute(gitaly_pagination: false)
|
||||||
tags = if gitaly_pagination
|
tags = if gitaly_pagination
|
||||||
repository.tags_sorted_by(sort, pagination_params)
|
repository.tags_sorted_by(sort, pagination_params)
|
||||||
|
|
|
@ -68,6 +68,12 @@ module IssueResolverArguments
|
||||||
description: 'Negated arguments.',
|
description: 'Negated arguments.',
|
||||||
prepare: ->(negated_args, ctx) { negated_args.to_h },
|
prepare: ->(negated_args, ctx) { negated_args.to_h },
|
||||||
required: false
|
required: false
|
||||||
|
argument :crm_contact_id, GraphQL::Types::String,
|
||||||
|
required: false,
|
||||||
|
description: 'ID of a contact assigned to the issues.'
|
||||||
|
argument :crm_organization_id, GraphQL::Types::String,
|
||||||
|
required: false,
|
||||||
|
description: 'ID of an organization assigned to the issues.'
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_with_lookahead(**args)
|
def resolve_with_lookahead(**args)
|
||||||
|
|
|
@ -93,6 +93,7 @@ module NamespacesHelper
|
||||||
namespace_actual_plan_name: namespace.actual_plan_name,
|
namespace_actual_plan_name: namespace.actual_plan_name,
|
||||||
namespace_path: namespace.full_path,
|
namespace_path: namespace.full_path,
|
||||||
namespace_id: namespace.id,
|
namespace_id: namespace.id,
|
||||||
|
user_namespace: namespace.user_namespace?.to_s,
|
||||||
page_size: page_size
|
page_size: page_size
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,9 +25,7 @@ module MergeRequests
|
||||||
|
|
||||||
# expose issuable create method so it can be called from email
|
# expose issuable create method so it can be called from email
|
||||||
# handler CreateMergeRequestHandler
|
# handler CreateMergeRequestHandler
|
||||||
def create(merge_request)
|
public :create
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -3,22 +3,13 @@
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :password_authentication_enabled_for_web,
|
||||||
= f.check_box :password_authentication_enabled_for_web, class: 'form-check-input'
|
_('Allow password authentication for the web interface'),
|
||||||
= f.label :password_authentication_enabled_for_web, class: 'form-check-label' do
|
help_text: _('Clear this checkbox to use an external authentication provider instead.')
|
||||||
= _('Allow password authentication for the web interface')
|
|
||||||
.form-text.text-muted
|
|
||||||
= _('Clear this checkbox to use an external authentication provider instead.')
|
|
||||||
.form-group
|
.form-group
|
||||||
.form-check
|
= f.gitlab_ui_checkbox_component :password_authentication_enabled_for_git,
|
||||||
= f.check_box :password_authentication_enabled_for_git, class: 'form-check-input'
|
_('Allow password authentication for Git over HTTP(S)'),
|
||||||
= f.label :password_authentication_enabled_for_git, class: 'form-check-label' do
|
help_text: Gitlab::Auth::Ldap::Config.enabled? ? _('Clear this checkbox to use a personal access token or LDAP password instead.') : _('Clear this checkbox to use a personal access token instead.')
|
||||||
= _('Allow password authentication for Git over HTTP(S)')
|
|
||||||
.form-text.text-muted
|
|
||||||
- if Gitlab::Auth::Ldap::Config.enabled?
|
|
||||||
= _('Clear this checkbox to use a personal access token or LDAP password instead.')
|
|
||||||
- else
|
|
||||||
= _('Clear this checkbox to use a personal access token instead.')
|
|
||||||
- if omniauth_enabled? && button_based_providers.any?
|
- if omniauth_enabled? && button_based_providers.any?
|
||||||
%fieldset.form-group
|
%fieldset.form-group
|
||||||
%legend.gl-font-base.gl-mb-3.gl-border-none.gl-font-weight-bold= _('Enabled OAuth authentication sources')
|
%legend.gl-font-base.gl-mb-3.gl-border-none.gl-font-weight-bold= _('Enabled OAuth authentication sources')
|
||||||
|
@ -27,13 +18,11 @@
|
||||||
= source
|
= source
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :two_factor_authentication, _('Two-factor authentication'), class: 'label-bold'
|
= f.label :two_factor_authentication, _('Two-factor authentication'), class: 'label-bold'
|
||||||
.form-check
|
- help_text = _('Enforce two-factor authentication for all user sign-ins.')
|
||||||
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
|
- help_link = link_to _('Learn more.'), help_page_path('security/two_factor_authentication.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
= f.label :require_two_factor_authentication, class: 'form-check-label' do
|
= f.gitlab_ui_checkbox_component :require_two_factor_authentication,
|
||||||
= _('Enforce two-factor authentication')
|
_('Enforce two-factor authentication'),
|
||||||
%p.form-text.text-muted
|
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||||
= _('Enforce two-factor authentication for all user sign-ins.')
|
|
||||||
= link_to _('Learn more.'), help_page_path('security/two_factor_authentication.md'), target: '_blank', rel: 'noopener noreferrer'
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :two_factor_authentication, _('Two-factor grace period'), class: 'label-bold'
|
= f.label :two_factor_authentication, _('Two-factor grace period'), class: 'label-bold'
|
||||||
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control gl-form-input', placeholder: '0'
|
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control gl-form-input', placeholder: '0'
|
||||||
|
@ -42,22 +31,18 @@
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :admin_mode, _('Admin Mode'), class: 'label-bold'
|
= f.label :admin_mode, _('Admin Mode'), class: 'label-bold'
|
||||||
= sprite_icon('lock', css_class: 'gl-icon')
|
= sprite_icon('lock', css_class: 'gl-icon')
|
||||||
.form-check
|
- help_text = _('Require additional authentication for administrative tasks.')
|
||||||
= f.check_box :admin_mode, class: 'form-check-input'
|
- help_link = link_to _('Learn more.'), help_page_path('user/admin_area/settings/sign_in_restrictions', anchor: 'admin-mode'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
= f.label :admin_mode, class: 'form-check-label' do
|
= f.gitlab_ui_checkbox_component :admin_mode,
|
||||||
= _('Enable admin mode')
|
_('Enable admin mode'),
|
||||||
%p.form-text.text-muted
|
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||||
= _('Require additional authentication for administrative tasks.')
|
|
||||||
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/sign_in_restrictions', anchor: 'admin-mode'), target: '_blank', rel: 'noopener noreferrer'
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold'
|
= f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold'
|
||||||
.form-check
|
- help_text = _('Notify users by email when sign-in location is not recognized.')
|
||||||
= f.check_box :notify_on_unknown_sign_in, class: 'form-check-input'
|
- help_link = link_to _('Learn more.'), help_page_path('user/profile/unknown_sign_in_notification.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||||
= f.label :notify_on_unknown_sign_in, class: 'form-check-label' do
|
= f.gitlab_ui_checkbox_component :notify_on_unknown_sign_in,
|
||||||
= _('Enable email notification')
|
_('Enable email notification'),
|
||||||
%p.form-text.text-muted
|
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||||
= _('Notify users by email when sign-in location is not recognized.')
|
|
||||||
= link_to _('Learn more.'), help_page_path('user/profile/unknown_sign_in_notification.md'), target: '_blank', rel: 'noopener noreferrer'
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :home_page_url, _('Home page URL'), class: 'label-bold'
|
= f.label :home_page_url, _('Home page URL'), class: 'label-bold'
|
||||||
= f.text_field :home_page_url, class: 'form-control gl-form-input', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
|
= f.text_field :home_page_url, class: 'form-control gl-form-input', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
|
||||||
|
|
|
@ -39,10 +39,9 @@
|
||||||
.label-container
|
.label-container
|
||||||
- if generic_commit_status.tags.any?
|
- if generic_commit_status.tags.any?
|
||||||
- generic_commit_status.tags.each do |tag|
|
- generic_commit_status.tags.each do |tag|
|
||||||
%span.badge.badge-primary
|
= gl_badge_tag tag, variant: :info, size: :sm
|
||||||
= tag
|
|
||||||
- if retried
|
- if retried
|
||||||
%span.badge.badge-warning retried
|
= gl_badge_tag retried, variant: :warning, size: :sm
|
||||||
|
|
||||||
- if pipeline_link
|
- if pipeline_link
|
||||||
%td
|
%td
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
description: Run Pipeline
|
||||||
|
category: Gitlab::UsageDataCounters::CiTemplateUniqueCounter
|
||||||
|
action: ci_templates_unique
|
||||||
|
label_description:
|
||||||
|
property_description:
|
||||||
|
value_description:
|
||||||
|
extra_properties:
|
||||||
|
identifiers:
|
||||||
|
product_section: ops
|
||||||
|
product_stage: configure
|
||||||
|
product_group: group::configure
|
||||||
|
product_category: infrastructure_as_code
|
||||||
|
milestone: "15.0"
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84337
|
||||||
|
distributions:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tiers:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
description: Perform Git operation (read/write/push)
|
||||||
|
category: EventCreateService
|
||||||
|
action: action_active_users_project_repo
|
||||||
|
label_description:
|
||||||
|
property_description:
|
||||||
|
value_description:
|
||||||
|
extra_properties:
|
||||||
|
identifiers:
|
||||||
|
product_section: dev
|
||||||
|
product_stage: create
|
||||||
|
product_group: group::source code
|
||||||
|
product_category: source_code_management
|
||||||
|
milestone: "15.0"
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83795
|
||||||
|
distributions:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tiers:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
|
|
@ -11579,6 +11579,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
||||||
| <a id="groupissuesconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
| <a id="groupissuesconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
||||||
| <a id="groupissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
| <a id="groupissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
||||||
| <a id="groupissuescreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
| <a id="groupissuescreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
||||||
|
| <a id="groupissuescrmcontactid"></a>`crmContactId` | [`String`](#string) | ID of a contact assigned to the issues. |
|
||||||
|
| <a id="groupissuescrmorganizationid"></a>`crmOrganizationId` | [`String`](#string) | ID of an organization assigned to the issues. |
|
||||||
| <a id="groupissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
| <a id="groupissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
||||||
| <a id="groupissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
| <a id="groupissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
||||||
| <a id="groupissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
| <a id="groupissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
||||||
|
@ -14859,6 +14861,8 @@ Returns [`Issue`](#issue).
|
||||||
| <a id="projectissueconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
| <a id="projectissueconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
||||||
| <a id="projectissuecreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
| <a id="projectissuecreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
||||||
| <a id="projectissuecreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
| <a id="projectissuecreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
||||||
|
| <a id="projectissuecrmcontactid"></a>`crmContactId` | [`String`](#string) | ID of a contact assigned to the issues. |
|
||||||
|
| <a id="projectissuecrmorganizationid"></a>`crmOrganizationId` | [`String`](#string) | ID of an organization assigned to the issues. |
|
||||||
| <a id="projectissueepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
| <a id="projectissueepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
||||||
| <a id="projectissueiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
| <a id="projectissueiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
||||||
| <a id="projectissueiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
| <a id="projectissueiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
||||||
|
@ -14899,6 +14903,8 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype).
|
||||||
| <a id="projectissuestatuscountsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
| <a id="projectissuestatuscountsconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
||||||
| <a id="projectissuestatuscountscreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
| <a id="projectissuestatuscountscreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
||||||
| <a id="projectissuestatuscountscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
| <a id="projectissuestatuscountscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
||||||
|
| <a id="projectissuestatuscountscrmcontactid"></a>`crmContactId` | [`String`](#string) | ID of a contact assigned to the issues. |
|
||||||
|
| <a id="projectissuestatuscountscrmorganizationid"></a>`crmOrganizationId` | [`String`](#string) | ID of an organization assigned to the issues. |
|
||||||
| <a id="projectissuestatuscountsiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
| <a id="projectissuestatuscountsiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
||||||
| <a id="projectissuestatuscountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
| <a id="projectissuestatuscountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
||||||
| <a id="projectissuestatuscountslabelname"></a>`labelName` | [`[String]`](#string) | Labels applied to this issue. |
|
| <a id="projectissuestatuscountslabelname"></a>`labelName` | [`[String]`](#string) | Labels applied to this issue. |
|
||||||
|
@ -14936,6 +14942,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
||||||
| <a id="projectissuesconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
| <a id="projectissuesconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues. |
|
||||||
| <a id="projectissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
| <a id="projectissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after this date. |
|
||||||
| <a id="projectissuescreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
| <a id="projectissuescreatedbefore"></a>`createdBefore` | [`Time`](#time) | Issues created before this date. |
|
||||||
|
| <a id="projectissuescrmcontactid"></a>`crmContactId` | [`String`](#string) | ID of a contact assigned to the issues. |
|
||||||
|
| <a id="projectissuescrmorganizationid"></a>`crmOrganizationId` | [`String`](#string) | ID of an organization assigned to the issues. |
|
||||||
| <a id="projectissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
| <a id="projectissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
|
||||||
| <a id="projectissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
| <a id="projectissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
||||||
| <a id="projectissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
| <a id="projectissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
|
||||||
|
|
|
@ -190,40 +190,43 @@ data to be in the new format.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
The table `integrations` has a field called `properties`, stored in JSON. For all rows,
|
The `routes` table has a `source_type` field that's used for a polymorphic relationship.
|
||||||
extract the `url` key from this JSON object and store it in the `integrations.url`
|
As part of a database redesign, we're removing the polymorphic relationship. One step of
|
||||||
column. Millions of integrations exist, and parsing JSON is slow, so you can't
|
the work will be migrating data from the `source_id` column into a new singular foreign key.
|
||||||
do this work in a regular migration.
|
Because we intend to delete old rows later, there's no need to update them as part of the
|
||||||
|
background migration.
|
||||||
|
|
||||||
1. Start by defining our migration class:
|
1. Start by defining our migration class, which should inherit
|
||||||
|
from `Gitlab::BackgroundMigration::BatchedMigrationJob`:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
|
class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
|
||||||
class Integration < ::ApplicationRecord
|
# For illustration purposes, if we were to use a local model we could
|
||||||
self.table_name = 'integrations'
|
# define it like below, using an `ApplicationRecord` as the base class
|
||||||
end
|
# class Route < ::ApplicationRecord
|
||||||
|
# self.table_name = 'routes'
|
||||||
|
# end
|
||||||
|
|
||||||
def perform(start_id, end_id)
|
def perform
|
||||||
Integration.where(id: start_id..end_id).each do |integration|
|
each_sub_batch(
|
||||||
json = JSON.load(integration.properties)
|
operation_name: :update_all,
|
||||||
|
batching_scope: -> (relation) { relation.where("source_type <> 'UnusedType'") }
|
||||||
integration.update(url: json['url']) if json['url']
|
) do |sub_batch|
|
||||||
rescue JSON::ParserError
|
sub_batch.update_all('namespace_id = source_id')
|
||||||
# If the JSON is invalid we don't want to keep the job around forever,
|
|
||||||
# instead we'll just leave the "url" field to whatever the default value
|
|
||||||
# is.
|
|
||||||
next
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
To get a `connection` in the batched background migration,use an inheritance
|
Job classes must be subclasses of `BatchedMigrationJob` to be
|
||||||
relation using the following base class `Gitlab::BackgroundMigration::BaseJob`.
|
correctly handled by the batched migration framework. Any subclass of
|
||||||
For example: `class Gitlab::BackgroundMigration::ExtractIntegrationsUrl < Gitlab::BackgroundMigration::BaseJob`
|
`BatchedMigrationJob` will be initialized with necessary arguments to
|
||||||
|
execute the batch, as well as a connection to the tracking database.
|
||||||
|
Additional `job_arguments` set on the migration will be passed to the
|
||||||
|
job's `perform` method.
|
||||||
|
|
||||||
1. Add a new trigger to the database to update newly created and updated integrations,
|
1. Add a new trigger to the database to update newly created and updated routes,
|
||||||
similar to this example:
|
similar to this example:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
|
@ -232,7 +235,7 @@ do this work in a regular migration.
|
||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
NEW."url" := NEW.properties -> "url"
|
NEW."namespace_id" = NEW."source_id"
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
@ -242,16 +245,16 @@ do this work in a regular migration.
|
||||||
1. Create a post-deployment migration that queues the migration for existing data:
|
1. Create a post-deployment migration that queues the migration for existing data:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class QueueExtractIntegrationsUrl < Gitlab::Database::Migration[1.0]
|
class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[1.0]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
MIGRATION = 'ExtractIntegrationsUrl'
|
MIGRATION = 'BackfillRouteNamespaceId'
|
||||||
DELAY_INTERVAL = 2.minutes
|
DELAY_INTERVAL = 2.minutes
|
||||||
|
|
||||||
def up
|
def up
|
||||||
queue_batched_background_migration(
|
queue_batched_background_migration(
|
||||||
MIGRATION,
|
MIGRATION,
|
||||||
:integrations,
|
:routes,
|
||||||
:id,
|
:id,
|
||||||
job_interval: DELAY_INTERVAL
|
job_interval: DELAY_INTERVAL
|
||||||
)
|
)
|
||||||
|
@ -259,7 +262,7 @@ do this work in a regular migration.
|
||||||
|
|
||||||
def down
|
def down
|
||||||
Gitlab::Database::BackgroundMigration::BatchedMigration
|
Gitlab::Database::BackgroundMigration::BatchedMigration
|
||||||
.for_configuration(MIGRATION, :integrations, :id, []).delete_all
|
.for_configuration(MIGRATION, :routes, :id, []).delete_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
@ -272,14 +275,14 @@ do this work in a regular migration.
|
||||||
that checks that the batched background migration is completed. For example:
|
that checks that the batched background migration is completed. For example:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
class FinalizeExtractIntegrationsUrlJobs < Gitlab::Database::Migration[1.0]
|
class FinalizeBackfillRouteNamespaceId < Gitlab::Database::Migration[1.0]
|
||||||
MIGRATION = 'ExtractIntegrationsUrl'
|
MIGRATION = 'BackfillRouteNamespaceId'
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
ensure_batched_background_migration_is_finished(
|
ensure_batched_background_migration_is_finished(
|
||||||
job_class_name: MIGRATION,
|
job_class_name: MIGRATION,
|
||||||
table_name: :integrations,
|
table_name: :routes,
|
||||||
column_name: :id,
|
column_name: :id,
|
||||||
job_arguments: []
|
job_arguments: []
|
||||||
)
|
)
|
||||||
|
@ -295,7 +298,8 @@ do this work in a regular migration.
|
||||||
instance, the data is advisory, and not mission-critical), then you can skip this
|
instance, the data is advisory, and not mission-critical), then you can skip this
|
||||||
final step. This step confirms that the migration is completed, and all of the rows were migrated.
|
final step. This step confirms that the migration is completed, and all of the rows were migrated.
|
||||||
|
|
||||||
After the batched migration is completed, you can safely remove the `integrations.properties` column.
|
After the batched migration is completed, you can safely depend on the
|
||||||
|
data in `routes.namespace_id` being populated.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,11 @@ Fill in the following form to contact us and learn more about this offering.
|
||||||
|
|
||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
|
|
||||||
|
<!-- NOTE: The following form only shows when the site is served under HTTPS,
|
||||||
|
so it will not appear when developing locally or in a review app.
|
||||||
|
See https://gitlab.com/gitlab-com/marketing/marketing-operations/-/issues/6238#note_923358643
|
||||||
|
-->
|
||||||
|
|
||||||
<script src="//page.gitlab.com/js/forms2/js/forms2.min.js"></script>
|
<script src="//page.gitlab.com/js/forms2/js/forms2.min.js"></script>
|
||||||
<form id="mktoForm_3226"></form>
|
<form id="mktoForm_3226"></form>
|
||||||
<script>MktoForms2.loadForm("//page.gitlab.com", "194-VVC-221", 3226);</script>
|
<script>MktoForms2.loadForm("//page.gitlab.com", "194-VVC-221", 3226);</script>
|
||||||
|
|
|
@ -53,10 +53,8 @@ A URL to the reported user's comment is pre-filled in the abuse report's
|
||||||
|
|
||||||
## Report abuse from a merge request
|
## Report abuse from a merge request
|
||||||
|
|
||||||
1. On the merge request, in the top right corner, either:
|
1. On the merge request, in the top right corner, select the vertical ellipsis (**{ellipsis_v}**).
|
||||||
- Select **Report abuse**. This option is displayed if you do not have permission to close the merge request.
|
1. Select **Report abuse**.
|
||||||
- Next to **Mark as draft**, select the down arrow (**{chevron-down}**) and then select **Report abuse**.
|
|
||||||
This option is displayed if you have permission to close the merge request.
|
|
||||||
1. Submit an abuse report.
|
1. Submit an abuse report.
|
||||||
1. Select **Send report**.
|
1. Select **Send report**.
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
module Bitbucket
|
module Bitbucket
|
||||||
module Representation
|
module Representation
|
||||||
class Repo < Representation::Base
|
class Repo < Representation::Base
|
||||||
def initialize(raw)
|
|
||||||
super(raw)
|
|
||||||
end
|
|
||||||
|
|
||||||
def owner_and_slug
|
def owner_and_slug
|
||||||
@owner_and_slug ||= full_name.split('/', 2)
|
@owner_and_slug ||= full_name.split('/', 2)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
module BitbucketServer
|
module BitbucketServer
|
||||||
module Representation
|
module Representation
|
||||||
class Repo < Representation::Base
|
class Repo < Representation::Base
|
||||||
def initialize(raw)
|
|
||||||
super(raw)
|
|
||||||
end
|
|
||||||
|
|
||||||
def project_key
|
def project_key
|
||||||
raw.dig('project', 'key')
|
raw.dig('project', 'key')
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,3 +50,12 @@ Style/FrozenStringLiteralComment:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Details: >-
|
Details: >-
|
||||||
This removes the need for calling "freeze", reducing noise in the code.
|
This removes the need for calling "freeze", reducing noise in the code.
|
||||||
|
|
||||||
|
Migration/BackgroundMigrationBaseClass:
|
||||||
|
Enabled: true
|
||||||
|
Exclude:
|
||||||
|
- 'batching_strategies/**/*.rb'
|
||||||
|
- 'job_coordinator.rb'
|
||||||
|
- 'base_job.rb'
|
||||||
|
- 'batched_migration_job.rb'
|
||||||
|
- 'logger.rb'
|
||||||
|
|
|
@ -8,10 +8,6 @@ module Gitlab
|
||||||
class String < Lexeme::Value
|
class String < Lexeme::Value
|
||||||
PATTERN = /("(?<string>.*?)")|('(?<string>.*?)')/.freeze
|
PATTERN = /("(?<string>.*?)")|('(?<string>.*?)')/.freeze
|
||||||
|
|
||||||
def initialize(value)
|
|
||||||
super(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def evaluate(variables = {})
|
def evaluate(variables = {})
|
||||||
@value.to_s
|
@value.to_s
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,10 +7,6 @@ module Gitlab
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
include Entry::Validators
|
include Entry::Validators
|
||||||
|
|
||||||
def initialize(entry)
|
|
||||||
super(entry)
|
|
||||||
end
|
|
||||||
|
|
||||||
def messages
|
def messages
|
||||||
errors.full_messages.map do |error|
|
errors.full_messages.map do |error|
|
||||||
"#{location} #{error}".downcase
|
"#{location} #{error}".downcase
|
||||||
|
|
|
@ -13,10 +13,6 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(repository, name, target, target_commit)
|
|
||||||
super(repository, name, target, target_commit)
|
|
||||||
end
|
|
||||||
|
|
||||||
def active?
|
def active?
|
||||||
self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago
|
self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,10 +10,6 @@ module Gitlab
|
||||||
@expires_in = expires_in
|
@expires_in = expires_in
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_key(key)
|
|
||||||
super(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear_cache!(key)
|
def clear_cache!(key)
|
||||||
with do |redis|
|
with do |redis|
|
||||||
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
|
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
|
||||||
|
|
|
@ -4,10 +4,6 @@ module Gitlab
|
||||||
class SnippetSearchResults < SearchResults
|
class SnippetSearchResults < SearchResults
|
||||||
include SnippetsHelper
|
include SnippetsHelper
|
||||||
|
|
||||||
def initialize(current_user, query)
|
|
||||||
super(current_user, query)
|
|
||||||
end
|
|
||||||
|
|
||||||
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, preload_method: nil)
|
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, preload_method: nil)
|
||||||
paginated_objects(snippet_titles, page, per_page)
|
paginated_objects(snippet_titles, page, per_page)
|
||||||
end
|
end
|
||||||
|
|
|
@ -40835,6 +40835,9 @@ msgstr ""
|
||||||
msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
|
msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "UsageQuota|%{linkStart}Shared runners%{linkEnd} are disabled, so there are no limits set on pipeline usage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "UsageQuota|%{linkTitle} help link"
|
msgid "UsageQuota|%{linkTitle} help link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -40934,6 +40937,9 @@ msgstr ""
|
||||||
msgid "UsageQuota|Snippets"
|
msgid "UsageQuota|Snippets"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "UsageQuota|Something went wrong while fetching pipeline statistics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "UsageQuota|Something went wrong while fetching project storage statistics"
|
msgid "UsageQuota|Something went wrong while fetching project storage statistics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -41663,6 +41669,9 @@ msgstr ""
|
||||||
msgid "ValueStreamAnalytics|Average number of deployments to production per day."
|
msgid "ValueStreamAnalytics|Average number of deployments to production per day."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ValueStreamAnalytics|DORA metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ValueStreamAnalytics|Dashboard"
|
msgid "ValueStreamAnalytics|Dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -41672,6 +41681,9 @@ msgstr ""
|
||||||
msgid "ValueStreamAnalytics|Items in Value Stream Analytics are currently filtered by their creation time. There is an %{epic_link_start}epic%{epic_link_end} that will change the Value Stream Analytics date filter to use the end event time for the selected stage."
|
msgid "ValueStreamAnalytics|Items in Value Stream Analytics are currently filtered by their creation time. There is an %{epic_link_start}epic%{epic_link_end} that will change the Value Stream Analytics date filter to use the end event time for the selected stage."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ValueStreamAnalytics|Key metrics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period."
|
msgid "ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,6 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate_via_api!
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
def api_get_path
|
||||||
"/groups/#{group.id}/deploy_tokens"
|
"/groups/#{group.id}/deploy_tokens"
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,10 +15,6 @@ module QA
|
||||||
Page::Project::Settings::AccessTokens.perform(&:created_access_token)
|
Page::Project::Settings::AccessTokens.perform(&:created_access_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate_via_api!
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
def api_get_path
|
||||||
"/projects/#{project.api_resource[:id]}/access_tokens"
|
"/projects/#{project.api_resource[:id]}/access_tokens"
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,10 +17,6 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fabricate_via_api!
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def api_get_path
|
def api_get_path
|
||||||
"/projects/#{project.id}/deploy_tokens"
|
"/projects/#{project.id}/deploy_tokens"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Migration
|
||||||
|
class BackgroundMigrationBaseClass < RuboCop::Cop::Cop
|
||||||
|
MSG = 'Batched background migration jobs should inherit from Gitlab::BackgroundMigration::BatchedMigrationJob'
|
||||||
|
|
||||||
|
def_node_search :top_level_module?, <<~PATTERN
|
||||||
|
(module (const nil? :Gitlab) (module (const nil? :BackgroundMigration) ...))
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def_node_matcher :matching_parent_namespace?, <<~PATTERN
|
||||||
|
{nil? (const (const {cbase nil?} :Gitlab) :BackgroundMigration)}
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def_node_search :inherits_batched_migration_job?, <<~PATTERN
|
||||||
|
(class _ (const #matching_parent_namespace? :BatchedMigrationJob) ...)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_module(module_node)
|
||||||
|
return unless top_level_module?(module_node)
|
||||||
|
|
||||||
|
top_level_class_node = module_node.each_descendant(:class).first
|
||||||
|
|
||||||
|
return if top_level_class_node.nil? || inherits_batched_migration_job?(top_level_class_node)
|
||||||
|
|
||||||
|
add_offense(top_level_class_node, location: :expression)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,11 @@
|
||||||
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
|
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
|
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
|
||||||
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
||||||
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
|
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
|
||||||
import { METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
|
import { VSA_METRICS_GROUPS, METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
|
||||||
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
|
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
|
||||||
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
|
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
|
@ -27,7 +27,7 @@ describe('ValueStreamMetrics', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const createComponent = (props = {}) => {
|
const createComponent = (props = {}) => {
|
||||||
return shallowMount(ValueStreamMetrics, {
|
return shallowMountExtended(ValueStreamMetrics, {
|
||||||
propsData: {
|
propsData: {
|
||||||
requestPath,
|
requestPath,
|
||||||
requestParams: {},
|
requestParams: {},
|
||||||
|
@ -38,6 +38,7 @@ describe('ValueStreamMetrics', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const findMetrics = () => wrapper.findAllComponents(MetricTile);
|
const findMetrics = () => wrapper.findAllComponents(MetricTile);
|
||||||
|
const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group');
|
||||||
|
|
||||||
const expectToHaveRequest = (fields) => {
|
const expectToHaveRequest = (fields) => {
|
||||||
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
|
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
|
||||||
|
@ -63,24 +64,6 @@ describe('ValueStreamMetrics', () => {
|
||||||
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
|
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders hidden MetricTile components for each metric', async () => {
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
wrapper.setData({ isLoading: true });
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const components = findMetrics();
|
|
||||||
|
|
||||||
expect(components).toHaveLength(metricsData.length);
|
|
||||||
|
|
||||||
metricsData.forEach((metric, index) => {
|
|
||||||
expect(components.at(index).isVisible()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with data loaded', () => {
|
describe('with data loaded', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
@ -160,6 +143,27 @@ describe('ValueStreamMetrics', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('groupBy', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
|
||||||
|
wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS });
|
||||||
|
await waitForPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the metrics as separate groups', () => {
|
||||||
|
const groups = findMetricsGroups();
|
||||||
|
expect(groups).toHaveLength(VSA_METRICS_GROUPS.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders titles for each group', () => {
|
||||||
|
const groups = findMetricsGroups();
|
||||||
|
groups.wrappers.forEach((g, index) => {
|
||||||
|
const { title } = VSA_METRICS_GROUPS[index];
|
||||||
|
expect(g.html()).toContain(title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueApollo from 'vue-apollo';
|
import VueApollo from 'vue-apollo';
|
||||||
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
import { GlButton, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||||
|
@ -37,14 +37,15 @@ describe('Linked pipeline', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const findButton = () => wrapper.find(GlButton);
|
const findButton = () => wrapper.find(GlButton);
|
||||||
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
|
|
||||||
const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline');
|
const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline');
|
||||||
|
const findCardTooltip = () => wrapper.findComponent(GlTooltip);
|
||||||
const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title');
|
const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title');
|
||||||
const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
|
const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button');
|
||||||
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
|
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
|
||||||
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||||
|
const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
|
||||||
const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
|
const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
|
||||||
const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button');
|
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
|
||||||
|
|
||||||
const createWrapper = ({ propsData, downstreamRetryAction = false }) => {
|
const createWrapper = ({ propsData, downstreamRetryAction = false }) => {
|
||||||
const mockApollo = createMockApollo();
|
const mockApollo = createMockApollo();
|
||||||
|
@ -101,18 +102,13 @@ describe('Linked pipeline', () => {
|
||||||
expect(wrapper.text()).toContain(`#${props.pipeline.id}`);
|
expect(wrapper.text()).toContain(`#${props.pipeline.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly compute the tooltip text', () => {
|
it('adds the card tooltip text to the DOM', () => {
|
||||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
|
expect(findCardTooltip().exists()).toBe(true);
|
||||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.status.label);
|
|
||||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.sourceJob.name);
|
|
||||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the tooltip text as the title attribute', () => {
|
expect(findCardTooltip().text()).toContain(mockPipeline.project.name);
|
||||||
const titleAttr = findLinkedPipeline().attributes('title');
|
expect(findCardTooltip().text()).toContain(mockPipeline.status.label);
|
||||||
|
expect(findCardTooltip().text()).toContain(mockPipeline.sourceJob.name);
|
||||||
expect(titleAttr).toContain(mockPipeline.project.name);
|
expect(findCardTooltip().text()).toContain(mockPipeline.id);
|
||||||
expect(titleAttr).toContain(mockPipeline.status.label);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
|
it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
|
||||||
|
@ -204,6 +200,14 @@ describe('Linked pipeline', () => {
|
||||||
expect(findRetryButton().exists()).toBe(true);
|
expect(findRetryButton().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('hides the card tooltip when the action button tooltip is hovered', async () => {
|
||||||
|
expect(findCardTooltip().exists()).toBe(true);
|
||||||
|
|
||||||
|
await findRetryButton().trigger('mouseover');
|
||||||
|
|
||||||
|
expect(findCardTooltip().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
describe('and the retry button is clicked', () => {
|
describe('and the retry button is clicked', () => {
|
||||||
describe('on success', () => {
|
describe('on success', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -258,6 +262,14 @@ describe('Linked pipeline', () => {
|
||||||
expect(findRetryButton().exists()).toBe(false);
|
expect(findRetryButton().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('hides the card tooltip when the action button tooltip is hovered', async () => {
|
||||||
|
expect(findCardTooltip().exists()).toBe(true);
|
||||||
|
|
||||||
|
await findCancelButton().trigger('mouseover');
|
||||||
|
|
||||||
|
expect(findCardTooltip().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
describe('and the cancel button is clicked', () => {
|
describe('and the cancel button is clicked', () => {
|
||||||
describe('on success', () => {
|
describe('on success', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
|
@ -68,6 +68,7 @@ describe('Attention require toggle', () => {
|
||||||
{
|
{
|
||||||
user: { attention_requested: true, can_update_merge_request: true },
|
user: { attention_requested: true, can_update_merge_request: true },
|
||||||
callback: expect.anything(),
|
callback: expect.anything(),
|
||||||
|
direction: 'remove',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import createFlash from '~/flash';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import * as urlUtility from '~/lib/utils/url_utility';
|
import * as urlUtility from '~/lib/utils/url_utility';
|
||||||
import SidebarService, { gqClient } from '~/sidebar/services/sidebar_service';
|
import SidebarService, { gqClient } from '~/sidebar/services/sidebar_service';
|
||||||
|
@ -8,6 +9,7 @@ import toast from '~/vue_shared/plugins/global_toast';
|
||||||
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
||||||
import Mock from './mock_data';
|
import Mock from './mock_data';
|
||||||
|
|
||||||
|
jest.mock('~/flash');
|
||||||
jest.mock('~/vue_shared/plugins/global_toast');
|
jest.mock('~/vue_shared/plugins/global_toast');
|
||||||
jest.mock('~/commons/nav/user_merge_requests');
|
jest.mock('~/commons/nav/user_merge_requests');
|
||||||
|
|
||||||
|
@ -122,25 +124,39 @@ describe('Sidebar mediator', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toggleAttentionRequested', () => {
|
describe('toggleAttentionRequested', () => {
|
||||||
let attentionRequiredService;
|
let requestAttentionMock;
|
||||||
|
let removeAttentionRequestMock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
attentionRequiredService = jest
|
requestAttentionMock = jest.spyOn(mediator.service, 'requestAttention').mockResolvedValue();
|
||||||
.spyOn(mediator.service, 'toggleAttentionRequested')
|
removeAttentionRequestMock = jest
|
||||||
|
.spyOn(mediator.service, 'removeAttentionRequest')
|
||||||
.mockResolvedValue();
|
.mockResolvedValue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls attentionRequired service method', async () => {
|
it.each`
|
||||||
mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }];
|
attentionIsCurrentlyRequested | serviceMethod
|
||||||
|
${true} | ${'remove'}
|
||||||
|
${false} | ${'add'}
|
||||||
|
`(
|
||||||
|
"calls the $serviceMethod service method when the user's attention request is set to $attentionIsCurrentlyRequested",
|
||||||
|
async ({ serviceMethod }) => {
|
||||||
|
const methods = {
|
||||||
|
add: requestAttentionMock,
|
||||||
|
remove: removeAttentionRequestMock,
|
||||||
|
};
|
||||||
|
mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }];
|
||||||
|
|
||||||
await mediator.toggleAttentionRequested('reviewer', {
|
await mediator.toggleAttentionRequested('reviewer', {
|
||||||
user: { id: 1, username: 'root' },
|
user: { id: 1, username: 'root' },
|
||||||
callback: jest.fn(),
|
callback: jest.fn(),
|
||||||
});
|
direction: serviceMethod,
|
||||||
|
});
|
||||||
|
|
||||||
expect(attentionRequiredService).toHaveBeenCalledWith(1);
|
expect(methods[serviceMethod]).toHaveBeenCalledWith(1);
|
||||||
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
|
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it.each`
|
it.each`
|
||||||
type | method
|
type | method
|
||||||
|
@ -172,5 +188,27 @@ describe('Sidebar mediator', () => {
|
||||||
expect(toast).toHaveBeenCalledWith(toastMessage);
|
expect(toast).toHaveBeenCalledWith(toastMessage);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest
|
||||||
|
.spyOn(mediator.service, 'removeAttentionRequest')
|
||||||
|
.mockRejectedValueOnce(new Error('Something went wrong'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error message', async () => {
|
||||||
|
await mediator.toggleAttentionRequested('reviewer', {
|
||||||
|
user: { id: 1, username: 'root' },
|
||||||
|
callback: jest.fn(),
|
||||||
|
direction: 'remove',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createFlash).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Updating the attention request for root failed.',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -389,6 +389,34 @@ RSpec.describe Resolvers::IssuesResolver do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'filtering by crm' do
|
||||||
|
let_it_be(:organization) { create(:organization, group: group) }
|
||||||
|
let_it_be(:contact1) { create(:contact, group: group, organization: organization) }
|
||||||
|
let_it_be(:contact2) { create(:contact, group: group, organization: organization) }
|
||||||
|
let_it_be(:contact3) { create(:contact, group: group) }
|
||||||
|
let_it_be(:crm_issue1) { create(:issue, project: project) }
|
||||||
|
let_it_be(:crm_issue2) { create(:issue, project: project) }
|
||||||
|
let_it_be(:crm_issue3) { create(:issue, project: project) }
|
||||||
|
|
||||||
|
before_all do
|
||||||
|
create(:issue_customer_relations_contact, issue: crm_issue1, contact: contact1)
|
||||||
|
create(:issue_customer_relations_contact, issue: crm_issue2, contact: contact2)
|
||||||
|
create(:issue_customer_relations_contact, issue: crm_issue3, contact: contact3)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'contact' do
|
||||||
|
it 'returns only the issues for the contact' do
|
||||||
|
expect(resolve_issues({ crm_contact_id: contact1.id })).to contain_exactly(crm_issue1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'organization' do
|
||||||
|
it 'returns only the issues for the contact' do
|
||||||
|
expect(resolve_issues({ crm_organization_id: organization.id })).to contain_exactly(crm_issue1, crm_issue2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'sorting' do
|
describe 'sorting' do
|
||||||
context 'when sorting by created' do
|
context 'when sorting by created' do
|
||||||
it 'sorts issues ascending' do
|
it 'sorts issues ascending' do
|
||||||
|
|
|
@ -275,6 +275,7 @@ RSpec.describe NamespacesHelper do
|
||||||
namespace_actual_plan_name: user_group.actual_plan_name,
|
namespace_actual_plan_name: user_group.actual_plan_name,
|
||||||
namespace_path: user_group.full_path,
|
namespace_path: user_group.full_path,
|
||||||
namespace_id: user_group.id,
|
namespace_id: user_group.id,
|
||||||
|
user_namespace: user_group.user_namespace?.to_s,
|
||||||
page_size: Kaminari.config.default_per_page
|
page_size: Kaminari.config.default_per_page
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
require_relative '../../../../rubocop/cop/migration/background_migration_base_class'
|
||||||
|
|
||||||
|
RSpec.describe RuboCop::Cop::Migration::BackgroundMigrationBaseClass do
|
||||||
|
subject(:cop) { described_class.new }
|
||||||
|
|
||||||
|
context 'when the migration class inherits from BatchedMigrationJob' do
|
||||||
|
it 'does not register any offenses' do
|
||||||
|
expect_no_offenses(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob < BatchedMigrationJob
|
||||||
|
def perform
|
||||||
|
connection.execute("select 1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the migration class inherits from the namespaced BatchedMigrationJob' do
|
||||||
|
it 'does not register any offenses' do
|
||||||
|
expect_no_offenses(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob < Gitlab::BackgroundMigration::BatchedMigrationJob
|
||||||
|
def perform
|
||||||
|
connection.execute("select 1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the migration class inherits from the top-level namespaced BatchedMigrationJob' do
|
||||||
|
it 'does not register any offenses' do
|
||||||
|
expect_no_offenses(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob < ::Gitlab::BackgroundMigration::BatchedMigrationJob
|
||||||
|
def perform
|
||||||
|
connection.execute("select 1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a nested class is used inside the job class' do
|
||||||
|
it 'does not register any offenses' do
|
||||||
|
expect_no_offenses(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob < BatchedMigrationJob
|
||||||
|
class Project < ApplicationRecord
|
||||||
|
self.table_name = 'projects'
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
Project.update!(name: 'hi')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the migration class inherits from another class' do
|
||||||
|
it 'registers an offense' do
|
||||||
|
expect_offense(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob < SomeOtherClass
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the migration class does not inherit from anything' do
|
||||||
|
it 'registers an offense' do
|
||||||
|
expect_offense(<<~RUBY)
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class MyJob
|
||||||
|
^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue