Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
48640cf76a
commit
8ed0a009f0
|
@ -747,3 +747,6 @@ Performance/ActiveRecordSubtransactionMethods:
|
|||
Exclude:
|
||||
- 'spec/**/*.rb'
|
||||
- 'ee/spec/**/*.rb'
|
||||
|
||||
Migration/BackgroundMigrationBaseClass:
|
||||
Enabled: false
|
||||
|
|
|
@ -11,12 +11,6 @@ Gitlab/PolicyRuleBoolean:
|
|||
Exclude:
|
||||
- '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
|
||||
# Cop supports --auto-correct.
|
||||
# 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 {
|
||||
name: 'ValueStreamMetrics',
|
||||
components: {
|
||||
|
@ -52,13 +68,24 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
groupBy: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
metrics: [],
|
||||
groupedMetrics: [],
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasGroupedMetrics() {
|
||||
return Boolean(this.groupBy.length);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
requestParams(newVal, oldVal) {
|
||||
if (!isEqual(newVal, oldVal)) {
|
||||
|
@ -76,6 +103,11 @@ export default {
|
|||
return fetchMetricsData(this.requests, this.requestPath, this.requestParams)
|
||||
.then((data) => {
|
||||
this.metrics = this.filterFn ? this.filterFn(data) : data;
|
||||
|
||||
if (this.hasGroupedMetrics) {
|
||||
this.groupedMetrics = groupRawMetrics(this.groupBy, this.metrics);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -86,14 +118,35 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<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" />
|
||||
<metric-tile
|
||||
v-for="metric in metrics"
|
||||
v-show="!isLoading"
|
||||
:key="metric.identifier"
|
||||
:metric="metric"
|
||||
class="gl-my-6 gl-pr-9"
|
||||
/>
|
||||
<template v-else>
|
||||
<div v-if="hasGroupedMetrics" class="gl-flex-direction-column">
|
||||
<div
|
||||
v-for="group in groupedMetrics"
|
||||
:key="group.key"
|
||||
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>
|
||||
</template>
|
||||
|
|
|
@ -56,3 +56,14 @@ export const METRICS_POPOVER_CONTENT = {
|
|||
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>
|
||||
<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-qa-selector="board_add_new_list"
|
||||
>
|
||||
<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
|
||||
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"
|
||||
>
|
||||
<h3 class="gl-font-size-h2 gl-px-5 gl-py-5 gl-m-0" data-testid="board-add-column-form-title">
|
||||
{{ $options.i18n.newList }}
|
||||
</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"
|
||||
>
|
||||
<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 }}
|
||||
</h3>
|
||||
<p class="gl-mb-3">{{ $options.i18n.scopeDescription }}</p>
|
||||
|
@ -147,23 +144,18 @@ export default {
|
|||
</gl-dropdown>
|
||||
</gl-form-group>
|
||||
</div>
|
||||
<div
|
||||
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
|
||||
>
|
||||
<div class="gl-display-flex gl-mb-4">
|
||||
<gl-button
|
||||
data-testid="addNewColumnButton"
|
||||
:disabled="!selectedId"
|
||||
variant="confirm"
|
||||
class="gl-mr-4"
|
||||
class="gl-mr-3 gl-ml-4"
|
||||
@click="$emit('add-list')"
|
||||
>{{ $options.i18n.add }}</gl-button
|
||||
>
|
||||
<gl-button data-testid="cancelAddNewColumn" @click="setAddColumnFormVisibility(false)">{{
|
||||
$options.i18n.cancel
|
||||
}}</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -75,7 +75,7 @@ export default {
|
|||
v-if="!isSwimlanesOn"
|
||||
ref="list"
|
||||
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"
|
||||
>
|
||||
<board-column
|
||||
|
|
|
@ -3,6 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import { getCookie, setCookie } from '~/lib/utils/common_utils';
|
||||
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 PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
|
||||
import StageTable from '~/cycle_analytics/components/stage_table.vue';
|
||||
|
@ -150,6 +151,7 @@ export default {
|
|||
pageTitle: __('Value Stream Analytics'),
|
||||
recentActivity: __('Recent Project Activity'),
|
||||
},
|
||||
VSA_METRICS_GROUPS,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -178,6 +180,7 @@ export default {
|
|||
:request-path="endpoints.fullPath"
|
||||
:request-params="filterParams"
|
||||
:requests="metricsRequests"
|
||||
:group-by="$options.VSA_METRICS_GROUPS"
|
||||
/>
|
||||
<gl-loading-icon v-if="isLoading" size="lg" />
|
||||
<stage-table
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<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 { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
@ -21,6 +28,7 @@ export default {
|
|||
GlButton,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlTooltip,
|
||||
},
|
||||
actionSizeClasses: ['gl-h-7 gl-w-7'],
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
|
@ -48,6 +56,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
hasActionTooltip: false,
|
||||
isActionLoading: false,
|
||||
};
|
||||
},
|
||||
|
@ -139,13 +148,16 @@ export default {
|
|||
showAction() {
|
||||
return Boolean(this.action?.method && this.action?.icon && this.action?.ariaLabel);
|
||||
},
|
||||
showCardTooltip() {
|
||||
return !this.hasActionTooltip;
|
||||
},
|
||||
sourceJobName() {
|
||||
return this.pipeline.sourceJob?.name ?? '';
|
||||
},
|
||||
sourceJobInfo() {
|
||||
return this.isDownstream ? sprintf(__('Created by %{job}'), { job: this.sourceJobName }) : '';
|
||||
},
|
||||
tooltipText() {
|
||||
cardTooltipText() {
|
||||
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
|
||||
${this.sourceJobInfo}`;
|
||||
},
|
||||
|
@ -191,6 +203,9 @@ export default {
|
|||
retryPipeline() {
|
||||
this.executePipelineAction(RetryPipelineMutation);
|
||||
},
|
||||
setActionTooltip(flag) {
|
||||
this.hasActionTooltip = flag;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -198,14 +213,15 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
ref="linkedPipeline"
|
||||
v-gl-tooltip
|
||||
class="gl-h-full gl-display-flex! gl-border-solid gl-border-gray-100 gl-border-1"
|
||||
:class="flexDirection"
|
||||
:title="tooltipText"
|
||||
data-qa-selector="linked_pipeline_container"
|
||||
@mouseover="onDownstreamHovered"
|
||||
@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-display-flex gl-pr-3">
|
||||
<ci-status
|
||||
|
@ -227,12 +243,16 @@ export default {
|
|||
</div>
|
||||
<gl-button
|
||||
v-if="showAction"
|
||||
v-gl-tooltip
|
||||
:title="action.ariaLabel"
|
||||
:loading="isActionLoading"
|
||||
:icon="action.icon"
|
||||
class="gl-rounded-full!"
|
||||
:class="$options.actionSizeClasses"
|
||||
:aria-label="action.ariaLabel"
|
||||
@click="action.method"
|
||||
@mouseover="setActionTooltip(true)"
|
||||
@mouseout="setActionTooltip(false)"
|
||||
/>
|
||||
<div v-else :class="$options.actionSizeClasses"></div>
|
||||
</div>
|
||||
|
|
|
@ -47,6 +47,23 @@ export default {
|
|||
|
||||
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: {
|
||||
toggleAttentionRequired() {
|
||||
|
@ -57,6 +74,7 @@ export default {
|
|||
this.$emit('toggle-attention-requested', {
|
||||
user: this.user,
|
||||
callback: this.toggleAttentionRequiredComplete,
|
||||
direction: this.request.direction,
|
||||
});
|
||||
},
|
||||
toggleAttentionRequiredComplete() {
|
||||
|
@ -74,8 +92,8 @@ export default {
|
|||
>
|
||||
<gl-button
|
||||
:loading="loading"
|
||||
:variant="user.attention_requested ? 'warning' : 'default'"
|
||||
:icon="user.attention_requested ? 'attention-solid' : 'attention'"
|
||||
:variant="request.variant"
|
||||
:icon="request.icon"
|
||||
:aria-label="tooltipTitle"
|
||||
:class="{ 'gl-pointer-events-none': !user.can_update_merge_request }"
|
||||
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 reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.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 = {
|
||||
merge_request: sidebarDetailsMRQuery,
|
||||
|
@ -92,9 +93,19 @@ export default class SidebarService {
|
|||
});
|
||||
}
|
||||
|
||||
toggleAttentionRequested(userId) {
|
||||
requestAttention(userId) {
|
||||
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: {
|
||||
userId: convertToGraphQLId(TYPE_USER, `${userId}`),
|
||||
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 {
|
||||
const isReviewer = type === 'reviewer';
|
||||
const reviewerOrAssignee = isReviewer
|
||||
? this.store.findReviewer(user)
|
||||
: this.store.findAssignee(user);
|
||||
|
||||
await this.service.toggleAttentionRequested(user.id);
|
||||
await mutations[direction]?.(user.id);
|
||||
|
||||
if (reviewerOrAssignee.attention_requested) {
|
||||
toast(
|
||||
|
@ -138,7 +143,7 @@ export default class SidebarMediator {
|
|||
captureError: true,
|
||||
actionConfig: {
|
||||
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});
|
||||
overflow-x: scroll;
|
||||
min-height: 200px;
|
||||
border-left: 8px solid var(--gray-10, $white);
|
||||
|
||||
@include media-breakpoint-only(sm) {
|
||||
height: calc(100vh - #{$issue-board-list-difference-sm});
|
||||
|
|
|
@ -29,13 +29,12 @@ class Import::BitbucketController < Import::BaseController
|
|||
end
|
||||
end
|
||||
|
||||
# We need to re-expose controller's internal method 'status' as action.
|
||||
# rubocop:disable Lint/UselessMethodDefinition
|
||||
def status
|
||||
super
|
||||
end
|
||||
|
||||
def realtime_changes
|
||||
super
|
||||
end
|
||||
# rubocop:enable Lint/UselessMethodDefinition
|
||||
|
||||
def create
|
||||
bitbucket_client = Bitbucket::Client.new(credentials)
|
||||
|
|
|
@ -52,13 +52,12 @@ class Import::BitbucketServerController < Import::BaseController
|
|||
redirect_to status_import_bitbucket_server_path
|
||||
end
|
||||
|
||||
# We need to re-expose controller's internal method 'status' as action.
|
||||
# rubocop:disable Lint/UselessMethodDefinition
|
||||
def status
|
||||
super
|
||||
end
|
||||
|
||||
def realtime_changes
|
||||
super
|
||||
end
|
||||
# rubocop:enable Lint/UselessMethodDefinition
|
||||
|
||||
protected
|
||||
|
||||
|
|
|
@ -54,10 +54,6 @@ class Import::FogbugzController < Import::BaseController
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def realtime_changes
|
||||
super
|
||||
end
|
||||
|
||||
def create
|
||||
repo = client.repo(params[:repo_id])
|
||||
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
|
||||
|
|
|
@ -16,10 +16,12 @@ class Import::GiteaController < Import::GithubController
|
|||
super
|
||||
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
|
||||
super
|
||||
end
|
||||
# rubocop:enable Lint/UselessMethodDefinition
|
||||
|
||||
protected
|
||||
|
||||
|
|
|
@ -16,9 +16,12 @@ class Import::GitlabController < Import::BaseController
|
|||
redirect_to status_import_gitlab_url
|
||||
end
|
||||
|
||||
# We need to re-expose controller's internal method 'status' as action.
|
||||
# rubocop:disable Lint/UselessMethodDefinition
|
||||
def status
|
||||
super
|
||||
end
|
||||
# rubocop:enable Lint/UselessMethodDefinition
|
||||
|
||||
def create
|
||||
repo = client.project(params[:repo_id].to_i)
|
||||
|
|
|
@ -10,9 +10,12 @@ class Import::ManifestController < Import::BaseController
|
|||
def new
|
||||
end
|
||||
|
||||
# We need to re-expose controller's internal method 'status' as action.
|
||||
# rubocop:disable Lint/UselessMethodDefinition
|
||||
def status
|
||||
super
|
||||
end
|
||||
# rubocop:enable Lint/UselessMethodDefinition
|
||||
|
||||
def upload
|
||||
group = Group.find(params[:group_id])
|
||||
|
@ -36,10 +39,6 @@ class Import::ManifestController < Import::BaseController
|
|||
end
|
||||
end
|
||||
|
||||
def realtime_changes
|
||||
super
|
||||
end
|
||||
|
||||
def create
|
||||
repository = importable_repos.find do |project|
|
||||
project[:id] == params[:repo_id].to_i
|
||||
|
|
|
@ -13,7 +13,9 @@ module Projects
|
|||
prepend_before_action :repository, :project_without_auth
|
||||
|
||||
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
|
||||
token = extract_alert_manager_token(request)
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsFinder < GitRefsFinder
|
||||
def initialize(repository, params)
|
||||
super(repository, params)
|
||||
end
|
||||
|
||||
def execute(gitaly_pagination: false)
|
||||
tags = if gitaly_pagination
|
||||
repository.tags_sorted_by(sort, pagination_params)
|
||||
|
|
|
@ -68,6 +68,12 @@ module IssueResolverArguments
|
|||
description: 'Negated arguments.',
|
||||
prepare: ->(negated_args, ctx) { negated_args.to_h },
|
||||
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
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
|
|
|
@ -93,6 +93,7 @@ module NamespacesHelper
|
|||
namespace_actual_plan_name: namespace.actual_plan_name,
|
||||
namespace_path: namespace.full_path,
|
||||
namespace_id: namespace.id,
|
||||
user_namespace: namespace.user_namespace?.to_s,
|
||||
page_size: page_size
|
||||
}
|
||||
end
|
||||
|
|
|
@ -25,9 +25,7 @@ module MergeRequests
|
|||
|
||||
# expose issuable create method so it can be called from email
|
||||
# handler CreateMergeRequestHandler
|
||||
def create(merge_request)
|
||||
super
|
||||
end
|
||||
public :create
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -3,22 +3,13 @@
|
|||
|
||||
%fieldset
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :password_authentication_enabled_for_web, class: 'form-check-input'
|
||||
= f.label :password_authentication_enabled_for_web, class: 'form-check-label' do
|
||||
= _('Allow password authentication for the web interface')
|
||||
.form-text.text-muted
|
||||
= _('Clear this checkbox to use an external authentication provider instead.')
|
||||
= f.gitlab_ui_checkbox_component :password_authentication_enabled_for_web,
|
||||
_('Allow password authentication for the web interface'),
|
||||
help_text: _('Clear this checkbox to use an external authentication provider instead.')
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :password_authentication_enabled_for_git, class: 'form-check-input'
|
||||
= f.label :password_authentication_enabled_for_git, class: 'form-check-label' do
|
||||
= _('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.')
|
||||
= f.gitlab_ui_checkbox_component :password_authentication_enabled_for_git,
|
||||
_('Allow password authentication for Git over HTTP(S)'),
|
||||
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.')
|
||||
- if omniauth_enabled? && button_based_providers.any?
|
||||
%fieldset.form-group
|
||||
%legend.gl-font-base.gl-mb-3.gl-border-none.gl-font-weight-bold= _('Enabled OAuth authentication sources')
|
||||
|
@ -27,13 +18,11 @@
|
|||
= source
|
||||
.form-group
|
||||
= f.label :two_factor_authentication, _('Two-factor authentication'), class: 'label-bold'
|
||||
.form-check
|
||||
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
|
||||
= f.label :require_two_factor_authentication, class: 'form-check-label' do
|
||||
= _('Enforce two-factor authentication')
|
||||
%p.form-text.text-muted
|
||||
= _('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'
|
||||
- help_text = _('Enforce two-factor authentication for all user sign-ins.')
|
||||
- help_link = link_to _('Learn more.'), help_page_path('security/two_factor_authentication.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= f.gitlab_ui_checkbox_component :require_two_factor_authentication,
|
||||
_('Enforce two-factor authentication'),
|
||||
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||
.form-group
|
||||
= 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'
|
||||
|
@ -42,22 +31,18 @@
|
|||
.form-group
|
||||
= f.label :admin_mode, _('Admin Mode'), class: 'label-bold'
|
||||
= sprite_icon('lock', css_class: 'gl-icon')
|
||||
.form-check
|
||||
= f.check_box :admin_mode, class: 'form-check-input'
|
||||
= f.label :admin_mode, class: 'form-check-label' do
|
||||
= _('Enable admin mode')
|
||||
%p.form-text.text-muted
|
||||
= _('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'
|
||||
- help_text = _('Require additional authentication for administrative tasks.')
|
||||
- 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.gitlab_ui_checkbox_component :admin_mode,
|
||||
_('Enable admin mode'),
|
||||
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||
.form-group
|
||||
= f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold'
|
||||
.form-check
|
||||
= f.check_box :notify_on_unknown_sign_in, class: 'form-check-input'
|
||||
= f.label :notify_on_unknown_sign_in, class: 'form-check-label' do
|
||||
= _('Enable email notification')
|
||||
%p.form-text.text-muted
|
||||
= _('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'
|
||||
- help_text = _('Notify users by email when sign-in location is not recognized.')
|
||||
- help_link = link_to _('Learn more.'), help_page_path('user/profile/unknown_sign_in_notification.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= f.gitlab_ui_checkbox_component :notify_on_unknown_sign_in,
|
||||
_('Enable email notification'),
|
||||
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
|
||||
.form-group
|
||||
= 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'
|
||||
|
|
|
@ -39,10 +39,9 @@
|
|||
.label-container
|
||||
- if generic_commit_status.tags.any?
|
||||
- generic_commit_status.tags.each do |tag|
|
||||
%span.badge.badge-primary
|
||||
= tag
|
||||
= gl_badge_tag tag, variant: :info, size: :sm
|
||||
- if retried
|
||||
%span.badge.badge-warning retried
|
||||
= gl_badge_tag retried, variant: :warning, size: :sm
|
||||
|
||||
- if pipeline_link
|
||||
%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="groupissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after 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="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"]`. |
|
||||
|
@ -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="projectissuecreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after 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="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"]`. |
|
||||
|
@ -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="projectissuestatuscountscreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after 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="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. |
|
||||
|
@ -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="projectissuescreatedafter"></a>`createdAfter` | [`Time`](#time) | Issues created after 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="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"]`. |
|
||||
|
|
|
@ -190,40 +190,43 @@ data to be in the new format.
|
|||
|
||||
## Example
|
||||
|
||||
The table `integrations` has a field called `properties`, stored in JSON. For all rows,
|
||||
extract the `url` key from this JSON object and store it in the `integrations.url`
|
||||
column. Millions of integrations exist, and parsing JSON is slow, so you can't
|
||||
do this work in a regular migration.
|
||||
The `routes` table has a `source_type` field that's used for a polymorphic relationship.
|
||||
As part of a database redesign, we're removing the polymorphic relationship. One step of
|
||||
the work will be migrating data from the `source_id` column into a new singular foreign key.
|
||||
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
|
||||
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
|
||||
class Integration < ::ApplicationRecord
|
||||
self.table_name = 'integrations'
|
||||
end
|
||||
class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
|
||||
# For illustration purposes, if we were to use a local model we could
|
||||
# define it like below, using an `ApplicationRecord` as the base class
|
||||
# class Route < ::ApplicationRecord
|
||||
# self.table_name = 'routes'
|
||||
# end
|
||||
|
||||
def perform(start_id, end_id)
|
||||
Integration.where(id: start_id..end_id).each do |integration|
|
||||
json = JSON.load(integration.properties)
|
||||
|
||||
integration.update(url: json['url']) if json['url']
|
||||
rescue JSON::ParserError
|
||||
# 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
|
||||
def perform
|
||||
each_sub_batch(
|
||||
operation_name: :update_all,
|
||||
batching_scope: -> (relation) { relation.where("source_type <> 'UnusedType'") }
|
||||
) do |sub_batch|
|
||||
sub_batch.update_all('namespace_id = source_id')
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
NOTE:
|
||||
To get a `connection` in the batched background migration,use an inheritance
|
||||
relation using the following base class `Gitlab::BackgroundMigration::BaseJob`.
|
||||
For example: `class Gitlab::BackgroundMigration::ExtractIntegrationsUrl < Gitlab::BackgroundMigration::BaseJob`
|
||||
Job classes must be subclasses of `BatchedMigrationJob` to be
|
||||
correctly handled by the batched migration framework. Any subclass of
|
||||
`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:
|
||||
|
||||
```ruby
|
||||
|
@ -232,7 +235,7 @@ do this work in a regular migration.
|
|||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."url" := NEW.properties -> "url"
|
||||
NEW."namespace_id" = NEW."source_id"
|
||||
RETURN NEW;
|
||||
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:
|
||||
|
||||
```ruby
|
||||
class QueueExtractIntegrationsUrl < Gitlab::Database::Migration[1.0]
|
||||
class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
MIGRATION = 'ExtractIntegrationsUrl'
|
||||
MIGRATION = 'BackfillRouteNamespaceId'
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:integrations,
|
||||
:routes,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL
|
||||
)
|
||||
|
@ -259,7 +262,7 @@ do this work in a regular migration.
|
|||
|
||||
def down
|
||||
Gitlab::Database::BackgroundMigration::BatchedMigration
|
||||
.for_configuration(MIGRATION, :integrations, :id, []).delete_all
|
||||
.for_configuration(MIGRATION, :routes, :id, []).delete_all
|
||||
end
|
||||
end
|
||||
```
|
||||
|
@ -272,14 +275,14 @@ do this work in a regular migration.
|
|||
that checks that the batched background migration is completed. For example:
|
||||
|
||||
```ruby
|
||||
class FinalizeExtractIntegrationsUrlJobs < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'ExtractIntegrationsUrl'
|
||||
class FinalizeBackfillRouteNamespaceId < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'BackfillRouteNamespaceId'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION,
|
||||
table_name: :integrations,
|
||||
table_name: :routes,
|
||||
column_name: :id,
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -67,6 +67,11 @@ Fill in the following form to contact us and learn more about this offering.
|
|||
|
||||
<!-- 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>
|
||||
<form id="mktoForm_3226"></form>
|
||||
<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
|
||||
|
||||
1. On the merge request, in the top right corner, either:
|
||||
- Select **Report abuse**. This option is displayed if you do not have permission to close the merge request.
|
||||
- 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. On the merge request, in the top right corner, select the vertical ellipsis (**{ellipsis_v}**).
|
||||
1. Select **Report abuse**.
|
||||
1. Submit an abuse report.
|
||||
1. Select **Send report**.
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
module Bitbucket
|
||||
module Representation
|
||||
class Repo < Representation::Base
|
||||
def initialize(raw)
|
||||
super(raw)
|
||||
end
|
||||
|
||||
def owner_and_slug
|
||||
@owner_and_slug ||= full_name.split('/', 2)
|
||||
end
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
module BitbucketServer
|
||||
module Representation
|
||||
class Repo < Representation::Base
|
||||
def initialize(raw)
|
||||
super(raw)
|
||||
end
|
||||
|
||||
def project_key
|
||||
raw.dig('project', 'key')
|
||||
end
|
||||
|
|
|
@ -50,3 +50,12 @@ Style/FrozenStringLiteralComment:
|
|||
Enabled: true
|
||||
Details: >-
|
||||
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
|
||||
PATTERN = /("(?<string>.*?)")|('(?<string>.*?)')/.freeze
|
||||
|
||||
def initialize(value)
|
||||
super(value)
|
||||
end
|
||||
|
||||
def evaluate(variables = {})
|
||||
@value.to_s
|
||||
end
|
||||
|
|
|
@ -7,10 +7,6 @@ module Gitlab
|
|||
include ActiveModel::Validations
|
||||
include Entry::Validators
|
||||
|
||||
def initialize(entry)
|
||||
super(entry)
|
||||
end
|
||||
|
||||
def messages
|
||||
errors.full_messages.map do |error|
|
||||
"#{location} #{error}".downcase
|
||||
|
|
|
@ -13,10 +13,6 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(repository, name, target, target_commit)
|
||||
super(repository, name, target, target_commit)
|
||||
end
|
||||
|
||||
def active?
|
||||
self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago
|
||||
end
|
||||
|
|
|
@ -10,10 +10,6 @@ module Gitlab
|
|||
@expires_in = expires_in
|
||||
end
|
||||
|
||||
def cache_key(key)
|
||||
super(key)
|
||||
end
|
||||
|
||||
def clear_cache!(key)
|
||||
with do |redis|
|
||||
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
|
||||
|
|
|
@ -4,10 +4,6 @@ module Gitlab
|
|||
class SnippetSearchResults < SearchResults
|
||||
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)
|
||||
paginated_objects(snippet_titles, page, per_page)
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|%{linkStart}Shared runners%{linkEnd} are disabled, so there are no limits set on pipeline usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|%{linkTitle} help link"
|
||||
msgstr ""
|
||||
|
||||
|
@ -40934,6 +40937,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Snippets"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Something went wrong while fetching pipeline statistics"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Something went wrong while fetching project storage statistics"
|
||||
msgstr ""
|
||||
|
||||
|
@ -41663,6 +41669,9 @@ msgstr ""
|
|||
msgid "ValueStreamAnalytics|Average number of deployments to production per day."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|DORA metrics"
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Dashboard"
|
||||
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."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -17,10 +17,6 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
super
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/groups/#{group.id}/deploy_tokens"
|
||||
end
|
||||
|
|
|
@ -15,10 +15,6 @@ module QA
|
|||
Page::Project::Settings::AccessTokens.perform(&:created_access_token)
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
super
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/projects/#{project.api_resource[:id]}/access_tokens"
|
||||
end
|
||||
|
|
|
@ -17,10 +17,6 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def fabricate_via_api!
|
||||
super
|
||||
end
|
||||
|
||||
def api_get_path
|
||||
"/projects/#{project.id}/deploy_tokens"
|
||||
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 { shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
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 ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
||||
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 MetricTile from '~/analytics/shared/components/metric_tile.vue';
|
||||
import createFlash from '~/flash';
|
||||
|
@ -27,7 +27,7 @@ describe('ValueStreamMetrics', () => {
|
|||
});
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
return shallowMount(ValueStreamMetrics, {
|
||||
return shallowMountExtended(ValueStreamMetrics, {
|
||||
propsData: {
|
||||
requestPath,
|
||||
requestParams: {},
|
||||
|
@ -38,6 +38,7 @@ describe('ValueStreamMetrics', () => {
|
|||
};
|
||||
|
||||
const findMetrics = () => wrapper.findAllComponents(MetricTile);
|
||||
const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group');
|
||||
|
||||
const expectToHaveRequest = (fields) => {
|
||||
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
|
||||
|
@ -63,24 +64,6 @@ describe('ValueStreamMetrics', () => {
|
|||
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', () => {
|
||||
beforeEach(async () => {
|
||||
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 VueApollo from 'vue-apollo';
|
||||
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlButton, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
|
@ -37,14 +37,15 @@ describe('Linked pipeline', () => {
|
|||
};
|
||||
|
||||
const findButton = () => wrapper.find(GlButton);
|
||||
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
|
||||
const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline');
|
||||
const findCardTooltip = () => wrapper.findComponent(GlTooltip);
|
||||
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 findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
|
||||
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 mockApollo = createMockApollo();
|
||||
|
@ -101,18 +102,13 @@ describe('Linked pipeline', () => {
|
|||
expect(wrapper.text()).toContain(`#${props.pipeline.id}`);
|
||||
});
|
||||
|
||||
it('should correctly compute the tooltip text', () => {
|
||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
|
||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.status.label);
|
||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.sourceJob.name);
|
||||
expect(wrapper.vm.tooltipText).toContain(mockPipeline.id);
|
||||
});
|
||||
it('adds the card tooltip text to the DOM', () => {
|
||||
expect(findCardTooltip().exists()).toBe(true);
|
||||
|
||||
it('should render the tooltip text as the title attribute', () => {
|
||||
const titleAttr = findLinkedPipeline().attributes('title');
|
||||
|
||||
expect(titleAttr).toContain(mockPipeline.project.name);
|
||||
expect(titleAttr).toContain(mockPipeline.status.label);
|
||||
expect(findCardTooltip().text()).toContain(mockPipeline.project.name);
|
||||
expect(findCardTooltip().text()).toContain(mockPipeline.status.label);
|
||||
expect(findCardTooltip().text()).toContain(mockPipeline.sourceJob.name);
|
||||
expect(findCardTooltip().text()).toContain(mockPipeline.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);
|
||||
});
|
||||
|
||||
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('on success', () => {
|
||||
beforeEach(async () => {
|
||||
|
@ -258,6 +262,14 @@ describe('Linked pipeline', () => {
|
|||
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('on success', () => {
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -68,6 +68,7 @@ describe('Attention require toggle', () => {
|
|||
{
|
||||
user: { attention_requested: true, can_update_merge_request: true },
|
||||
callback: expect.anything(),
|
||||
direction: 'remove',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
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 Mock from './mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/vue_shared/plugins/global_toast');
|
||||
jest.mock('~/commons/nav/user_merge_requests');
|
||||
|
||||
|
@ -122,25 +124,39 @@ describe('Sidebar mediator', () => {
|
|||
});
|
||||
|
||||
describe('toggleAttentionRequested', () => {
|
||||
let attentionRequiredService;
|
||||
let requestAttentionMock;
|
||||
let removeAttentionRequestMock;
|
||||
|
||||
beforeEach(() => {
|
||||
attentionRequiredService = jest
|
||||
.spyOn(mediator.service, 'toggleAttentionRequested')
|
||||
requestAttentionMock = jest.spyOn(mediator.service, 'requestAttention').mockResolvedValue();
|
||||
removeAttentionRequestMock = jest
|
||||
.spyOn(mediator.service, 'removeAttentionRequest')
|
||||
.mockResolvedValue();
|
||||
});
|
||||
|
||||
it('calls attentionRequired service method', async () => {
|
||||
mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }];
|
||||
it.each`
|
||||
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', {
|
||||
user: { id: 1, username: 'root' },
|
||||
callback: jest.fn(),
|
||||
});
|
||||
await mediator.toggleAttentionRequested('reviewer', {
|
||||
user: { id: 1, username: 'root' },
|
||||
callback: jest.fn(),
|
||||
direction: serviceMethod,
|
||||
});
|
||||
|
||||
expect(attentionRequiredService).toHaveBeenCalledWith(1);
|
||||
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
|
||||
});
|
||||
expect(methods[serviceMethod]).toHaveBeenCalledWith(1);
|
||||
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
type | method
|
||||
|
@ -172,5 +188,27 @@ describe('Sidebar mediator', () => {
|
|||
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
|
||||
|
||||
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
|
||||
context 'when sorting by created' do
|
||||
it 'sorts issues ascending' do
|
||||
|
|
|
@ -275,6 +275,7 @@ RSpec.describe NamespacesHelper do
|
|||
namespace_actual_plan_name: user_group.actual_plan_name,
|
||||
namespace_path: user_group.full_path,
|
||||
namespace_id: user_group.id,
|
||||
user_namespace: user_group.user_namespace?.to_s,
|
||||
page_size: Kaminari.config.default_per_page
|
||||
})
|
||||
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