Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-17 03:08:51 +00:00
parent f52c68bbac
commit bb6a3bf05e
142 changed files with 512 additions and 498 deletions

View File

@ -1653,7 +1653,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/services/arkose/blocked_users_report_service_spec.rb'
- 'ee/spec/services/arkose/record_user_data_service_spec.rb'
- 'ee/spec/services/arkose/token_verification_service_spec.rb'
- 'ee/spec/services/audit_event_service_spec.rb'
- 'ee/spec/services/audit_events/build_service_spec.rb'
- 'ee/spec/services/audit_events/custom_audit_event_service_spec.rb'
- 'ee/spec/services/audit_events/impersonation_audit_event_service_spec.rb'
@ -1676,7 +1675,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb'
- 'ee/spec/services/award_emojis/add_service_spec.rb'
- 'ee/spec/services/award_emojis/destroy_service_spec.rb'
- 'ee/spec/services/base_count_service_spec.rb'
- 'ee/spec/services/billable_members/destroy_service_spec.rb'
- 'ee/spec/services/boards/create_service_spec.rb'
- 'ee/spec/services/boards/epic_boards/create_service_spec.rb'
@ -1695,28 +1693,11 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/services/boards/update_service_spec.rb'
- 'ee/spec/services/boards/user_preferences/update_service_spec.rb'
- 'ee/spec/services/branches/delete_service_spec.rb'
- 'ee/spec/services/ee/auto_merge_service_spec.rb'
- 'ee/spec/services/ee/event_create_service_spec.rb'
- 'ee/spec/services/ee/merge_request_metrics_service_spec.rb'
- 'ee/spec/services/ee/notes/destroy_service_spec.rb'
- 'ee/spec/services/ee/notes/post_process_service_spec.rb'
- 'ee/spec/services/ee/notes/quick_actions_service_spec.rb'
- 'ee/spec/services/ee/notes/update_service_spec.rb'
- 'ee/spec/services/ee/null_notification_service_spec.rb'
- 'ee/spec/services/ee/post_receive_service_spec.rb'
- 'ee/spec/services/ee/preview_markdown_service_spec.rb'
- 'ee/spec/services/ee/users/approve_service_spec.rb'
- 'ee/spec/services/ee/users/authorized_build_service_spec.rb'
- 'ee/spec/services/ee/users/block_service_spec.rb'
- 'ee/spec/services/ee/users/build_service_spec.rb'
- 'ee/spec/services/ee/users/create_service_spec.rb'
- 'ee/spec/services/ee/users/destroy_service_spec.rb'
- 'ee/spec/services/ee/users/migrate_records_to_ghost_user_service_spec.rb'
- 'ee/spec/services/ee/users/reject_service_spec.rb'
- 'ee/spec/services/ee/users/update_service_spec.rb'
- 'ee/spec/services/ee/vulnerability_feedback_module/update_service_spec.rb'
- 'ee/spec/services/external_status_checks/create_service_spec.rb'
- 'ee/spec/services/ldap_group_reset_service_spec.rb'
- 'ee/spec/services/projects/after_rename_service_spec.rb'
- 'ee/spec/services/projects/alerting/notify_service_spec.rb'
- 'ee/spec/services/projects/cleanup_service_spec.rb'
@ -1756,33 +1737,7 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/services/requirements_management/process_test_reports_service_spec.rb'
- 'ee/spec/services/resource_access_tokens/create_service_spec.rb'
- 'ee/spec/services/resource_access_tokens/revoke_service_spec.rb'
- 'ee/spec/services/start_pull_mirroring_service_spec.rb'
- 'ee/spec/services/system_note_service_spec.rb'
- 'ee/spec/services/timebox_report_service_spec.rb'
- 'ee/spec/services/todo_service_spec.rb'
- 'ee/spec/services/users/abuse/namespace_bans/create_service_spec.rb'
- 'ee/spec/services/users/abuse/namespace_bans/destroy_service_spec.rb'
- 'ee/spec/services/users/abuse/projects_download_ban_check_service_spec.rb'
- 'ee/spec/services/users/captcha_challenge_service_spec.rb'
- 'ee/spec/services/users/update_highest_member_role_service_spec.rb'
- 'ee/spec/services/users_ops_dashboard_projects/destroy_service_spec.rb'
- 'ee/spec/services/vulnerability_exports/create_service_spec.rb'
- 'ee/spec/services/vulnerability_exports/export_service_spec.rb'
- 'ee/spec/services/vulnerability_external_issue_links/create_service_spec.rb'
- 'ee/spec/services/vulnerability_external_issue_links/destroy_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/destroy_service_spec.rb'
- 'ee/spec/services/vulnerability_issue_links/create_service_spec.rb'
- 'ee/spec/services/vulnerability_issue_links/delete_service_spec.rb'
- 'ee/spec/services/vulnerability_merge_request_links/create_service_spec.rb'
- 'ee/spec/services/vulnerability_scanners/list_service_spec.rb'
- 'ee/spec/services/web_hook_service_spec.rb'
- 'ee/spec/services/wiki_pages/create_service_spec.rb'
- 'ee/spec/services/wiki_pages/destroy_service_spec.rb'
- 'ee/spec/services/wiki_pages/update_service_spec.rb'
- 'ee/spec/services/wikis/create_attachment_service_spec.rb'
- 'ee/spec/services/work_items/update_service_spec.rb'
- 'ee/spec/services/work_items/widgets/status_service/update_service_spec.rb'
- 'ee/spec/services/work_items/widgets/weight_service/update_service_spec.rb'
- 'ee/spec/tasks/geo/git_rake_spec.rb'
- 'ee/spec/tasks/gitlab/license_rake_spec.rb'
- 'ee/spec/tasks/gitlab/spdx_rake_spec.rb'
@ -6096,120 +6051,10 @@ RSpec/MissingFeatureCategory:
- 'spec/serializers/user_serializer_spec.rb'
- 'spec/serializers/web_ide_terminal_entity_spec.rb'
- 'spec/serializers/web_ide_terminal_serializer_spec.rb'
- 'spec/services/access_token_validation_service_spec.rb'
- 'spec/services/application_settings/update_service_spec.rb'
- 'spec/services/applications/create_service_spec.rb'
- 'spec/services/audit_event_service_spec.rb'
- 'spec/services/auto_merge_service_spec.rb'
- 'spec/services/base_container_service_spec.rb'
- 'spec/services/base_count_service_spec.rb'
- 'spec/services/bulk_create_integration_service_spec.rb'
- 'spec/services/bulk_push_event_payload_service_spec.rb'
- 'spec/services/bulk_update_integration_service_spec.rb'
- 'spec/services/cohorts_service_spec.rb'
- 'spec/services/compare_service_spec.rb'
- 'spec/services/event_create_service_spec.rb'
- 'spec/services/gpg_keys/destroy_service_spec.rb'
- 'spec/services/gravatar_service_spec.rb'
- 'spec/services/import_export_clean_up_service_spec.rb'
- 'spec/services/markdown_content_rewriter_service_spec.rb'
- 'spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb'
- 'spec/services/note_summary_spec.rb'
- 'spec/services/post_receive_service_spec.rb'
- 'spec/services/preview_markdown_service_spec.rb'
- 'spec/services/push_event_payload_service_spec.rb'
- 'spec/services/repository_archive_clean_up_service_spec.rb'
- 'spec/services/reset_project_cache_service_spec.rb'
- 'spec/services/service_response_spec.rb'
- 'spec/services/system_hooks_service_spec.rb'
- 'spec/services/task_list_toggle_service_spec.rb'
- 'spec/services/tasks_to_be_done/base_service_spec.rb'
- 'spec/services/terraform/remote_state_handler_spec.rb'
- 'spec/services/terraform/states/destroy_service_spec.rb'
- 'spec/services/terraform/states/trigger_destroy_service_spec.rb'
- 'spec/services/test_hooks/project_service_spec.rb'
- 'spec/services/test_hooks/system_service_spec.rb'
- 'spec/services/timelogs/delete_service_spec.rb'
- 'spec/services/todo_service_spec.rb'
- 'spec/services/todos/allowed_target_filter_service_spec.rb'
- 'spec/services/todos/destroy/confidential_issue_service_spec.rb'
- 'spec/services/todos/destroy/design_service_spec.rb'
- 'spec/services/todos/destroy/destroyed_issuable_service_spec.rb'
- 'spec/services/todos/destroy/project_private_service_spec.rb'
- 'spec/services/todos/destroy/unauthorized_features_service_spec.rb'
- 'spec/services/topics/merge_service_spec.rb'
- 'spec/services/two_factor/destroy_service_spec.rb'
- 'spec/services/update_container_registry_info_service_spec.rb'
- 'spec/services/update_merge_request_metrics_service_spec.rb'
- 'spec/services/upload_service_spec.rb'
- 'spec/services/uploads/destroy_service_spec.rb'
- 'spec/services/user_preferences/update_service_spec.rb'
- 'spec/services/users/activity_service_spec.rb'
- 'spec/services/users/approve_service_spec.rb'
- 'spec/services/users/authorized_build_service_spec.rb'
- 'spec/services/users/ban_service_spec.rb'
- 'spec/services/users/banned_user_base_service_spec.rb'
- 'spec/services/users/batch_status_cleaner_service_spec.rb'
- 'spec/services/users/block_service_spec.rb'
- 'spec/services/users/build_service_spec.rb'
- 'spec/services/users/create_service_spec.rb'
- 'spec/services/users/destroy_service_spec.rb'
- 'spec/services/users/dismiss_callout_service_spec.rb'
- 'spec/services/users/dismiss_group_callout_service_spec.rb'
- 'spec/services/users/dismiss_project_callout_service_spec.rb'
- 'spec/services/users/email_verification/generate_token_service_spec.rb'
- 'spec/services/users/email_verification/validate_token_service_spec.rb'
- 'spec/services/users/in_product_marketing_email_records_spec.rb'
- 'spec/services/users/keys_count_service_spec.rb'
- 'spec/services/users/last_push_event_service_spec.rb'
- 'spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb'
- 'spec/services/users/migrate_records_to_ghost_user_service_spec.rb'
- 'spec/services/users/refresh_authorized_projects_service_spec.rb'
- 'spec/services/users/registrations_build_service_spec.rb'
- 'spec/services/users/reject_service_spec.rb'
- 'spec/services/users/repair_ldap_blocked_service_spec.rb'
- 'spec/services/users/respond_to_terms_service_spec.rb'
- 'spec/services/users/saved_replies/create_service_spec.rb'
- 'spec/services/users/saved_replies/destroy_service_spec.rb'
- 'spec/services/users/saved_replies/update_service_spec.rb'
- 'spec/services/users/set_status_service_spec.rb'
- 'spec/services/users/signup_service_spec.rb'
- 'spec/services/users/unban_service_spec.rb'
- 'spec/services/users/unblock_service_spec.rb'
- 'spec/services/users/update_canonical_email_service_spec.rb'
- 'spec/services/users/update_highest_member_role_service_spec.rb'
- 'spec/services/users/update_service_spec.rb'
- 'spec/services/users/update_todo_count_cache_service_spec.rb'
- 'spec/services/users/upsert_credit_card_validation_service_spec.rb'
- 'spec/services/users/validate_manual_otp_service_spec.rb'
- 'spec/services/users/validate_push_otp_service_spec.rb'
- 'spec/services/verify_pages_domain_service_spec.rb'
- 'spec/services/web_hooks/destroy_service_spec.rb'
- 'spec/services/web_hooks/log_destroy_service_spec.rb'
- 'spec/services/web_hooks/log_execution_service_spec.rb'
- 'spec/services/webauthn/authenticate_service_spec.rb'
- 'spec/services/webauthn/register_service_spec.rb'
- 'spec/services/wiki_pages/base_service_spec.rb'
- 'spec/services/wiki_pages/create_service_spec.rb'
- 'spec/services/wiki_pages/destroy_service_spec.rb'
- 'spec/services/wiki_pages/event_create_service_spec.rb'
- 'spec/services/wiki_pages/update_service_spec.rb'
- 'spec/services/wikis/create_attachment_service_spec.rb'
- 'spec/services/work_items/build_service_spec.rb'
- 'spec/services/work_items/create_from_task_service_spec.rb'
- 'spec/services/work_items/create_service_spec.rb'
- 'spec/services/work_items/delete_service_spec.rb'
- 'spec/services/work_items/delete_task_service_spec.rb'
- 'spec/services/work_items/parent_links/destroy_service_spec.rb'
- 'spec/services/work_items/task_list_reference_removal_service_spec.rb'
- 'spec/services/work_items/task_list_reference_replacement_service_spec.rb'
- 'spec/services/work_items/update_service_spec.rb'
- 'spec/services/work_items/widgets/assignees_service/update_service_spec.rb'
- 'spec/services/work_items/widgets/description_service/update_service_spec.rb'
- 'spec/services/work_items/widgets/milestone_service/create_service_spec.rb'
- 'spec/services/work_items/widgets/milestone_service/update_service_spec.rb'
- 'spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb'
- 'spec/services/x509_certificate_revoke_service_spec.rb'
- 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/sidekiq_cluster/sidekiq_cluster_spec.rb'
- 'spec/spam/concerns/has_spam_action_response_fields_spec.rb'

View File

@ -1,25 +1,19 @@
import Vue from 'vue';
import { darkModeEnabled } from '~/lib/utils/color_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
export function getFrameSrc(url) {
return `${setUrlParams({ theme: darkModeEnabled() ? 'dark' : 'light' }, url)}&kiosk=inline-embed`;
}
import ObservabilityApp from '~/observability/components/observability_app.vue';
import { SKELETON_VARIANT_EMBED, INLINE_EMBED_DIMENSIONS } from '~/observability/constants';
const mountVueComponent = (element) => {
const url = [element.dataset.frameUrl];
const url = element.dataset.frameUrl;
return new Vue({
el: element,
render(h) {
return h('iframe', {
style: {
height: '366px',
width: '768px',
},
attrs: {
src: getFrameSrc(url),
frameBorder: '0',
return h(ObservabilityApp, {
props: {
observabilityIframeSrc: url,
inlineEmbed: true,
skeletonVariant: SKELETON_VARIANT_EMBED,
height: INLINE_EMBED_DIMENSIONS.HEIGHT,
width: INLINE_EMBED_DIMENSIONS.WIDTH,
},
});
},
@ -27,7 +21,5 @@ const mountVueComponent = (element) => {
};
export default function renderObservability(elements) {
elements.forEach((element) => {
mountVueComponent(element);
});
return elements.map(mountVueComponent);
}

View File

@ -102,10 +102,10 @@ export default {
'issuable-info-container': !canReorder,
'card-body': canReorder,
}"
class="item-body gl-display-flex gl-align-items-center gl-gap-3 gl-px-3 gl-py-2 py-xl-0 gl-mx-n2"
class="item-body gl-display-flex gl-align-items-center gl-gap-3 gl-mx-n2"
>
<div
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap gl-flex-grow-1 gl-gap-2 flex-xl-nowrap gl-min-h-7"
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap gl-flex-grow-1 gl-gap-2 gl-px-3 gl-py-2 py-xl-0 flex-xl-nowrap gl-min-h-7"
>
<!-- Title area: Status icon (XL) and title -->
<div class="item-title gl-display-flex gl-gap-3 gl-min-w-0">

View File

@ -96,8 +96,12 @@ export default {
label="Fetching related merge requests"
class="gl-py-4"
/>
<ul v-else class="content-list related-items-list">
<li v-for="mr in mergeRequests" :key="mr.id" class="list-item gl-m-0! gl-px-4! gl-py-3!">
<ul v-else class="content-list related-items-list gl-px-4! gl-py-3!">
<li
v-for="mr in mergeRequests"
:key="mr.id"
class="list-item gl-m-0! gl-p-0! gl-border-b-0!"
>
<related-issuable-item
:id-key="mr.id"
:display-reference="mr.reference"

View File

@ -2,7 +2,7 @@
import { darkModeEnabled } from '~/lib/utils/color_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { MESSAGE_EVENT_TYPE, SKELETON_VARIANTS_BY_ROUTE } from '../constants';
import { MESSAGE_EVENT_TYPE, FULL_APP_DIMENSIONS } from '../constants';
import ObservabilitySkeleton from './skeleton/index.vue';
export default {
@ -14,25 +14,33 @@ export default {
type: String,
required: true,
},
inlineEmbed: {
type: Boolean,
required: false,
default: false,
},
skeletonVariant: {
type: String,
required: false,
default: 'dashboards',
},
height: {
type: String,
required: false,
default: FULL_APP_DIMENSIONS.HEIGHT,
},
width: {
type: String,
required: false,
default: FULL_APP_DIMENSIONS.WIDTH,
},
},
computed: {
iframeSrcWithParams() {
return setUrlParams(
return `${setUrlParams(
{ theme: darkModeEnabled() ? 'dark' : 'light', username: gon?.current_username },
this.observabilityIframeSrc,
);
},
getSkeletonVariant() {
const [, variant] =
Object.entries(SKELETON_VARIANTS_BY_ROUTE).find(([path]) =>
this.$route.path.endsWith(path),
) || [];
const DEFAULT_SKELETON = 'dashboards';
if (!variant) return DEFAULT_SKELETON;
return variant;
)}${this.inlineEmbed ? '&kiosk=inline-embed' : ''}`;
},
},
mounted() {
@ -54,38 +62,24 @@ export default {
this.$refs.observabilitySkeleton.onContentLoaded();
break;
case MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE:
this.routeUpdateHandler(payload);
this.$emit('route-update', payload);
break;
default:
break;
}
},
routeUpdateHandler(payload) {
const isNewObservabilityPath = this.$route?.query?.observability_path !== payload?.url;
const shouldNotHandleMessage = !payload.url || !isNewObservabilityPath;
if (shouldNotHandleMessage) {
return;
}
// this will update the `observability_path` query param on each route change inside Observability UI
this.$router.replace({
name: this.$route.pathname,
query: { ...this.$route.query, observability_path: payload.url },
});
},
},
};
</script>
<template>
<observability-skeleton ref="observabilitySkeleton" :variant="getSkeletonVariant">
<observability-skeleton ref="observabilitySkeleton" :variant="skeletonVariant">
<iframe
id="observability-ui-iframe"
data-testid="observability-ui-iframe"
frameborder="0"
height="100%"
:width="width"
:height="height"
:src="iframeSrcWithParams"
sandbox="allow-same-origin allow-forms allow-scripts"
></iframe>

View File

@ -0,0 +1,15 @@
<script>
import { GlSkeletonLoader } from '@gitlab/ui';
export default {
components: {
GlSkeletonLoader,
},
};
</script>
<template>
<gl-skeleton-loader>
<rect y="5" width="400" height="30" rx="2" ry="2" />
<rect y="50" width="400" height="80" rx="2" ry="2" />
</gl-skeleton-loader>
</template>

View File

@ -8,10 +8,12 @@ import {
OBSERVABILITY_ROUTES,
TIMEOUT_ERROR_LABEL,
TIMEOUT_ERROR_MESSAGE,
SKELETON_VARIANT_EMBED,
} from '../../constants';
import DashboardsSkeleton from './dashboards.vue';
import ExploreSkeleton from './explore.vue';
import ManageSkeleton from './manage.vue';
import EmbedSkeleton from './embed.vue';
export default {
components: {
@ -19,11 +21,13 @@ export default {
DashboardsSkeleton,
ExploreSkeleton,
ManageSkeleton,
EmbedSkeleton,
GlAlert,
},
SKELETON_VARIANTS_BY_ROUTE,
SKELETON_STATE,
OBSERVABILITY_ROUTES,
SKELETON_VARIANT_EMBED,
i18n: {
TIMEOUT_ERROR_LABEL,
TIMEOUT_ERROR_MESSAGE,
@ -102,6 +106,7 @@ export default {
<dashboards-skeleton v-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.DASHBOARDS)" />
<explore-skeleton v-else-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.EXPLORE)" />
<manage-skeleton v-else-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.MANAGE)" />
<embed-skeleton v-else-if="variant === $options.SKELETON_VARIANT_EMBED" />
<gl-skeleton-loader v-else>
<rect y="2" width="10" height="8" />
@ -122,12 +127,14 @@ export default {
{{ $options.i18n.TIMEOUT_ERROR_MESSAGE }}
</gl-alert>
<div
v-show="state === $options.SKELETON_STATE.HIDDEN"
data-testid="observability-wrapper"
class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-flex-align-items-stretch"
>
<slot></slot>
</div>
<transition>
<div
v-show="state === $options.SKELETON_STATE.HIDDEN"
data-testid="observability-wrapper"
class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-flex-align-items-stretch"
>
<slot></slot>
</div>
</transition>
</div>
</template>

View File

@ -17,6 +17,8 @@ export const SKELETON_VARIANTS_BY_ROUTE = Object.freeze({
[OBSERVABILITY_ROUTES.MANAGE]: 'manage',
});
export const SKELETON_VARIANT_EMBED = 'embed';
export const SKELETON_STATE = Object.freeze({
ERROR: 'error',
VISIBLE: 'visible',
@ -30,3 +32,13 @@ export const DEFAULT_TIMERS = Object.freeze({
export const TIMEOUT_ERROR_LABEL = __('Unable to load the page');
export const TIMEOUT_ERROR_MESSAGE = __('Reload the page to try again.');
export const INLINE_EMBED_DIMENSIONS = Object.freeze({
HEIGHT: '366px',
WIDTH: '768px',
});
export const FULL_APP_DIMENSIONS = Object.freeze({
HEIGHT: '100%',
WIDTH: '100%',
});

View File

@ -2,6 +2,7 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import ObservabilityApp from './components/observability_app.vue';
import { SKELETON_VARIANTS_BY_ROUTE } from './constants';
Vue.use(VueRouter);
@ -17,10 +18,41 @@ export default () => {
return new Vue({
el,
router,
computed: {
skeletonVariant() {
const [, variant] =
Object.entries(SKELETON_VARIANTS_BY_ROUTE).find(([path]) =>
this.$route.path.endsWith(path),
) || [];
return variant;
},
},
methods: {
routeUpdateHandler(payload) {
const isNewObservabilityPath = this.$route?.query?.observability_path !== payload?.url;
const shouldNotHandleMessage = !payload.url || !isNewObservabilityPath;
if (shouldNotHandleMessage) {
return;
}
// this will update the `observability_path` query param on each route change inside Observability UI
this.$router.replace({
name: this.$route?.pathname,
query: { ...this.$route.query, observability_path: payload.url },
});
},
},
render(h) {
return h(ObservabilityApp, {
props: {
observabilityIframeSrc: el.dataset.observabilityIframeSrc,
skeletonVariant: this.skeletonVariant,
},
on: {
'route-update': (payload) => this.routeUpdateHandler(payload),
},
});
},

View File

@ -159,6 +159,11 @@ $item-remove-button-space: 42px;
.mr-ci-status {
line-height: 0;
a:focus {
@include gl-rounded-full;
@include gl-focus;
}
}
@include media-breakpoint-down(xs) {

View File

@ -7,8 +7,9 @@ module Types
description 'Values for sorting projects'
value 'SIMILARITY', 'Most similar to the search query.', value: :similarity
value 'STORAGE', 'Sort by storage size.', value: :storage
value 'ACTIVITY_DESC', 'Sort by latest activity, in descending order.', value: :latest_activity_desc
value 'ACTIVITY_DESC', 'Sort by latest activity, descending order.', value: :latest_activity_desc
end
end
end
Types::Projects::NamespaceProjectSortEnum.prepend_mod

View File

@ -13,4 +13,5 @@
help_text: '%{help_text} %{learn_more_link}'.html_safe % { help_text: help_text, learn_more_link: learn_more_link },
checkbox_options: { checked: group.auto_devops_enabled? }
= f.submit _('Save changes'), class: 'gl-mt-5', pajamas_button: true
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { class: 'gl-mt-5' }) do
= _('Save changes')

View File

@ -1,6 +1,7 @@
---
table_name: bulk_import_batch_trackers
classes: []
classes:
- BulkImports::BatchTracker
feature_categories:
- importers
description: Used to store and track the import status of a batch of relations for the migration

View File

@ -1,6 +1,7 @@
---
table_name: bulk_import_export_batches
classes: []
classes:
- BulkImports::ExportBatch
feature_categories:
- importers
description: Used to track the generation status of export batch files for groups

View File

@ -47,6 +47,7 @@ classes:
- Integrations::Shimo
- Integrations::Slack
- Integrations::SlackSlashCommands
- Integrations::SquashTm
- Integrations::Teamcity
- Integrations::UnifyCircuit
- Integrations::WebexTeams

View File

@ -23781,9 +23781,11 @@ Values for sorting projects.
| Value | Description |
| ----- | ----------- |
| <a id="namespaceprojectsortactivity_desc"></a>`ACTIVITY_DESC` | Sort by latest activity, in descending order. |
| <a id="namespaceprojectsortactivity_desc"></a>`ACTIVITY_DESC` | Sort by latest activity, descending order. |
| <a id="namespaceprojectsortsimilarity"></a>`SIMILARITY` | Most similar to the search query. |
| <a id="namespaceprojectsortstorage"></a>`STORAGE` | Sort by storage size. |
| <a id="namespaceprojectsortstorage"></a>`STORAGE` | Sort by excess repository storage size, descending order. |
| <a id="namespaceprojectsortstorage_size_asc"></a>`STORAGE_SIZE_ASC` | Sort by total storage size, ascending order. |
| <a id="namespaceprojectsortstorage_size_desc"></a>`STORAGE_SIZE_DESC` | Sort by total storage size, descending order. |
### `NegatedIterationWildcardId`

View File

@ -630,6 +630,7 @@ The following variables allow configuration of global dependency scanning settin
| `DS_EXCLUDED_ANALYZERS` | Specify the analyzers (by name) to exclude from Dependency Scanning. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs (see [`doublestar.Match`](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4@v4.0.2#Match) for supported patterns), or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
| `DS_IMAGE_SUFFIX` | Suffix added to the image name. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354796) in GitLab 14.10.) Automatically set to `"-fips"` when FIPS mode is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357922) in GitLab 15.0.) |
| `DS_MAX_DEPTH` | Defines how many directory levels deep that the analyzer should search for supported files to scan. A value of `-1` scans all directories regardless of depth. Default: `2`. |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SECURE_LOG_LEVEL` | Set the minimum logging level. Messages of this logging level or higher are output. From highest to lowest severity, the logging levels are: `fatal`, `error`, `warn`, `info`, `debug`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10880) in GitLab 13.1. Default: `info`. |

View File

@ -10,8 +10,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
You can use scan result policies to take action based on scan results. For example, one type of scan
result policy is a security approval policy that allows approval to be required based on the
findings of one or more security scan jobs. Scan result policies are evaluated after a CI scanning
job is fully executed. The following video gives you an overview of GitLab scan result policies:
findings of one or more security scan jobs. Scan result policies are evaluated after a CI scanning job is fully executed.
NOTE:
Scan result policies are applicable only to [protected](../../project/protected_branches.md) target branches.
The following video gives you an overview of GitLab scan result policies:
<div class="video-fallback">
See the video: <a href="https://youtu.be/w5I9gcUgr9U">Overview of GitLab Scan Result Policies</a>.

View File

@ -170,11 +170,7 @@ passing in an optional value to the `commit_sha` query parameter.
## Compliance frameworks report
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387910) in GitLab 15.10 with a flag named `compliance_frameworks_report`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `compliance_frameworks_report`.
On GitLab.com, this feature is available.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387910) in GitLab 15.10.
With compliance frameworks report, you can see the compliance frameworks that are applied to projects in a group. Each row of the report shows:

View File

@ -9,7 +9,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8092) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `license_scanning_policies`. Disabled by default.
License Approval Policies allow you to specify multiple types of criteria that define when approval is required before a merge request can be merged in. The following video provides an overview of these policies.
License Approval Policies allow you to specify multiple types of criteria that define when approval is required before a merge request can be merged in.
NOTE:
License Approval Policies are applicable only to [protected](../project/protected_branches.md) target branches.
The following video provides an overview of these policies.
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=34qBQ9t8qO8">Overview of GitLab License Approval Policies</a>.

View File

@ -56,15 +56,15 @@ dependency_scanning:
.gemnasium-shared-rule:
exists:
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- '{composer.lock,*/composer.lock,*/*/composer.lock}'
- '{gems.locked,*/gems.locked,*/*/gems.locked}'
- '{go.sum,*/go.sum,*/*/go.sum}'
- '{npm-shrinkwrap.json,*/npm-shrinkwrap.json,*/*/npm-shrinkwrap.json}'
- '{package-lock.json,*/package-lock.json,*/*/package-lock.json}'
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
- '{packages.lock.json,*/packages.lock.json,*/*/packages.lock.json}'
- '{conan.lock,*/conan.lock,*/*/conan.lock}'
- '**/Gemfile.lock'
- '**/composer.lock'
- '**/gems.locked'
- '**/go.sum'
- '**/npm-shrinkwrap.json'
- '**/package-lock.json'
- '**/yarn.lock'
- '**/packages.lock.json'
- '**/conan.lock'
gemnasium-dependency_scanning:
extends:
@ -91,10 +91,10 @@ gemnasium-dependency_scanning:
.gemnasium-maven-shared-rule:
exists:
- '{build.gradle,*/build.gradle,*/*/build.gradle}'
- '{build.gradle.kts,*/build.gradle.kts,*/*/build.gradle.kts}'
- '{build.sbt,*/build.sbt,*/*/build.sbt}'
- '{pom.xml,*/pom.xml,*/*/pom.xml}'
- '**/build.gradle'
- '**/build.gradle.kts'
- '**/build.sbt'
- '**/pom.xml'
gemnasium-maven-dependency_scanning:
extends:
@ -119,12 +119,12 @@ gemnasium-maven-dependency_scanning:
.gemnasium-python-shared-rule:
exists:
- '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
- '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
- '{Pipfile,*/Pipfile,*/*/Pipfile}'
- '{requires.txt,*/requires.txt,*/*/requires.txt}'
- '{setup.py,*/setup.py,*/*/setup.py}'
- '{poetry.lock,*/poetry.lock,*/*/poetry.lock}'
- '**/requirements.txt'
- '**/requirements.pip'
- '**/Pipfile'
- '**/requires.txt'
- '**/setup.py'
- '**/poetry.lock'
gemnasium-python-dependency_scanning:
extends:

View File

@ -56,15 +56,15 @@ dependency_scanning:
.gemnasium-shared-rule:
exists:
- '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- '{composer.lock,*/composer.lock,*/*/composer.lock}'
- '{gems.locked,*/gems.locked,*/*/gems.locked}'
- '{go.sum,*/go.sum,*/*/go.sum}'
- '{npm-shrinkwrap.json,*/npm-shrinkwrap.json,*/*/npm-shrinkwrap.json}'
- '{package-lock.json,*/package-lock.json,*/*/package-lock.json}'
- '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
- '{packages.lock.json,*/packages.lock.json,*/*/packages.lock.json}'
- '{conan.lock,*/conan.lock,*/*/conan.lock}'
- '**/Gemfile.lock'
- '**/composer.lock'
- '**/gems.locked'
- '**/go.sum'
- '**/npm-shrinkwrap.json'
- '**/package-lock.json'
- '**/yarn.lock'
- '**/packages.lock.json'
- '**/conan.lock'
gemnasium-dependency_scanning:
extends:
@ -109,10 +109,10 @@ gemnasium-dependency_scanning:
.gemnasium-maven-shared-rule:
exists:
- '{build.gradle,*/build.gradle,*/*/build.gradle}'
- '{build.gradle.kts,*/build.gradle.kts,*/*/build.gradle.kts}'
- '{build.sbt,*/build.sbt,*/*/build.sbt}'
- '{pom.xml,*/pom.xml,*/*/pom.xml}'
- '**/build.gradle'
- '**/build.gradle.kts'
- '**/build.sbt'
- '**/pom.xml'
gemnasium-maven-dependency_scanning:
extends:
@ -155,12 +155,12 @@ gemnasium-maven-dependency_scanning:
.gemnasium-python-shared-rule:
exists:
- '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
- '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
- '{Pipfile,*/Pipfile,*/*/Pipfile}'
- '{requires.txt,*/requires.txt,*/*/requires.txt}'
- '{setup.py,*/setup.py,*/*/setup.py}'
- '{poetry.lock,*/poetry.lock,*/*/poetry.lock}'
- '**/requirements.txt'
- '**/requirements.pip'
- '**/Pipfile'
- '**/requires.txt'
- '**/setup.py'
- '**/poetry.lock'
gemnasium-python-dependency_scanning:
extends:

View File

@ -1,7 +1,10 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Verify', :runner, product_group: :pipeline_security do
RSpec.describe 'Verify', :runner, product_group: :pipeline_security, quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/396855',
type: :flaky
} do
describe "Unlocking job artifacts across parent-child pipelines" do
let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }

View File

@ -147,14 +147,14 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do
selected_group.add_owner(user)
end
it 'can successfully transfer the group', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384966' do
it 'can successfully transfer the group' do
visit edit_group_path(selected_group)
page.within('[data-testid="transfer-locations-dropdown"]') do
click_button _('Select parent group')
fill_in _('Search'), with: target_group_name
wait_for_requests
click_button target_group_name
click_button(target_group_name || 'No parent group')
end
click_button s_('GroupSettings|Transfer group')
@ -166,7 +166,10 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do
click_button 'Confirm'
end
expect(page).to have_text "Group '#{selected_group.name}' was successfully transferred."
within('[data-testid="breadcrumb-links"]') do
expect(page).to have_content(target_group_name) if target_group_name
expect(page).to have_content(selected_group.name)
end
expect(current_url).to include(selected_group.reload.full_path)
end
end
@ -175,7 +178,7 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do
let(:selected_group) { create(:group, path: 'foo-subgroup', parent: group) }
context 'when transfering to no parent group' do
let(:target_group_name) { 'No parent group' }
let(:target_group_name) { nil }
it_behaves_like 'can transfer the group'
end

View File

@ -8,9 +8,6 @@ RSpec.describe 'Observability rendering', :js, feature_category: :metrics do
let_it_be(:user) { create(:user) }
let_it_be(:observable_url) { "https://www.gitlab.com/groups/#{group.path}/-/observability/explore?observability_path=/explore?foo=bar" }
let_it_be(:expected_observable_url) { "https://observe.gitlab.com/-/#{group.id}/explore?foo=bar" }
let_it_be(:expected) do
%(<iframe src="#{expected_observable_url}&amp;theme=light&amp;kiosk=inline-embed" frameborder="0")
end
before do
stub_config_setting(url: "https://www.gitlab.com")

View File

@ -1,40 +1,43 @@
import Vue from 'vue';
import { createWrapper } from '@vue/test-utils';
import renderObservability from '~/behaviors/markdown/render_observability';
import * as ColorUtils from '~/lib/utils/color_utils';
import { INLINE_EMBED_DIMENSIONS, SKELETON_VARIANT_EMBED } from '~/observability/constants';
import ObservabilityApp from '~/observability/components/observability_app.vue';
describe('Observability iframe renderer', () => {
const findObservabilityIframes = (theme = 'light') =>
document.querySelectorAll(
`iframe[src="https://observe.gitlab.com/?theme=${theme}&kiosk=inline-embed"]`,
);
const renderEmbeddedObservability = () => {
renderObservability([...document.querySelectorAll('.js-render-observability')]);
jest.runAllTimers();
};
describe('renderObservability', () => {
let subject;
beforeEach(() => {
document.body.dataset.page = '';
document.body.innerHTML = '';
subject = document.createElement('div');
subject.classList.add('js-render-observability');
subject.dataset.frameUrl = 'https://observe.gitlab.com/';
document.body.appendChild(subject);
});
it('renders an observability iframe', () => {
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
expect(findObservabilityIframes()).toHaveLength(0);
renderEmbeddedObservability();
expect(findObservabilityIframes()).toHaveLength(1);
afterEach(() => {
subject.remove();
});
it('renders iframe with dark param when GL has dark theme', () => {
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true);
it('should return an array of Vue instances', () => {
const vueInstances = renderObservability([
...document.querySelectorAll('.js-render-observability'),
]);
expect(vueInstances).toEqual([expect.any(Vue)]);
});
expect(findObservabilityIframes('dark')).toHaveLength(0);
it('should correctly pass props to the ObservabilityApp component', () => {
const vueInstances = renderObservability([
...document.querySelectorAll('.js-render-observability'),
]);
renderEmbeddedObservability();
const wrapper = createWrapper(vueInstances[0]);
expect(findObservabilityIframes('dark')).toHaveLength(1);
expect(wrapper.findComponent(ObservabilityApp).props()).toMatchObject({
observabilityIframeSrc: 'https://observe.gitlab.com/',
skeletonVariant: SKELETON_VARIANT_EMBED,
inlineEmbed: true,
height: INLINE_EMBED_DIMENSIONS.HEIGHT,
width: INLINE_EMBED_DIMENSIONS.WIDTH,
});
});
});

View File

@ -0,0 +1,64 @@
import { createWrapper } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import renderObservability from '~/observability/index';
import ObservabilityApp from '~/observability/components/observability_app.vue';
import { SKELETON_VARIANTS_BY_ROUTE } from '~/observability/constants';
describe('renderObservability', () => {
let element;
let vueInstance;
let component;
const OBSERVABILITY_ROUTES = Object.keys(SKELETON_VARIANTS_BY_ROUTE);
const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
beforeEach(() => {
element = document.createElement('div');
element.setAttribute('id', 'js-observability-app');
element.dataset.observabilityIframeSrc = 'https://observe.gitlab.com/';
document.body.appendChild(element);
vueInstance = renderObservability();
component = createWrapper(vueInstance).findComponent(ObservabilityApp);
});
afterEach(() => {
element.remove();
});
it('should return a Vue instance', () => {
expect(vueInstance).toEqual(expect.any(Vue));
});
it('should render the ObservabilityApp component', () => {
expect(component.props('observabilityIframeSrc')).toBe('https://observe.gitlab.com/');
});
describe('skeleton variant', () => {
it.each`
pathDescription | path | variant
${'dashboards'} | ${OBSERVABILITY_ROUTES[0]} | ${SKELETON_VARIANTS[0]}
${'explore'} | ${OBSERVABILITY_ROUTES[1]} | ${SKELETON_VARIANTS[1]}
${'manage dashboards'} | ${OBSERVABILITY_ROUTES[2]} | ${SKELETON_VARIANTS[2]}
${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANTS[0]}
`(
'renders the $variant skeleton variant for $pathDescription path',
async ({ path, variant }) => {
component.vm.$router.push(path);
await nextTick();
expect(component.props('skeletonVariant')).toBe(variant);
},
);
});
it('handle route-update events', async () => {
component.vm.$router.push('/something?foo=bar');
component.vm.$emit('route-update', { url: '/some_path' });
expect(component.vm.$router.currentRoute.path).toBe('/something');
expect(component.vm.$router.currentRoute.query).toEqual({
foo: 'bar',
observability_path: '/some_path',
});
});
});

View File

@ -1,19 +1,20 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ObservabilityApp from '~/observability/components/observability_app.vue';
import ObservabilitySkeleton from '~/observability/components/skeleton/index.vue';
import { MESSAGE_EVENT_TYPE, SKELETON_VARIANTS_BY_ROUTE } from '~/observability/constants';
import {
MESSAGE_EVENT_TYPE,
INLINE_EMBED_DIMENSIONS,
FULL_APP_DIMENSIONS,
SKELETON_VARIANT_EMBED,
} from '~/observability/constants';
import { darkModeEnabled } from '~/lib/utils/color_utils';
jest.mock('~/lib/utils/color_utils');
describe('Observability root app', () => {
describe('ObservabilityApp', () => {
let wrapper;
const replace = jest.fn();
const $router = {
replace,
};
const $route = {
pathname: 'https://gitlab.com/gitlab-org/',
path: 'https://gitlab.com/gitlab-org/-/observability/dashboards',
@ -26,21 +27,19 @@ describe('Observability root app', () => {
const TEST_IFRAME_SRC = 'https://observe.gitlab.com/9970/?groupId=14485840';
const OBSERVABILITY_ROUTES = Object.keys(SKELETON_VARIANTS_BY_ROUTE);
const TEST_USERNAME = 'test-user';
const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
const mountComponent = (route = $route) => {
const mountComponent = (props) => {
wrapper = shallowMountExtended(ObservabilityApp, {
propsData: {
observabilityIframeSrc: TEST_IFRAME_SRC,
...props,
},
stubs: {
'observability-skeleton': ObservabilitySkeleton,
},
mocks: {
$router,
$route: route,
$route,
},
});
};
@ -48,13 +47,11 @@ describe('Observability root app', () => {
const dispatchMessageEvent = (message) =>
window.dispatchEvent(new MessageEvent('message', message));
beforeEach(() => {
gon.current_username = TEST_USERNAME;
});
describe('iframe src', () => {
const TEST_USERNAME = 'test-user';
beforeEach(() => {
gon.current_username = TEST_USERNAME;
});
it('should render an iframe with observabilityIframeSrc, decorated with light theme and username', () => {
darkModeEnabled.mockReturnValueOnce(false);
mountComponent();
@ -88,48 +85,70 @@ describe('Observability root app', () => {
});
});
describe('on GOUI_ROUTE_UPDATE', () => {
it('should not call replace method from vue router if message event does not have url', () => {
mountComponent();
dispatchMessageEvent({
type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE,
payload: { data: 'some other data' },
describe('iframe kiosk query param', () => {
it('when inlineEmbed, it should set the proper kiosk query parameter', () => {
mountComponent({
inlineEmbed: true,
});
expect(replace).not.toHaveBeenCalled();
const iframe = findIframe();
expect(iframe.attributes('src')).toBe(
`${TEST_IFRAME_SRC}&theme=light&username=${TEST_USERNAME}&kiosk=inline-embed`,
);
});
});
describe('iframe size', () => {
it('should set the specified size', () => {
mountComponent({
height: INLINE_EMBED_DIMENSIONS.HEIGHT,
width: INLINE_EMBED_DIMENSIONS.WIDTH,
});
const iframe = findIframe();
expect(iframe.attributes('width')).toBe(INLINE_EMBED_DIMENSIONS.WIDTH);
expect(iframe.attributes('height')).toBe(INLINE_EMBED_DIMENSIONS.HEIGHT);
});
it.each`
condition | origin | observability_path | url
${'message origin is different from iframe source origin'} | ${'https://example.com'} | ${'/'} | ${'/explore'}
${'path is same as before (observability_path)'} | ${'https://observe.gitlab.com'} | ${'/foo?bar=test'} | ${'/foo?bar=test'}
`(
'should not call replace method from vue router if $condition',
async ({ origin, observability_path, url }) => {
mountComponent({ ...$route, query: { observability_path } });
dispatchMessageEvent({
data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url } },
origin,
});
expect(replace).not.toHaveBeenCalled();
},
);
it('should fallback to default size', () => {
mountComponent({});
it('should call replace method from vue router on message event callback', () => {
const iframe = findIframe();
expect(iframe.attributes('width')).toBe(FULL_APP_DIMENSIONS.WIDTH);
expect(iframe.attributes('height')).toBe(FULL_APP_DIMENSIONS.HEIGHT);
});
});
describe('skeleton variant', () => {
it('sets the specified skeleton variant', () => {
mountComponent({ skeletonVariant: SKELETON_VARIANT_EMBED });
const props = wrapper.findComponent(ObservabilitySkeleton).props();
expect(props.variant).toBe(SKELETON_VARIANT_EMBED);
});
it('should have a default skeleton variant', () => {
mountComponent();
const props = wrapper.findComponent(ObservabilitySkeleton).props();
expect(props.variant).toBe('dashboards');
});
});
describe('on GOUI_ROUTE_UPDATE', () => {
it('should emit a route-update event', () => {
mountComponent();
const payload = { url: '/explore' };
dispatchMessageEvent({
data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url: '/explore' } },
data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload },
origin: 'https://observe.gitlab.com',
});
expect(replace).toHaveBeenCalled();
expect(replace).toHaveBeenCalledWith({
name: 'https://gitlab.com/gitlab-org/',
query: {
otherQuery: 100,
observability_path: '/explore',
},
});
expect(wrapper.emitted('route-update')[0]).toEqual([payload]);
});
});
@ -163,34 +182,17 @@ describe('Observability root app', () => {
});
});
describe('skeleton variant', () => {
it.each`
pathDescription | path | variant
${'dashboards'} | ${OBSERVABILITY_ROUTES[0]} | ${SKELETON_VARIANTS[0]}
${'explore'} | ${OBSERVABILITY_ROUTES[1]} | ${SKELETON_VARIANTS[1]}
${'manage dashboards'} | ${OBSERVABILITY_ROUTES[2]} | ${SKELETON_VARIANTS[2]}
${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANTS[0]}
`('renders the $variant skeleton variant for $pathDescription path', ({ path, variant }) => {
mountComponent({ ...$route, path });
const props = wrapper.findComponent(ObservabilitySkeleton).props();
expect(props.variant).toBe(variant);
});
});
describe('on observability ui unmount', () => {
it('should remove message event and should not call replace method from vue router', () => {
describe('on unmount', () => {
it('should not emit any even on route update', () => {
mountComponent();
wrapper.destroy();
// testing event cleanup logic, should not call on messege event after component is destroyed
dispatchMessageEvent({
data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url: '/explore' } },
origin: 'https://observe.gitlab.com',
});
expect(replace).not.toHaveBeenCalled();
expect(wrapper.emitted('route-update')).toBeUndefined();
});
});
});

View File

@ -6,8 +6,13 @@ import Skeleton from '~/observability/components/skeleton/index.vue';
import DashboardsSkeleton from '~/observability/components/skeleton/dashboards.vue';
import ExploreSkeleton from '~/observability/components/skeleton/explore.vue';
import ManageSkeleton from '~/observability/components/skeleton/manage.vue';
import EmbedSkeleton from '~/observability/components/skeleton/embed.vue';
import { SKELETON_VARIANTS_BY_ROUTE, DEFAULT_TIMERS } from '~/observability/constants';
import {
SKELETON_VARIANTS_BY_ROUTE,
DEFAULT_TIMERS,
SKELETON_VARIANT_EMBED,
} from '~/observability/constants';
describe('Skeleton component', () => {
let wrapper;
@ -22,6 +27,8 @@ describe('Skeleton component', () => {
const findManageSkeleton = () => wrapper.findComponent(ManageSkeleton);
const findEmbedSkeleton = () => wrapper.findComponent(EmbedSkeleton);
const findAlert = () => wrapper.findComponent(GlAlert);
const mountComponent = ({ ...props } = {}) => {
@ -97,16 +104,20 @@ describe('Skeleton component', () => {
${'dashboards'} | ${'variant is dashboards'} | ${SKELETON_VARIANTS[0]}
${'explore'} | ${'variant is explore'} | ${SKELETON_VARIANTS[1]}
${'manage'} | ${'variant is manage'} | ${SKELETON_VARIANTS[2]}
${'embed'} | ${'variant is embed'} | ${SKELETON_VARIANT_EMBED}
${'default'} | ${'variant is not manage, dashboards or explore'} | ${'unknown'}
`('should render $skeletonType skeleton if $condition', async ({ skeletonType, variant }) => {
mountComponent({ variant });
jest.advanceTimersByTime(DEFAULT_TIMERS.CONTENT_WAIT_MS);
await nextTick();
const showsDefaultSkeleton = !SKELETON_VARIANTS.includes(variant);
const showsDefaultSkeleton = ![...SKELETON_VARIANTS, SKELETON_VARIANT_EMBED].includes(
variant,
);
expect(findDashboardsSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[0]);
expect(findExploreSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[1]);
expect(findManageSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[2]);
expect(findEmbedSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANT_EMBED);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(showsDefaultSkeleton);
});

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe AccessTokenValidationService do
RSpec.describe AccessTokenValidationService, feature_category: :system_access do
describe ".include_any_scope?" do
let(:request) { double("request") }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe AuditEventService, :with_license do
RSpec.describe AuditEventService, :with_license, feature_category: :audit_events do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, :with_sign_ins) }
let_it_be(:project_member) { create(:project_member, user: user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe AutoMergeService do
RSpec.describe AutoMergeService, feature_category: :code_review_workflow do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BaseContainerService do
RSpec.describe BaseContainerService, feature_category: :container_registry do
let(:project) { Project.new }
let(:user) { User.new }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching do
RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching, feature_category: :shared do
let(:service) { described_class.new }
describe '#relation_for_count' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BulkCreateIntegrationService do
RSpec.describe BulkCreateIntegrationService, feature_category: :integrations do
include JiraIntegrationHelpers
before_all do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BulkPushEventPayloadService do
RSpec.describe BulkPushEventPayloadService, feature_category: :source_code_management do
let(:event) { create(:push_event) }
let(:push_data) do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BulkUpdateIntegrationService do
RSpec.describe BulkUpdateIntegrationService, feature_category: :integrations do
include JiraIntegrationHelpers
before_all do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe CohortsService do
RSpec.describe CohortsService, feature_category: :shared do
describe '#execute' do
def month_start(months_ago)
months_ago.months.ago.beginning_of_month.to_date

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe CompareService do
RSpec.describe CompareService, feature_category: :source_code_management do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, 'feature') }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state do
RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, feature_category: :service_ping do
include SnowplowHelpers
let(:service) { described_class.new }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe GravatarService do
RSpec.describe GravatarService, feature_category: :user_profile do
describe '#execute' do
let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ImportExportCleanUpService do
RSpec.describe ImportExportCleanUpService, feature_category: :importers do
describe '#execute' do
let(:service) { described_class.new }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe MarkdownContentRewriterService do
RSpec.describe MarkdownContentRewriterService, feature_category: :team_planning do
let_it_be(:user) { create(:user) }
let_it_be(:source_parent) { create(:project, :public) }
let_it_be(:target_parent) { create(:project, :public) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe NoteSummary do
RSpec.describe NoteSummary, feature_category: :code_review_workflow do
let(:project) { build(:project) }
let(:noteable) { build(:issue) }
let(:user) { build(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe PostReceiveService do
RSpec.describe PostReceiveService, feature_category: :team_planning do
include GitlabShellHelpers
include Gitlab::Routing

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe PreviewMarkdownService do
RSpec.describe PreviewMarkdownService, feature_category: :team_planning do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe PushEventPayloadService do
RSpec.describe PushEventPayloadService, feature_category: :source_code_management do
let(:event) { create(:push_event) }
describe '#execute' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe RepositoryArchiveCleanUpService do
RSpec.describe RepositoryArchiveCleanUpService, feature_category: :source_code_management do
subject(:service) { described_class.new }
describe '#execute (new archive locations)' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ResetProjectCacheService do
RSpec.describe ResetProjectCacheService, feature_category: :projects do
let(:project) { create(:project) }
let(:user) { create(:user) }

View File

@ -7,7 +7,7 @@ require 're2'
require_relative '../../app/services/service_response'
require_relative '../../lib/gitlab/error_tracking'
RSpec.describe ServiceResponse do
RSpec.describe ServiceResponse, feature_category: :shared do
describe '.success' do
it 'creates a successful response without a message' do
expect(described_class.success).to be_success

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe SystemHooksService do
RSpec.describe SystemHooksService, feature_category: :webhooks do
describe '#execute_hooks_for' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TaskListToggleService do
RSpec.describe TaskListToggleService, feature_category: :team_planning do
let(:markdown) do
<<-EOT.strip_heredoc
* [ ] Task 1

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TasksToBeDone::BaseService do
RSpec.describe TasksToBeDone::BaseService, feature_category: :team_planning do
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:assignee_one) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Terraform::RemoteStateHandler do
RSpec.describe Terraform::RemoteStateHandler, feature_category: :infrastructure_as_code do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Terraform::States::DestroyService do
RSpec.describe Terraform::States::DestroyService, feature_category: :infrastructure_as_code do
let_it_be(:state) { create(:terraform_state, :with_version, :deletion_in_progress) }
let(:file) { instance_double(Terraform::StateUploader, relative_path: 'path') }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Terraform::States::TriggerDestroyService do
RSpec.describe Terraform::States::TriggerDestroyService, feature_category: :infrastructure_as_code do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, maintainer_projects: [project]) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TestHooks::ProjectService do
RSpec.describe TestHooks::ProjectService, feature_category: :code_testing do
include AfterNextHelpers
let(:current_user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TestHooks::SystemService do
RSpec.describe TestHooks::SystemService, feature_category: :code_testing do
include AfterNextHelpers
describe '#execute' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Timelogs::DeleteService do
RSpec.describe Timelogs::DeleteService, feature_category: :team_planning do
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TodoService do
RSpec.describe TodoService, feature_category: :team_planning do
include AfterNextHelpers
let_it_be(:project) { create(:project, :repository) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::AllowedTargetFilterService do
RSpec.describe Todos::AllowedTargetFilterService, feature_category: :team_planning do
include DesignManagementTestHelpers
let_it_be(:authorized_group) { create(:group, :private) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::ConfidentialIssueService do
RSpec.describe Todos::Destroy::ConfidentialIssueService, feature_category: :team_planning do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:author) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::DesignService do
RSpec.describe Todos::Destroy::DesignService, feature_category: :design_management do
let_it_be(:user) { create(:user) }
let_it_be(:user_2) { create(:user) }
let_it_be(:design) { create(:design) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::DestroyedIssuableService do
RSpec.describe Todos::Destroy::DestroyedIssuableService, feature_category: :team_planning do
describe '#execute' do
let_it_be(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::ProjectPrivateService do
RSpec.describe Todos::Destroy::ProjectPrivateService, feature_category: :team_planning do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, group: group) }
let(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Todos::Destroy::UnauthorizedFeaturesService do
RSpec.describe Todos::Destroy::UnauthorizedFeaturesService, feature_category: :team_planning do
let_it_be(:project, reload: true) { create(:project, :public, :repository) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:mr) { create(:merge_request, source_project: project) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Topics::MergeService do
RSpec.describe Topics::MergeService, feature_category: :shared do
let_it_be(:source_topic) { create(:topic, name: 'source_topic') }
let_it_be(:target_topic) { create(:topic, name: 'target_topic') }
let_it_be(:project_1) { create(:project, :public, topic_list: source_topic.name) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe TwoFactor::DestroyService do
RSpec.describe TwoFactor::DestroyService, feature_category: :system_access do
let_it_be(:current_user) { create(:user) }
subject { described_class.new(current_user, user: user).execute }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe UpdateContainerRegistryInfoService do
RSpec.describe UpdateContainerRegistryInfoService, feature_category: :container_registry do
let_it_be(:application_settings) { Gitlab::CurrentSettings }
let_it_be(:api_url) { 'http://registry.gitlab' }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe MergeRequestMetricsService do
RSpec.describe MergeRequestMetricsService, feature_category: :code_review_workflow do
let(:metrics) { create(:merge_request).metrics }
describe '#merge' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe UploadService do
RSpec.describe UploadService, feature_category: :shared do
describe 'File service' do
before do
@user = create(:user)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Uploads::DestroyService do
RSpec.describe Uploads::DestroyService, feature_category: :shared do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:upload) { create(:upload, :issuable_upload, model: project) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe UserPreferences::UpdateService do
RSpec.describe UserPreferences::UpdateService, feature_category: :user_profile do
let(:user) { create(:user) }
let(:params) { { view_diffs_file_by_file: false } }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::ActivityService do
RSpec.describe Users::ActivityService, feature_category: :user_profile do
include ExclusiveLeaseHelpers
let(:user) { create(:user, last_activity_on: last_activity_on) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::ApproveService do
RSpec.describe Users::ApproveService, feature_category: :user_management do
let_it_be(:current_user) { create(:admin) }
let(:user) { create(:user, :blocked_pending_approval) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::AuthorizedBuildService do
RSpec.describe Users::AuthorizedBuildService, feature_category: :user_management do
describe '#execute' do
let_it_be(:current_user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::BanService do
RSpec.describe Users::BanService, feature_category: :user_management do
let(:user) { create(:user) }
let_it_be(:current_user) { create(:admin) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::BannedUserBaseService do
RSpec.describe Users::BannedUserBaseService, feature_category: :user_management do
let(:admin) { create(:admin) }
let(:base_service) { described_class.new(admin) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::BatchStatusCleanerService do
RSpec.describe Users::BatchStatusCleanerService, feature_category: :user_management do
let_it_be(:user_status_1) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 1.year.ago) }
let_it_be(:user_status_2) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 1.year.from_now) }
let_it_be(:user_status_3) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 2.years.ago) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::BlockService do
RSpec.describe Users::BlockService, feature_category: :user_management do
let_it_be(:current_user) { create(:admin) }
subject(:service) { described_class.new(current_user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::BuildService do
RSpec.describe Users::BuildService, feature_category: :user_management do
using RSpec::Parameterized::TableSyntax
describe '#execute' do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::CreateService do
RSpec.describe Users::CreateService, feature_category: :user_management do
describe '#execute' do
let(:password) { User.random_password }
let(:admin_user) { create(:admin) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::DestroyService do
RSpec.describe Users::DestroyService, feature_category: :user_management do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
let!(:namespace) { user.namespace }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::DismissCalloutService do
RSpec.describe Users::DismissCalloutService, feature_category: :user_management do
describe '#execute' do
let_it_be(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::DismissGroupCalloutService do
RSpec.describe Users::DismissGroupCalloutService, feature_category: :user_management do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::DismissProjectCalloutService do
RSpec.describe Users::DismissProjectCalloutService, feature_category: :user_management do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::EmailVerification::GenerateTokenService do
RSpec.describe Users::EmailVerification::GenerateTokenService, feature_category: :system_access do
using RSpec::Parameterized::TableSyntax
let(:service) { described_class.new(attr: attr) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::EmailVerification::ValidateTokenService, :clean_gitlab_redis_rate_limiting do
RSpec.describe Users::EmailVerification::ValidateTokenService, :clean_gitlab_redis_rate_limiting, feature_category: :system_access do
using RSpec::Parameterized::TableSyntax
let(:service) { described_class.new(attr: attr, user: user, token: token) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::InProductMarketingEmailRecords do
RSpec.describe Users::InProductMarketingEmailRecords, feature_category: :onboarding do
let_it_be(:user) { create :user }
subject(:records) { described_class.new }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching, feature_category: :system_access do
let(:user) { create(:user) }
subject { described_class.new(user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::LastPushEventService do
RSpec.describe Users::LastPushEventService, feature_category: :source_code_management do
let(:user) { build(:user, id: 1) }
let(:project) { build(:project, id: 2) }
let(:event) { build(:push_event, id: 3, author: user, project: project) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::MigrateRecordsToGhostUserInBatchesService do
RSpec.describe Users::MigrateRecordsToGhostUserInBatchesService, feature_category: :user_management do
let(:service) { described_class.new }
let_it_be(:ghost_user_migration) { create(:ghost_user_migration) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::MigrateRecordsToGhostUserService do
RSpec.describe Users::MigrateRecordsToGhostUserService, feature_category: :user_management do
include BatchDestroyDependentAssociationsHelper
let!(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::RefreshAuthorizedProjectsService do
RSpec.describe Users::RefreshAuthorizedProjectsService, feature_category: :user_management do
include ExclusiveLeaseHelpers
# We're using let! here so that any expectations for the service class are not

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::RegistrationsBuildService do
RSpec.describe Users::RegistrationsBuildService, feature_category: :system_access do
describe '#execute' do
let(:base_params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
let(:skip_param) { {} }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::RejectService do
RSpec.describe Users::RejectService, feature_category: :user_management do
let_it_be(:current_user) { create(:admin) }
let(:user) { create(:user, :blocked_pending_approval) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::RepairLdapBlockedService do
RSpec.describe Users::RepairLdapBlockedService, feature_category: :system_access do
let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:identity) { user.ldap_identity }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::RespondToTermsService do
RSpec.describe Users::RespondToTermsService, feature_category: :user_profile do
let(:user) { create(:user) }
let(:term) { create(:term) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::SavedReplies::CreateService do
RSpec.describe Users::SavedReplies::CreateService, feature_category: :team_planning do
describe '#execute' do
let_it_be(:current_user) { create(:user) }
let_it_be(:saved_reply) { create(:saved_reply, user: current_user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Users::SavedReplies::DestroyService do
RSpec.describe Users::SavedReplies::DestroyService, feature_category: :team_planning do
describe '#execute' do
let!(:saved_reply) { create(:saved_reply) }

Some files were not shown because too many files have changed in this diff Show More