Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-05-28 21:13:37 +00:00
parent 3eeaac66d3
commit 8bee002871
94 changed files with 1266 additions and 248 deletions

View File

@ -73,19 +73,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/requests/api/deployments_spec.rb'
- 'ee/spec/requests/api/dora/metrics_spec.rb'
- 'ee/spec/requests/api/epics_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/security_policy/assign_security_policy_project_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/security_policy/create_security_policy_project_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/security_policy/unassign_security_policy_project_spec.rb'
- 'ee/spec/requests/api/group_boards_spec.rb'
- 'ee/spec/requests/api/group_clusters_spec.rb'
- 'ee/spec/requests/api/group_push_rule_spec.rb'
- 'ee/spec/requests/api/groups_spec.rb'
- 'ee/spec/requests/api/internal/base_spec.rb'
- 'ee/spec/requests/api/invitations_spec.rb'
- 'ee/spec/requests/api/issue_links_spec.rb'
- 'ee/spec/requests/api/issues_spec.rb'
- 'ee/spec/requests/api/members_spec.rb'
- 'ee/spec/requests/api/merge_trains_spec.rb'
- 'ee/spec/requests/api/namespaces_spec.rb'
- 'ee/spec/requests/api/project_clusters_spec.rb'
- 'ee/spec/requests/api/project_push_rule_spec.rb'

View File

@ -62,21 +62,8 @@ Lint/SymbolConversion:
- 'ee/spec/models/concerns/elastic/repository_spec.rb'
- 'ee/spec/models/plan_spec.rb'
- 'ee/spec/models/project_feature_spec.rb'
- 'ee/spec/requests/api/analytics/product_analytics_spec.rb'
- 'ee/spec/requests/api/dependency_proxy/packages/npm_spec.rb'
- 'ee/spec/requests/api/graphql/audit_events/streaming/headers/create_spec.rb'
- 'ee/spec/requests/api/graphql/audit_events/streaming/headers/destroy_spec.rb'
- 'ee/spec/requests/api/graphql/audit_events/streaming/headers/update_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/audit_events/external_audit_event_destinations/create_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/audit_events/external_audit_event_destinations/destroy_spec.rb'
- 'ee/spec/requests/api/graphql/mutations/audit_events/external_audit_event_destinations/update_spec.rb'
- 'ee/spec/requests/api/scim/group_scim_spec.rb'
- 'ee/spec/requests/api/scim/instance_scim_spec.rb'
- 'ee/spec/services/elastic/data_migration_service_spec.rb'
- 'ee/spec/services/phone_verification/users/send_verification_code_service_spec.rb'
- 'ee/spec/services/security/security_orchestration_policies/scan_pipeline_service_spec.rb'
- 'ee/spec/services/security/token_revocation_service_spec.rb'
- 'ee/spec/support/helpers/subscription_portal_helpers.rb'
- 'ee/spec/support/matchers/ee/epic_aggregate_matchers.rb'
- 'ee/spec/support/shared_examples/google_cloud_platform/artifact_registry/services_shared_examples.rb'
- 'ee/spec/support/shared_examples/status_page/image_post_process_examples.rb'

View File

@ -99,7 +99,7 @@ export default {
:items="messages"
:fields="$options.fields"
:tbody-tr-attr="{ 'data-testid': 'message-row' }"
class="-gl-mt-1 gl-mb-n2"
class="-gl-mt-1 -gl-mb-2"
stacked="md"
>
<template #cell(preview)="{ item: { message, theme, broadcast_type, dismissable } }">

View File

@ -217,7 +217,7 @@ export default {
:fields="$options.fields"
stacked="md"
data-testid="deploy-keys-list"
class="-gl-mt-1 gl-mb-n2"
class="-gl-mt-1 -gl-mb-2"
>
<template #table-busy>
<gl-loading-icon size="sm" class="gl-my-5" />

View File

@ -180,7 +180,7 @@ export default {
</template>
<template #cell(actions)="{ item }">
<gl-button-group class="gl-ml-3 -gl-mt-2 gl-mb-n2">
<gl-button-group class="gl-ml-3 -gl-mt-2 -gl-mb-2">
<gl-button
icon="settings"
:aria-label="$options.i18n.editIntegration"

View File

@ -347,7 +347,7 @@ export default {
</span>
</span>
</div>
<div class="board-card-assignee gl-display-flex gl-mb-n2">
<div class="board-card-assignee gl-display-flex -gl-mb-2">
<user-avatar-link
v-for="assignee in cappedAssignees"
:key="assignee.id"
@ -368,7 +368,7 @@ export default {
v-if="shouldRenderCounter"
v-gl-tooltip
:title="assigneeCounterTooltip"
class="avatar-counter gl-bg-gray-100 gl-text-gray-900 gl-cursor-help gl-font-weight-bold gl-border-0 gl-leading-24 gl-ml-n3"
class="avatar-counter gl-bg-gray-100 gl-text-gray-900 gl-cursor-help gl-font-weight-bold gl-border-0 gl-leading-24 -gl-ml-3"
data-placement="bottom"
>{{ assigneeCounterLabel }}</span
>

View File

@ -473,7 +473,7 @@ export default {
</gl-form-group>
<gl-form-group class="gl-border-none -gl-mb-3">
<template #label>
<div class="gl-mb-n3">
<div class="-gl-mb-3">
{{ $options.i18n.visibility }}
</div>
</template>

View File

@ -123,7 +123,7 @@ export default {
hide-tooltip
/>
<div class="gl-font-weight-100 gl-font-size-lg -gl-ml-4 gl-align-self-center">
<div class="gl-font-size-lg -gl-ml-4 gl-align-self-center">
{{ group.size }}
</div>
</div>

View File

@ -172,7 +172,7 @@ export default {
v-if="node.attrs.showPreview"
:contenteditable="false"
data-testid="sandbox-preview"
class="!-gl-mt-3 gl-ml-n4! !-gl-mr-4 gl-mb-3 gl-bg-white! gl-p-4 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
class="!-gl-mt-3 !-gl-ml-4 !-gl-mr-4 gl-mb-3 gl-bg-white! gl-p-4 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
<sandboxed-mermaid v-if="node.attrs.language === 'mermaid'" :source="diagramSource" />
<img v-else ref="diagramContainer" :src="diagramUrl" />

View File

@ -127,7 +127,7 @@ export default {
{{ item.cronTimezone.formattedTimezone }}
</template>
<template #cell(actions)="{ item }">
<div class="gl-display-flex gl-justify-content-end -gl-mt-2 gl-mb-n2">
<div class="gl-display-flex gl-justify-content-end -gl-mt-2 -gl-mb-2">
<gl-button
v-gl-modal.deploy-freeze-modal
icon="pencil"

View File

@ -371,7 +371,7 @@ export default {
<template v-if="discussion.resolvable" #resolve-checkbox>
<gl-form-checkbox
v-model="shouldChangeResolvedStatus"
class="gl-mt-5 gl-mb-n3"
class="gl-mt-5 -gl-mb-3"
data-testid="resolve-checkbox"
>
{{ resolveCheckboxText }}

View File

@ -392,7 +392,7 @@ export default {
v-if="isReviewable && showLocalFileReviews"
v-gl-tooltip.hover.focus.left
data-testid="fileReviewCheckbox"
class="gl-mr-5 gl-mb-n3 gl-display-flex gl-align-items-center"
class="gl-mr-5 -gl-mb-3 gl-display-flex gl-align-items-center"
:title="$options.i18n.fileReviewTooltip"
:checked="reviewed"
@change="toggleReview"

View File

@ -44,7 +44,7 @@ export default {
<template>
<div>
<gl-button-group class="gl-flex-direction-column gl-md-flex-direction-row gl-ml-n6">
<gl-button-group class="gl-flex-direction-column gl-md-flex-direction-row -gl-ml-6">
<gl-button
:key="ignoreBtn.status"
:ref="`${ignoreBtn.title.toLowerCase()}Error`"

View File

@ -51,7 +51,7 @@ export default {
<h3 class="gl-new-card-title">{{ $options.i18n.activeIntegrationsHeading }}</h3>
</template>
<integrations-table
class="gl-mb-n2"
class="-gl-mb-2"
:integrations="integrationsGrouped.active"
:empty-text="$options.i18n.activeTableEmptyText"
show-updated-at
@ -67,7 +67,7 @@ export default {
<h3 class="gl-new-card-title">{{ $options.i18n.inactiveIntegrationsHeading }}</h3>
</template>
<integrations-table
class="gl-mb-n2"
class="-gl-mb-2"
inactive
:integrations="integrationsGrouped.inactive"
:empty-text="$options.i18n.inactiveTableEmptyText"

View File

@ -4,7 +4,7 @@ import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import EmptyState from '../components/empty_state.vue';
import EmptyState from '../components/model_list_empty_state.vue';
import * as i18n from '../translations';
import { BASE_SORT_FIELDS, MODEL_ENTITIES } from '../constants';
import ModelRow from '../components/model_row.vue';
@ -71,6 +71,7 @@ export default {
errorMessage: '',
skipQueries: true,
queryVariables: {},
createModelVisible: false,
};
},
computed: {
@ -127,7 +128,12 @@ export default {
<metadata-item icon="machine-learning" :text="$options.i18n.modelsCountLabel(count)" />
</template>
<template #right-actions>
<model-create v-if="canWriteModelRegistry" />
<model-create
v-if="canWriteModelRegistry"
:create-model-visible="createModelVisible"
@show-create-model="createModelVisible = true"
@hide-create-model="createModelVisible = false"
/>
<actions-dropdown />
</template>
@ -142,7 +148,7 @@ export default {
@fetch-page="fetchPage"
>
<template #empty-state>
<empty-state :entity-type="$options.modelEntity" />
<empty-state @open-create-model="createModelVisible = true" />
</template>
<template #item="{ item }">

View File

@ -28,13 +28,18 @@ export default {
ImportArtifactZone: () => import('./import_artifact_zone.vue'),
},
inject: ['projectPath'],
props: {
createModelVisible: {
type: Boolean,
required: true,
},
},
data() {
return {
name: null,
version: null,
description: null,
versionDescription: null,
modalVisible: false,
errorMessage: null,
selectedFile: null,
modelData: null,
@ -107,10 +112,10 @@ export default {
}
},
showCreateModal() {
this.modalVisible = true;
this.$emit('show-create-model');
},
cancelModal() {
this.modalVisible = false;
this.$emit('hide-create-model');
},
hideAlert() {
this.errorMessage = null;
@ -158,13 +163,14 @@ export default {
<div>
<gl-button @click="showCreateModal">{{ $options.modal.buttonTitle }}</gl-button>
<gl-modal
v-model="modalVisible"
modal-id="create-model-modal"
:visible="createModelVisible"
:title="$options.modal.title"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
size="sm"
@primary="create"
@hide="cancelModal"
@cancel="cancelModal"
>
<gl-form>

View File

@ -0,0 +1,91 @@
<script>
import { GlEmptyState, GlButton } from '@gitlab/ui';
import emptySvgUrl from '@gitlab/svgs/dist/illustrations/empty-state/empty-dag-md.svg?url';
import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
components: {
GlEmptyState,
ClipboardButton,
GlButton,
},
inject: ['mlflowTrackingUrl'],
title: s__('MlModelRegistry|No models registered'),
description: s__(
'MlModelRegistry|Import your machine learning using GitLab directly or using the MLflow client:',
),
createNew: s__('MlModelRegistry|Create model'),
mlflowDocs: s__('MlModelRegistry|MLflow compatibility'),
helpPath: helpPagePath('user/project/ml/model_registry/index', {
anchor: 'creating-machine-learning-models-and-model-versions',
}),
emptySvgPath: emptySvgUrl,
computed: {
mlflowCommand() {
return [
// eslint-disable-next-line @gitlab/require-i18n-strings
'import os',
'from mlflow import MlflowClient',
'',
`os.environ["MLFLOW_TRACKING_URI"] = "${this.mlflowTrackingUrl}"`,
'os.environ["MLFLOW_TRACKING_TOKEN"] = <your_gitlab_token>',
'',
s__('MlModelRegistry|# Create a model'),
'client = MlflowClient()',
"model_name = '<your_model_name>'",
// eslint-disable-next-line @gitlab/require-i18n-strings
"description = 'Model description'",
'model = client.create_registered_model(model_name, description=description)',
'',
s__('MlModelRegistry|# Create a version'),
'tags = { "gitlab.version": version }',
'model_version = client.create_model_version(model_name, version, tags=tags)',
'',
s__('MlModelRegistry|# Log artifacts'),
'client.log_artifact(run_id, \'<local/path/to/file.txt>\', artifact_path="")',
].join('\n');
},
},
methods: {
emitOpenCreateModel() {
this.$emit('open-create-model');
},
},
};
</script>
<template>
<gl-empty-state
:title="$options.title"
:svg-path="$options.emptySvgPath"
:svg-height="null"
class="gl-py-8"
>
<template #description>
<p>{{ $options.description }}</p>
<pre
class="code highlight gl-flex gl-border-none gl-text-left gl-p-2"
data-testid="preview-code"
>
<code>{{ mlflowCommand }}</code>
<clipboard-button
category="tertiary"
:text="mlflowCommand"
class="gl-self-start"
:title="__('Copy')"
/>
</pre>
</template>
<template #actions>
<gl-button variant="confirm" class="gl-mx-2 gl-mb-3" @click="emitOpenCreateModel">{{
$options.createNew
}}</gl-button>
<gl-button class="gl-mb-3 gl-mr-3 gl-mx-2" :href="$options.helpPath"
>{{ $options.mlflowDocs }}
</gl-button>
</template>
</gl-empty-state>
</template>

View File

@ -232,7 +232,7 @@ export default {
:aria-label="$options.i18n.SEARCH_OR_COMMAND_MODE_PLACEHOLDER"
class="gl-relative gl-rounded-lg gl-w-full gl-pb-0"
>
<div class="input-box-wrapper gl-bg-white gl-border-b gl-mb-n1 gl-p-2">
<div class="input-box-wrapper gl-bg-white gl-border-b -gl-mb-1 gl-p-2">
<gl-search-box-by-type
id="search"
ref="searchInput"

View File

@ -2,20 +2,36 @@
import { GlDisclosureDropdownGroup } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapGetters } from 'vuex';
import { InternalEvents } from '~/tracking';
import { ALL_GITLAB } from '~/vue_shared/global_search/constants';
import { OVERLAY_GOTO } from '../command_palette/constants';
import {
OVERLAY_GOTO,
ISSUES_ASSIGNED_TO_ME_TITLE,
ISSUES_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_THAT_I_AM_A_REVIEWER,
MERGE_REQUESTS_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_ASSIGNED_TO_ME_TITLE,
} from '~/super_sidebar/components/global_search/command_palette/constants';
import SearchResultHoverLayover from './global_search_hover_overlay.vue';
const trackingMixin = InternalEvents.mixin();
export default {
name: 'DefaultIssuables',
i18n: {
ALL_GITLAB,
OVERLAY_GOTO,
ISSUES_ASSIGNED_TO_ME_TITLE,
ISSUES_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_THAT_I_AM_A_REVIEWER,
MERGE_REQUESTS_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_ASSIGNED_TO_ME_TITLE,
},
components: {
GlDisclosureDropdownGroup,
SearchResultHoverLayover,
},
mixins: [trackingMixin],
computed: {
...mapState(['searchContext']),
...mapGetters(['defaultSearchOptions']),
@ -46,11 +62,46 @@ export default {
this.$emit('nothing-to-render');
}
},
methods: {
trackingTypes({ text }) {
switch (text) {
case this.$options.i18n.ISSUES_ASSIGNED_TO_ME_TITLE: {
this.trackEvent('click_issues_assigned_to_me_in_command_palette');
break;
}
case this.$options.i18n.ISSUES_I_HAVE_CREATED_TITLE: {
this.trackEvent('click_issues_i_created_in_command_palette');
break;
}
case this.$options.i18n.MERGE_REQUESTS_ASSIGNED_TO_ME_TITLE: {
this.trackEvent('click_merge_requests_assigned_to_me_in_command_palette');
break;
}
case this.$options.i18n.MERGE_REQUESTS_THAT_I_AM_A_REVIEWER: {
this.trackEvent('click_merge_requests_that_im_a_reviewer_in_command_palette');
break;
}
case this.$options.i18n.MERGE_REQUESTS_I_HAVE_CREATED_TITLE: {
this.trackEvent('click_merge_requests_i_created_in_command_palette');
break;
}
default: {
break;
}
}
},
},
};
</script>
<template>
<gl-disclosure-dropdown-group v-if="shouldRender" v-bind="$attrs" :group="group">
<gl-disclosure-dropdown-group
v-if="shouldRender"
v-bind="$attrs"
:group="group"
@action="trackingTypes"
>
<template #list-item="{ item }">
<search-result-hover-layover :text-message="$options.i18n.OVERLAY_GOTO">
<span>{{ item.text }}</span>

View File

@ -46,7 +46,7 @@ module Groups
@children = GroupDescendantsFinder.new(
current_user: current_user,
parent_group: parent,
params: group_descendants_params
params: safe_params
).execute.page(params[:page])
end

View File

@ -58,17 +58,9 @@ module AutoMerge
def available_for?(merge_request)
strong_memoize("available_for_#{merge_request.id}") do
if Feature.enabled?(:refactor_auto_merge, merge_request.project, type: :gitlab_com_derisk)
merge_request.can_be_merged_by?(current_user) &&
merge_request.mergeability_checks_pass?(**skippable_available_for_checks(merge_request)) &&
yield
else
merge_request.can_be_merged_by?(current_user) &&
merge_request.open? &&
!merge_request.broken? &&
overrideable_available_for_checks(merge_request) &&
yield
end
merge_request.can_be_merged_by?(current_user) &&
merge_request.mergeability_checks_pass?(**skippable_available_for_checks(merge_request)) &&
yield
end
end
@ -81,12 +73,6 @@ module AutoMerge
)
end
def overrideable_available_for_checks(merge_request)
!merge_request.draft? &&
merge_request.mergeable_discussions_state? &&
!merge_request.merge_blocked_by_other_mrs?
end
# Overridden in child classes
def notify(merge_request)
end

View File

@ -1,5 +1,5 @@
%div{ class: 'top-bar-fixed container-fluid', data: { testid: 'top-bar' } }
.top-bar-container.gl-display-flex.gl-align-items-center.gl-gap-2
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'js-super-sidebar-toggle-expand super-sidebar-toggle gl-ml-n3', aria: { controls: 'super-sidebar', expanded: 'false', label: _('Primary navigation sidebar') } })
= render Pajamas::ButtonComponent.new(icon: 'sidebar', category: :tertiary, button_options: { class: 'js-super-sidebar-toggle-expand super-sidebar-toggle -gl-ml-3', aria: { controls: 'super-sidebar', expanded: 'false', label: _('Primary navigation sidebar') } })
= render "layouts/nav/breadcrumbs/breadcrumbs"
= render_if_exists "layouts/nav/ask_duo_button"

View File

@ -17,7 +17,7 @@
= render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
.gl-text-truncate.-gl-my-2.gl-ml-n2
.gl-text-truncate.-gl-my-2.-gl-ml-2
- if commit
= render 'projects/branches/commit', commit: commit, project: @project, class_name: 'gl-p-2'
- else

View File

@ -18,7 +18,7 @@
.item-contents.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-flex-grow-1.gl-min-h-7
.item-title.gl-display-flex.mb-xl-0.gl-min-w-0.gl-align-items-center
- if branch[:pipeline_status].present?
%span.-gl-mt-2.gl-mb-n2.gl-mr-3
%span.-gl-mt-2.-gl-mb-2.gl-mr-3
= render 'ci/status/icon', status: branch[:pipeline_status]
%span.related-branch-info
%strong

View File

@ -8,7 +8,7 @@
= form_tag network_path, method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: _("Git revision"), class: 'search-input form-control gl-form-input input-mx-250 search-sha gl-mr-2'
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, icon: 'search')
.form-group{ class: 'gl-ml-5 gl-mb-n3!' }
.form-group{ class: 'gl-ml-5 !gl-mb-3' }
= render Pajamas::CheckboxTagComponent.new(name: :filter_ref, checked: @options[:filter_ref]) do |c|
- c.with_label do
= _("Begin with the selected commit")

View File

@ -11,7 +11,7 @@
%li
= clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'gl-rounded-0!', size: :medium, hide_tooltip: true, hide_button_icon: true)
- unless is_current_user
.gl-ml-n2
.-gl-ml-2
.js-report-abuse-dropdown-item{ data: { report_abuse_path: add_category_abuse_reports_path, reported_user_id: note.author.id, reported_from_url: noteable_note_url(note) } }
- if note_editable
%li

View File

@ -35,7 +35,7 @@
- if @applications.any?
.table-holder
%table.table.b-table.gl-table.b-table-stacked-sm.-gl-mt-1.gl-mb-n2
%table.table.b-table.gl-table.b-table-stacked-sm.-gl-mt-1.-gl-mb-2
%thead.gl-hidden.md:gl-table-header-group
%tr
%th= _('Name')
@ -77,7 +77,7 @@
- c.with_body do
- if @authorized_tokens.any?
.table-holder
%table.table.b-table.gl-table.b-table-stacked-sm.-gl-mt-1.gl-mb-n2
%table.table.b-table.gl-table.b-table-stacked-sm.-gl-mt-1.-gl-mb-2
%thead.gl-hidden.md:gl-table-header-group
%tr
%th= _('Name')

View File

@ -45,7 +45,7 @@
= markdown_field(project, :description)
- if project.topics.any?
.gl-mt-3.gl-ml-n1
.gl-mt-3.-gl-ml-1
= render "shared/projects/topics", project: project.present(current_user: current_user)
- if project.catalog_resource
= render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(project.catalog_resource), css_class: 'gl-mt-2' }

View File

@ -3,7 +3,7 @@
- require_external_verification = Integrations::BeyondIdentity.activated_for_instance?
- if @gpg_keys.any?
.table-holder
%table.table.b-table.gl-table.b-table-stacked-md.-gl-mt-1.gl-mb-n2.ssh-keys-list
%table.table.b-table.gl-table.b-table-stacked-md.-gl-mt-1.-gl-mb-2.ssh-keys-list
%thead.gl-hidden.md:gl-table-header-group
%tr
%th= s_('Profiles|Key')

View File

@ -3,7 +3,7 @@
- if @keys.any?
.table-holder
%table.table.b-table.gl-table.b-table-stacked-md.-gl-mt-1.gl-mb-n2.ssh-keys-list{ data: { testid: 'ssh-keys-list' } }
%table.table.b-table.gl-table.b-table-stacked-md.-gl-mt-1.-gl-mb-2.ssh-keys-list{ data: { testid: 'ssh-keys-list' } }
%thead.gl-hidden.md:gl-table-header-group
%tr
%th= _('Title')

View File

@ -12,7 +12,7 @@
= link_to _('Edit file'), edit_blob_path(@user.user_project, @user.user_project.default_branch, @user.user_readme.path)
= render 'projects/blob/viewer', viewer: @user.user_readme.rich_viewer, load_async: false
.js-read-more-trigger.read-more-trigger.gl-h-8.gl-absolute.gl-z-2.gl-bg-white.gl-px-6.gl-rounded-bottom-base.gl-cursor-pointer
= render Pajamas::ButtonComponent.new(variant: :link, button_text_classes: 'gl-display-flex gl-align-items-center gl-gap-1', button_options: { class: 'gl-mt-4 gl-ml-n1', 'aria-label': _("Expand Readme") }) do
= render Pajamas::ButtonComponent.new(variant: :link, button_text_classes: 'gl-display-flex gl-align-items-center gl-gap-1', button_options: { class: 'gl-mt-4 -gl-ml-1', 'aria-label': _("Expand Readme") }) do
= sprite_icon('chevron-down', size: 14)
= _("Read more")

View File

@ -0,0 +1,19 @@
---
description: User clicks "issues assigned to me"
internal_events: true
action: click_issues_assigned_to_me_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,19 @@
---
description: User clicks "issues I created"
internal_events: true
action: click_issues_i_created_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,19 @@
---
description: User clicks "merge requests assigned to me"
internal_events: true
action: click_merge_requests_assigned_to_me_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,19 @@
---
description: User clicks "merge requests I created"
internal_events: true
action: click_merge_requests_i_created_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,19 @@
---
description: User clicks "merge reqeusts that I'm a reviewer"
internal_events: true
action: click_merge_requests_that_im_a_reviewer_in_command_palette
identifiers:
- namespace
- user
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -8,7 +8,7 @@ identifiers:
product_section: core_platform
product_stage: data_stores
product_group: global_search
milestone: '17.0'
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151645
distributions:
- ce

View File

@ -1,9 +0,0 @@
---
name: refactor_auto_merge
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/10874
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146153
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/443872
milestone: '16.11'
group: group::code review
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: use_ids_for_markdown_upload_urls
feature_issue_url: https://gitlab.com/gitlab-sirt/shared-incidents/incident_5281/-/work_items/6
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150939
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/461757
milestone: '17.0'
group: group::project management
type: gitlab_com_derisk
default_enabled: false

View File

@ -47,7 +47,6 @@ options:
- p_ci_templates_security_license_scanning
- p_ci_templates_security_coverage_fuzzing
- p_ci_templates_security_coverage_fuzzing_latest
- p_ci_templates_security_api_fuzzing_latest
- p_ci_templates_security_secure_binaries
- p_ci_templates_security_dast_api
- p_ci_templates_security_dast_api_latest
@ -56,6 +55,9 @@ options:
- p_ci_templates_security_dast_latest
- p_ci_templates_security_dependency_scanning
- p_ci_templates_security_api_fuzzing
- p_ci_templates_security_api_fuzzing_latest
- p_ci_templates_security_api_security
- p_ci_templates_security_api_security_latest
- p_ci_templates_security_dast
- p_ci_templates_security_api_discovery
- p_ci_templates_security_bas_latest
@ -170,6 +172,9 @@ options:
- p_ci_templates_implicit_security_dast_latest
- p_ci_templates_implicit_security_dependency_scanning
- p_ci_templates_implicit_security_api_fuzzing
- p_ci_templates_implicit_security_api_fuzzing_latest
- p_ci_templates_implicit_security_api_security
- p_ci_templates_implicit_security_api_security_latest
- p_ci_templates_implicit_security_dast
- p_ci_templates_implicit_security_sast_iac
- p_ci_templates_kaniko

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_api_security_latest_monthly
description: Monthly counts for API Security latest CI template
product_section: sec
product_stage: secure
product_group: dynamic_analysis
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147183
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_security_api_security_latest

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_api_security_monthly
description: Monthly counts for API Security CI template
product_section: sec
product_stage: secure
product_group: dynamic_analysis
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147183
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_security_api_security

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_issues_assigned_to_me_in_command_palette_monthly
description: Monthly count of unique users User clicks "issues assigned to me"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_issues_assigned_to_me_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_issues_i_created_in_command_palette_monthly
description: Monthly count of unique users User clicks "issues I created"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_issues_i_created_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_assigned_to_me_in_command_palette_monthly
description: Monthly count of unique users User clicks "merge requests assigned to me"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_assigned_to_me_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_i_created_in_command_palette_monthly
description: Monthly count of unique users User clicks "merge requests I created"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_i_created_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_that_im_a_reviewer_in_command_palette_monthly
description: Monthly count of unique users User clicks "merge reqeusts that I'm a reviewer"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_that_im_a_reviewer_in_command_palette
unique: user.id

View File

@ -7,7 +7,7 @@ product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.0'
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151645
time_frame: 28d
data_source: internal_events

View File

@ -47,7 +47,6 @@ options:
- p_ci_templates_security_license_scanning
- p_ci_templates_security_coverage_fuzzing
- p_ci_templates_security_coverage_fuzzing_latest
- p_ci_templates_security_api_fuzzing_latest
- p_ci_templates_security_secure_binaries
- p_ci_templates_security_dast_api
- p_ci_templates_security_dast_api_latest
@ -56,6 +55,9 @@ options:
- p_ci_templates_security_dast_latest
- p_ci_templates_security_dependency_scanning
- p_ci_templates_security_api_fuzzing
- p_ci_templates_security_api_fuzzing_latest
- p_ci_templates_security_api_security
- p_ci_templates_security_api_security_latest
- p_ci_templates_security_dast
- p_ci_templates_security_api_discovery
- p_ci_templates_security_bas_latest
@ -172,6 +174,9 @@ options:
- p_ci_templates_implicit_security_dast_latest
- p_ci_templates_implicit_security_dependency_scanning
- p_ci_templates_implicit_security_api_fuzzing
- p_ci_templates_implicit_security_api_fuzzing_latest
- p_ci_templates_implicit_security_api_security
- p_ci_templates_implicit_security_api_security_latest
- p_ci_templates_implicit_security_dast
- p_ci_templates_kaniko
- p_ci_templates_qualys_iac_security

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_api_security_latest_weekly
description: Weekly counts for API Security latest CI template
product_section: sec
product_stage: secure
product_group: dynamic_analysis
value_type: number
status: active
milestone: "17.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147183
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_security_api_security_latest

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_security_api_security_weekly
description: Weekly counts for API Security CI template
product_section: sec
product_stage: secure
product_group: dynamic_analysis
value_type: number
status: active
milestone: "17.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147183
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_security_api_security

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_issues_assigned_to_me_in_command_palette_weekly
description: Weekly count of unique users who click "issues assigned to me"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_issues_assigned_to_me_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_issues_i_created_in_command_palette_weekly
description: Weekly count of unique users who click "issues I created"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_issues_i_created_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_assigned_to_me_in_command_palette_weekly
description: Weekly count of unique users who click "merge requests assigned to me"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_assigned_to_me_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_i_created_in_command_palette_weekly
description: Weekly count of unique users who click "merge requests I created"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_i_created_in_command_palette
unique: user.id

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.count_distinct_user_id_from_click_merge_requests_that_im_a_reviewer_in_command_palette_weekly
description: Weekly count of unique users who click "merge reqeusts that I'm a reviewer"
product_section: core_platform
product_stage: data_stores
product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147919
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
events:
- name: click_merge_requests_that_im_a_reviewer_in_command_palette
unique: user.id

View File

@ -7,7 +7,7 @@ product_group: global_search
performance_indicator_type: []
value_type: number
status: active
milestone: '17.0'
milestone: '17.1'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151645
time_frame: 7d
data_source: internal_events

View File

@ -108,7 +108,8 @@ InitializerConnections.raise_if_new_database_connection do
constraints: {
model: /project|group/,
filename: %r{[^/]+}
}
},
as: 'banzai_upload'
get '/whats_new' => 'whats_new#index'

View File

@ -177,6 +177,27 @@ In exceptional cases, we may need to add a new bounded context to the list. This
- We are introducing a new product category that does not align with any existing bounded contexts.
- We are extracting a bounded context out of an existing one because it's too large and we want to decouple the two.
### GitLab/BoundedContexts and `config/bounded_contexts.yml` FAQ
1. **Is there ever a situation where the cop should be disabled?**
- The cop **should not** be disabled but it **could** be disabled temporarily if the offending class or module is part
of a cluster of classes that should otherwise be moved all together.
In this case you could disable the cop and create a follow-up issue to move all the classes at once.
1. **Is there a suggested timeline to get all of the existing code refactored into compliance?**
- We do not have a timeline defined but the quicker we consolidate code the more consistent it becomes.
1. **Do the bounded contexts apply for existing Sidekiq workers?**
- Existing workers would be already in the RuboCop TODO file so they do not raise offenses. However, they should
also be moved into the bounded context whenever possible.
Follow the Sidekiq [renaming worker](../development/sidekiq/compatibility_across_updates.md#renaming-worker-classes) guide.
1. **We are renaming a feature category and the `config/bounded_contexts.yml` references that. Is it safe to update?**
- Yes the file only expects that the feature categories mapped to bounded contexts are defined in `config/feature_categories.yml`
and nothing specifically depends on these values. This mapping is primarily for contributors to understand where features
may be living in the codebase.
## Distinguish domain code from generic code
The [guidelines above](#use-namespaces-to-define-bounded-contexts) refer primarily to the domain code.

View File

@ -7880,7 +7880,7 @@
canonical: |
<p><a href="groups-test-file">groups-test-file</a></p>
static: |-
<p data-sourcepos="1:1-1:45" dir="auto"><a data-sourcepos="1:1-1:45" href="/groups/glfm_group/-/uploads/groups-test-file" data-canonical-src="/uploads/groups-test-file" data-link="true" class="gfm">groups-test-file</a></p>
<p data-sourcepos="1:1-1:45" dir="auto"><a data-sourcepos="1:1-1:45" href="/-/group/66666/uploads/groups-test-file" data-canonical-src="/uploads/groups-test-file" data-link="true" class="gfm">groups-test-file</a></p>
08_03_00__gitlab_internal_extension_markdown__markdown_preview_api_request_overrides__002:
canonical: |
<p><a href="projects-test-file">projects-test-file</a></p>
@ -7910,14 +7910,14 @@
canonical: |
TODO: Write canonical HTML for this example
static: |-
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/groups/glfm_group/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img data-sourcepos="1:1-1:69" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" decoding="async" class="lazy gfm" data-src="/groups/glfm_group/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/-/group/66666/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img data-sourcepos="1:1-1:69" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" decoding="async" class="lazy gfm" data-src="/-/group/66666/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
wysiwyg: |-
<p dir="auto"><img src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" alt="test-file"></p>
08_04_02__gitlab_internal_extension_markdown__migrated_golden_master_examples__attachment_image_for_project__001:
canonical: |
TODO: Write canonical HTML for this example
static: |-
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/glfm_group/glfm_project/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img data-sourcepos="1:1-1:69" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" decoding="async" class="lazy gfm" data-src="/glfm_group/glfm_project/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/-/project/77777/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img data-sourcepos="1:1-1:69" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" decoding="async" class="lazy gfm" data-src="/-/project/77777/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
wysiwyg: |-
<p dir="auto"><img src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" alt="test-file"></p>
08_04_03__gitlab_internal_extension_markdown__migrated_golden_master_examples__attachment_image_for_project_wiki__001:
@ -7931,14 +7931,14 @@
canonical: |
TODO: Write canonical HTML for this example
static: |-
<p data-sourcepos="1:1-1:68" dir="auto"><a data-sourcepos="1:1-1:68" href="/groups/glfm_group/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
<p data-sourcepos="1:1-1:68" dir="auto"><a data-sourcepos="1:1-1:68" href="/-/group/66666/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip">test-file</a></p>
08_04_05__gitlab_internal_extension_markdown__migrated_golden_master_examples__attachment_link_for_project__001:
canonical: |
TODO: Write canonical HTML for this example
static: |-
<p data-sourcepos="1:1-1:68" dir="auto"><a data-sourcepos="1:1-1:68" href="/glfm_group/glfm_project/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
<p data-sourcepos="1:1-1:68" dir="auto"><a data-sourcepos="1:1-1:68" href="/-/project/77777/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip">test-file</a></p>
08_04_06__gitlab_internal_extension_markdown__migrated_golden_master_examples__attachment_link_for_project_wiki__001:

View File

@ -8,11 +8,20 @@ module API
expose :markdown_name, as: :alt
expose :secure_url, as: :url
expose :full_path do |uploader|
show_project_uploads_path(
uploader.model,
uploader.secret,
uploader.filename
)
if ::Feature.enabled?(:use_ids_for_markdown_upload_urls, uploader.model)
banzai_upload_path(
'project',
uploader.model.id,
uploader.secret,
uploader.filename
)
else
show_project_uploads_path(
uploader.model,
uploader.secret,
uploader.filename
)
end
end
expose :markdown_link, as: :markdown

View File

@ -32,9 +32,17 @@ module Banzai
path_parts = [unescape_and_scrub_uri(html_attr.value)]
if project
path_parts.unshift(relative_url_root, project.full_path)
if Feature.enabled?(:use_ids_for_markdown_upload_urls, project)
path_parts.unshift(relative_url_root, '-', 'project', project.id.to_s)
else
path_parts.unshift(relative_url_root, project.full_path)
end
elsif group
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
if Feature.enabled?(:use_ids_for_markdown_upload_urls, group)
path_parts.unshift(relative_url_root, '-', 'group', group.id.to_s)
else
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
end
else
path_parts.unshift(relative_url_root)
end

View File

@ -0,0 +1,62 @@
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Dast-API.gitlab-ci.yml
# To use this template, add the following to your .gitlab-ci.yml file:
#
# include:
# template: API-Security.gitlab-ci.yml
#
# You also need to add a `dast` stage to your `stages:` configuration. A sample configuration for DAST API:
#
# stages:
# - build
# - test
# - deploy
# - dast
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html
# Configure API Security scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/index.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
#
APISEC_VERSION: "5"
APISEC_IMAGE_SUFFIX: ""
APISEC_IMAGE: api-security
api_security:
stage: dast
image: $SECURE_ANALYZERS_PREFIX/$APISEC_IMAGE:$APISEC_VERSION$APISEC_IMAGE_SUFFIX
allow_failure: true
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH
script:
- /peach/analyzer-api-security
artifacts:
when: always
paths:
- gl-assets
- gl-api-security-report.json
- gl-*.log
reports:
dast: gl-api-security-report.json
# end

View File

@ -0,0 +1,75 @@
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Dast-API.latest.gitlab-ci.yml
# To use this template, add the following to your .gitlab-ci.yml file:
#
# include:
# template: API-Security.latest.gitlab-ci.yml
#
# You also need to add a `dast` stage to your `stages:` configuration. A sample configuration for DAST API:
#
# stages:
# - build
# - test
# - deploy
# - dast
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html
# Configure API Security scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/index.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
#
APISEC_VERSION: "5"
APISEC_IMAGE_SUFFIX: ""
APISEC_IMAGE: api-security
api_security:
stage: dast
image: $SECURE_ANALYZERS_PREFIX/$APISEC_IMAGE:$APISEC_VERSION$APISEC_IMAGE_SUFFIX
allow_failure: true
rules:
- if: $APISEC_DISABLED == 'true' || $APISEC_DISABLED == '1'
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == 'true' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
- if: $APISEC_DISABLED_FOR_DEFAULT_BRANCH == '1' &&
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
when: never
# Add the job to merge request pipelines if there's an open merge request.
- if: $CI_PIPELINE_SOURCE == "merge_request_event" &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
- if: $CI_OPEN_MERGE_REQUESTS
when: never
# Add the job to branch pipelines.
- if: $CI_COMMIT_BRANCH &&
$CI_GITLAB_FIPS_MODE == "true"
variables:
APISEC_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH
script:
- /peach/analyzer-api-security
artifacts:
when: always
paths:
- gl-assets
- gl-api-security-report.json
- gl-*.log
reports:
dast: gl-api-security-report.json
# end

View File

@ -90,15 +90,10 @@ module Gitlab
{ name: attributes[:action], time_framed?: true, filter: {} }
]
Gitlab::Usage::MetricDefinition.definitions.each_value do |metric_definition|
metric_definition.attributes[:events]&.each do |event_selection_rule|
if event_selection_rule[:name] == attributes[:action]
result << {
name: attributes[:action],
time_framed?: %w[7d 28d].include?(metric_definition.attributes[:time_frame]),
filter: event_selection_rule[:filter] || {}
}
end
matching_event_selection_rules = metric_definition.event_selection_rules.select do |event_selection_rule|
event_selection_rule[:name] == attributes[:action]
end
result.concat(matching_event_selection_rules)
end
result.uniq
end

View File

@ -39,7 +39,7 @@ module Gitlab
def instrumentation_object
instrumentation_class = "Gitlab::Usage::Metrics::Instrumentations::#{definition.instrumentation_class}"
@instrumentation_object ||= instrumentation_class.constantize.new(definition.attributes)
@instrumentation_object ||= instrumentation_class.constantize.new(definition.raw_attributes)
end
end
end

View File

@ -11,7 +11,6 @@ module Gitlab
InvalidError = Class.new(RuntimeError)
attr_reader :path
attr_reader :attributes
def initialize(path, opts = {})
@path = path
@ -19,28 +18,49 @@ module Gitlab
end
def key
attributes[:key_path]
@attributes[:key_path]
end
alias_method :key_path, :key
def events
events_from_new_structure || events_from_old_structure || {}
end
def event_selection_rules
return [] unless @attributes[:events]
@attributes[:events].map do |event|
{
name: event[:name],
time_framed?: time_framed?,
filter: event[:filter] || {}
}
end
end
def instrumentation_class
if internal_events?
events.each_value.first.nil? ? "TotalCountMetric" : "RedisHLLMetric"
else
attributes[:instrumentation_class]
@attributes[:instrumentation_class]
end
end
# This method can be removed when the refactoring is complete. It is only here to
# limit access to @attributes in a gradual manner.
def raw_attributes
@attributes
end
def status
attributes[:status]
@attributes[:status]
end
def value_json_schema
attributes[:value_json_schema]
@attributes[:value_json_schema]
end
def value_type
@attributes[:value_type]
end
def to_context
@ -50,7 +70,7 @@ module Gitlab
end
def to_h
attributes
@attributes
end
def json_schema
@ -62,15 +82,15 @@ module Gitlab
def json_schema_path
return '' unless has_json_schema?
Rails.root.join(attributes[:value_json_schema])
Rails.root.join(@attributes[:value_json_schema])
end
def has_json_schema?
attributes[:value_type] == 'object' && attributes[:value_json_schema].present?
@attributes[:value_type] == 'object' && @attributes[:value_json_schema].present?
end
def validation_errors
SCHEMA.validate(attributes.deep_stringify_keys).map do |error|
SCHEMA.validate(@attributes.deep_stringify_keys).map do |error|
<<~ERROR_MSG
--------------- VALIDATION ERROR ---------------
Metric file: #{path}
@ -81,16 +101,40 @@ module Gitlab
end
end
def product_group
@attributes[:product_group]
end
def time_frame
@attributes[:time_frame]
end
def time_framed?
%w[7d 28d].include?(time_frame)
end
def active?
status == 'active'
end
def broken?
status == 'broken'
end
def available?
AVAILABLE_STATUSES.include?(attributes[:status])
AVAILABLE_STATUSES.include?(status)
end
def valid_service_ping_status?
VALID_SERVICE_PING_STATUSES.include?(attributes[:status])
VALID_SERVICE_PING_STATUSES.include?(status)
end
def data_category
@attributes[:data_category]
end
def data_source
attributes[:data_source]
@attributes[:data_source]
end
def internal_events?
@ -113,12 +157,12 @@ module Gitlab
end
def not_removed
all.select { |definition| definition.attributes[:status] != 'removed' }.index_by(&:key_path)
all.select { |definition| definition.status != 'removed' }.index_by(&:key_path)
end
def with_instrumentation_class
all.select do |definition|
(definition.internal_events? || definition.attributes[:instrumentation_class].present?) && definition.available?
(definition.internal_events? || definition.instrumentation_class.present?) && definition.available?
end
end
@ -164,14 +208,14 @@ module Gitlab
private
def events_from_new_structure
events = attributes[:events]
events = @attributes[:events]
return unless events
events.to_h { |event| [event[:name], event[:unique]&.to_sym] }
end
def events_from_old_structure
options_events = attributes.dig(:options, :events)
options_events = @attributes.dig(:options, :events)
return unless options_events
options_events.index_with { nil }

View File

@ -486,6 +486,8 @@
- p_ci_templates_security_api_discovery
- p_ci_templates_security_api_fuzzing
- p_ci_templates_security_api_fuzzing_latest
- p_ci_templates_security_api_security
- p_ci_templates_security_api_security_latest
- p_ci_templates_security_bas_latest
- p_ci_templates_security_container_scanning
- p_ci_templates_security_container_scanning_latest

View File

@ -47,9 +47,7 @@ module ServicePing
end
def metric_category(key_path)
metric_definitions[key_path]
&.attributes
&.fetch(:data_category, ::ServicePing::PermitDataCategories::OPTIONAL_CATEGORY)
metric_definitions[key_path]&.data_category || ::ServicePing::PermitDataCategories::OPTIONAL_CATEGORY
end
def metric_definitions

View File

@ -33099,6 +33099,15 @@ msgstr ""
msgid "MlExperimentTracking|Version"
msgstr ""
msgid "MlModelRegistry|# Create a model"
msgstr ""
msgid "MlModelRegistry|# Create a version"
msgstr ""
msgid "MlModelRegistry|# Log artifacts"
msgstr ""
msgid "MlModelRegistry|%d model"
msgid_plural "MlModelRegistry|%d models"
msgstr[0] ""
@ -33217,6 +33226,9 @@ msgstr ""
msgid "MlModelRegistry|ID"
msgstr ""
msgid "MlModelRegistry|Import your machine learning using GitLab directly or using the MLflow client:"
msgstr ""
msgid "MlModelRegistry|Info"
msgstr ""
@ -33232,6 +33244,9 @@ msgstr ""
msgid "MlModelRegistry|Leave empty to skip version creation."
msgstr ""
msgid "MlModelRegistry|MLflow compatibility"
msgstr ""
msgid "MlModelRegistry|MLflow run ID"
msgstr ""
@ -33277,6 +33292,9 @@ msgstr ""
msgid "MlModelRegistry|No logged parameters"
msgstr ""
msgid "MlModelRegistry|No models registered"
msgstr ""
msgid "MlModelRegistry|No registered versions"
msgstr ""

View File

@ -5,14 +5,14 @@ require 'spec_helper'
RSpec.describe Groups::ChildrenController, feature_category: :groups_and_projects do
include ExternalAuthorizationServiceHelpers
let(:group) { create(:group, :public) }
let(:user) { create(:user) }
let!(:group_member) { create(:group_member, group: group, user: user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group_member) { create(:group_member, group: group, user: user) }
describe 'GET #index' do
context 'for projects' do
let!(:public_project) { create(:project, :public, namespace: group) }
let!(:private_project) { create(:project, :private, namespace: group) }
let_it_be(:public_project) { create(:project, :public, namespace: group) }
let_it_be(:private_project) { create(:project, :private, namespace: group) }
context 'as a user' do
before do
@ -47,10 +47,10 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
context 'for subgroups' do
let!(:public_subgroup) { create(:group, :public, parent: group) }
let!(:private_subgroup) { create(:group, :private, parent: group) }
let!(:public_project) { create(:project, :public, namespace: group) }
let!(:private_project) { create(:project, :private, namespace: group) }
let_it_be(:public_subgroup) { create(:group, :public, parent: group) }
let_it_be(:private_subgroup) { create(:group, :private, parent: group) }
let_it_be(:public_project) { create(:project, :public, namespace: group) }
let_it_be(:private_project) { create(:project, :private, namespace: group) }
context 'as a user' do
before do
@ -197,6 +197,17 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
end
context 'sorting children' do
it 'allows sorting projects' do
project_1 = create(:project, :public, namespace: group, name: 'mobile')
project_2 = create(:project, :public, namespace: group, name: 'hardware')
get :index, params: { group_id: group.to_param, sort: 'name_asc' }, format: :json
expect(assigns(:children)).to eq([public_subgroup, project_2, project_1, public_project])
end
end
context 'queries per rendered element', :request_store do
# We need to make sure the following counts are preloaded
# otherwise they will cause an extra query
@ -269,7 +280,7 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
context 'pagination' do
let(:per_page) { 3 }
let_it_be(:per_page) { 3 }
before do
allow(Kaminari.config).to receive(:default_per_page).and_return(per_page)
@ -288,8 +299,8 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
context 'with only projects' do
let!(:other_project) { create(:project, :public, namespace: group) }
let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
let_it_be(:other_project) { create(:project, :public, namespace: group) }
let_it_be(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'has projects on the first page' do
get :index, params: { group_id: group.to_param, sort: 'id_desc' }, format: :json
@ -305,9 +316,9 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
context 'with subgroups and projects' do
let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let!(:other_subgroup) { create(:group, :public, parent: group) }
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
let_it_be(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let_it_be(:other_subgroup) { create(:group, :public, parent: group) }
let_it_be(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'contains all subgroups' do
get :index, params: { group_id: group.to_param, sort: 'id_asc' }, format: :json
@ -322,8 +333,8 @@ RSpec.describe Groups::ChildrenController, feature_category: :groups_and_project
end
context 'with a mixed first page' do
let!(:first_page_subgroups) { [create(:group, :public, parent: group)] }
let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
let_it_be(:first_page_subgroups) { [create(:group, :public, parent: group)] }
let_it_be(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'correctly calculates the counts' do
get :index, params: { group_id: group.to_param, sort: 'id_asc', page: 2 }, format: :json

View File

@ -656,7 +656,7 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
end
def project_media_uri(project, media_path)
"#{project_path(project)}#{media_path}"
"/-/project/#{project.id}#{media_path}"
end
def verify_media_with_partial_path(gfm, media_uri)

View File

@ -51,7 +51,7 @@ RSpec.describe 'Projects > Snippets > Create Snippet', :js, feature_category: :s
wait_for_requests
link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src']
expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z})
expect(link).to match(%r{/-/project/#{project.id}/uploads/\h{32}/banana_sample\.gif\z})
end
context 'when the git operation fails' do

View File

@ -78,7 +78,7 @@ RSpec.describe 'User uploads file to note', feature_category: :team_planning do
wait_for_requests
expect(find('a.no-attachment-icon img.js-lazy-loaded[alt="dk"]')['src'])
.to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
.to match(%r{/-/project/#{project.id}/uploads/\h{32}/dk\.png$})
end
end
end

View File

@ -5,10 +5,9 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import { IndexMlModels } from '~/ml/model_registry/apps';
import ModelRow from '~/ml/model_registry/components/model_row.vue';
import ModelCreate from '~/ml/model_registry/components/model_create.vue';
import { MODEL_ENTITIES } from '~/ml/model_registry/constants';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import EmptyState from '~/ml/model_registry/components/empty_state.vue';
import EmptyState from '~/ml/model_registry/components/model_list_empty_state.vue';
import ActionsDropdown from '~/ml/model_registry/components/actions_dropdown.vue';
import SearchableList from '~/ml/model_registry/components/searchable_list.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@ -21,7 +20,6 @@ Vue.use(VueApollo);
const defaultProps = {
projectPath: 'path/to/project',
createModelPath: 'path/to/create',
canWriteModelRegistry: false,
};
@ -89,7 +87,18 @@ describe('ml/model_registry/apps/index_ml_models', () => {
await waitForPromises();
expect(findEmptyState().props('entityType')).toBe(MODEL_ENTITIES.model);
expect(findEmptyState().exists()).toBe(true);
});
it('opens model creation from empty state', async () => {
createWrapper({
props: { canWriteModelRegistry: true },
resolver: emptyQueryResolver(),
});
await findEmptyState().vm.$emit('open-create-model');
expect(findModelCreate().props('createModelVisible')).toBe(true);
});
});
@ -115,6 +124,21 @@ describe('ml/model_registry/apps/index_ml_models', () => {
expect(findModelCreate().exists()).toBe(true);
});
it('opens and closes model creation', async () => {
createWrapper({
props: { canWriteModelRegistry: true },
resolver: emptyQueryResolver(),
});
await findModelCreate().vm.$emit('show-create-model');
expect(findModelCreate().props('createModelVisible')).toBe(true);
await findModelCreate().vm.$emit('hide-create-model');
expect(findModelCreate().props('createModelVisible')).toBe(false);
});
});
});

View File

@ -1,39 +1,29 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { uploadModel } from '~/ml/model_registry/services/upload_model';
import ImportArtifactZone from '~/ml/model_registry/components/import_artifact_zone.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
jest.mock('~/alert');
jest.mock('~/ml/model_registry/services/upload_model', () => ({
uploadModel: jest.fn(() => Promise.resolve()),
}));
describe('ImportArtifactZone', () => {
let wrapper;
let axiosMock;
const file = { name: 'file.txt', size: 1024 };
const initialProps = {
path: 'some/path',
};
const filePath = 'some/path/file.txt';
const formattedFileSizeDiv = () => wrapper.findByTestId('formatted-file-size');
const fileNameDiv = () => wrapper.findByTestId('file-name');
const zone = () => wrapper.findComponent(UploadDropzone);
const loadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const emulateFileDrop = () => zone().vm.$emit('change', file);
beforeEach(() => {
axiosMock = new MockAdapter(axios);
axiosMock.onPut(filePath).replyOnce(HTTP_STATUS_OK, {});
});
afterEach(() => {
axiosMock.restore();
});
describe('successful upload', () => {
beforeEach(() => {
wrapper = shallowMountExtended(ImportArtifactZone, {
@ -72,9 +62,13 @@ describe('ImportArtifactZone', () => {
await emulateFileDrop();
await waitForPromises();
expect(axiosMock.history.put).toHaveLength(1);
const uploadRequest = axiosMock.history.put[0];
expect(uploadRequest.url).toBe('some/path/file.txt');
expect(uploadModel).toHaveBeenCalledWith({
file: {
name: 'file.txt',
size: 1024,
},
importPath: 'some/path',
});
});
it('emits a change event on success', async () => {
@ -94,8 +88,7 @@ describe('ImportArtifactZone', () => {
});
it('displays an error on failure', async () => {
axiosMock.reset();
axiosMock.onPut(filePath).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR, {});
uploadModel.mockRejectedValue('Internal server error.');
await emulateFileDrop();
await waitForPromises();
@ -106,8 +99,7 @@ describe('ImportArtifactZone', () => {
});
it('resets the state on failure', async () => {
axiosMock.reset();
axiosMock.onPut(filePath).timeout();
uploadModel.mockRejectedValue('Internal server error.');
await emulateFileDrop();
await waitForPromises();
@ -131,7 +123,7 @@ describe('ImportArtifactZone', () => {
await emulateFileDrop();
await waitForPromises();
expect(axiosMock.history.put).toHaveLength(0);
expect(uploadModel).not.toHaveBeenCalled();
expect(loadingIcon().exists()).toBe(false);
});
});
@ -150,7 +142,7 @@ describe('ImportArtifactZone', () => {
await emulateFileDrop();
await waitForPromises();
expect(axiosMock.history.put).toHaveLength(0);
expect(uploadModel).not.toHaveBeenCalled();
expect(loadingIcon().exists()).toBe(false);
});
});

View File

@ -40,6 +40,7 @@ describe('ModelCreate', () => {
const createWrapper = (
createModelResolver = jest.fn().mockResolvedValue(createModelResponses.success),
createModelVersionResolver = jest.fn().mockResolvedValue(createModelVersionResponses.success),
createModelVisible = false,
) => {
const requestHandlers = [
[createModelMutation, createModelResolver],
@ -48,6 +49,9 @@ describe('ModelCreate', () => {
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(ModelCreate, {
propsData: {
createModelVisible,
},
provide: {
projectPath: 'some/project',
},
@ -69,17 +73,37 @@ describe('ModelCreate', () => {
};
describe('Initial state', () => {
beforeEach(() => {
createWrapper();
});
describe('Modal closed', () => {
beforeEach(() => {
createWrapper();
});
it('renders the modal button', () => {
expect(findModalButton().text()).toBe('Create model');
it('does not show modal', () => {
expect(findGlModal().props('visible')).toBe(false);
});
it('renders the modal button', () => {
expect(findModalButton().text()).toBe('Create model');
});
it('clicking create button triggers show-create-model', async () => {
await findModalButton().vm.$emit('click');
expect(wrapper.emitted('show-create-model')).toHaveLength(1);
});
});
describe('Modal open', () => {
beforeEach(() => {
findModalButton().trigger('click');
createWrapper(
jest.fn().mockResolvedValue(createModelResponses.success),
jest.fn().mockResolvedValue(createModelVersionResponses.success),
true,
);
});
it('does not show modal', () => {
expect(findGlModal().props('visible')).toBe(true);
});
it('renders the name input', () => {
@ -135,6 +159,18 @@ describe('ModelCreate', () => {
it('does not render the alert by default', () => {
expect(findGlAlert().exists()).toBe(false);
});
it('clicking on cancel button triggers hide-create-model', async () => {
await findGlModal().vm.$emit('cancel');
expect(wrapper.emitted('hide-create-model')).toHaveLength(1);
});
it('dismissing modal triggers hide-create-model', async () => {
await findGlModal().vm.$emit('hide');
expect(wrapper.emitted('hide-create-model')).toHaveLength(1);
});
});
});

View File

@ -0,0 +1,78 @@
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EmptyState from '~/ml/model_registry/components/model_list_empty_state.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
let wrapper;
const createWrapper = () => {
wrapper = shallowMount(EmptyState, {
provide: { mlflowTrackingUrl: 'path/to/mlflow', createModelPath: '/path/to/create' },
});
};
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findCopyButton = () => wrapper.findComponent(ClipboardButton);
const findCreateButton = () => wrapper.findComponent(GlButton);
const findDocsButton = () => wrapper.findAllComponents(GlButton).at(1);
const mlflowCmd = [
'import os',
'from mlflow import MlflowClient',
'',
'os.environ["MLFLOW_TRACKING_URI"] = "path/to/mlflow"',
'os.environ["MLFLOW_TRACKING_TOKEN"] = <your_gitlab_token>',
'',
'# Create a model',
'client = MlflowClient()',
"model_name = '<your_model_name>'",
"description = 'Model description'",
'model = client.create_registered_model(model_name, description=description)',
'',
'# Create a version',
'tags = { "gitlab.version": version }',
'model_version = client.create_model_version(model_name, version, tags=tags)',
'',
'# Log artifacts',
'client.log_artifact(run_id, \'<local/path/to/file.txt>\', artifact_path="")',
].join('\n');
describe('ml/model_registry/components/model_list_empty_state.vue', () => {
beforeEach(() => {
createWrapper();
});
describe('when entity type is model', () => {
it('shows the correct empty state', () => {
expect(findEmptyState().props()).toMatchObject({
title: 'No models registered',
svgPath: 'file-mock',
});
expect(findEmptyState().text()).toContain(
'Import your machine learning using GitLab directly or using the MLflow client:',
);
expect(findEmptyState().text()).toContain(mlflowCmd);
});
it('creates the copy text button', () => {
expect(findCopyButton().props('text')).toBe(mlflowCmd);
});
it('creates button to open model creation', () => {
expect(findCreateButton().text()).toBe('Create model');
});
it('clicking creates triggers open-create-model', async () => {
await findCreateButton().vm.$emit('click');
expect(wrapper.emitted('open-create-model')).toHaveLength(1);
});
it('creates button to docs', () => {
expect(findDocsButton().text()).toBe('MLflow compatibility');
expect(findDocsButton().attributes('href')).toBe(
'/help/user/project/ml/model_registry/index#creating-machine-learning-models-and-model-versions',
);
});
});
});

View File

@ -5,6 +5,14 @@ import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue';
import SearchResultHoverLayover from '~/super_sidebar/components/global_search/components/global_search_hover_overlay.vue';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import {
ISSUES_ASSIGNED_TO_ME_TITLE,
ISSUES_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_THAT_I_AM_A_REVIEWER,
MERGE_REQUESTS_I_HAVE_CREATED_TITLE,
MERGE_REQUESTS_ASSIGNED_TO_ME_TITLE,
} from '~/super_sidebar/components/global_search/command_palette/constants';
import {
MOCK_SEARCH_CONTEXT,
MOCK_PROJECT_SEARCH_CONTEXT,
@ -158,4 +166,29 @@ describe('GlobalSearchDefaultPlaces', () => {
});
});
});
describe('Track events', () => {
beforeEach(() => {
createComponent({
searchContext: MOCK_PROJECT_SEARCH_CONTEXT,
mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS,
});
});
const { bindInternalEventDocument } = useMockInternalEventsTracking();
it.each`
eventTrigger | event
${ISSUES_ASSIGNED_TO_ME_TITLE} | ${'click_issues_assigned_to_me_in_command_palette'}
${ISSUES_I_HAVE_CREATED_TITLE} | ${'click_issues_i_created_in_command_palette'}
${MERGE_REQUESTS_ASSIGNED_TO_ME_TITLE} | ${'click_merge_requests_assigned_to_me_in_command_palette'}
${MERGE_REQUESTS_THAT_I_AM_A_REVIEWER} | ${'click_merge_requests_that_im_a_reviewer_in_command_palette'}
${MERGE_REQUESTS_I_HAVE_CREATED_TITLE} | ${'click_merge_requests_i_created_in_command_palette'}
`('triggers and tracks command dropdown $event', ({ eventTrigger, event }) => {
const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
findGroup().vm.$emit('action', { text: eventTrigger });
expect(trackEventSpy).toHaveBeenCalledWith(event, {}, undefined);
});
});
});

View File

@ -65,7 +65,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
describe 'inside a project' do
it 'renders uploads relative to project' do
expect(subject).to include("#{project.full_path}/uploads/test.png")
expect(subject).to include("/-/project/#{project.id}/uploads/test.png")
end
end
@ -76,7 +76,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
end
it 'renders uploads relative to the group' do
expect(subject).to include("#{group.full_path}/-/uploads/test.png")
expect(subject).to include("/-/group/#{group.id}/uploads/test.png")
end
end
@ -89,7 +89,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
end
it 'renders uploads relative to project' do
expect(subject).to include("#{project_in_group.path_with_namespace}/uploads/test.png")
expect(subject).to include("/-/project/#{project_in_group.id}/uploads/test.png")
end
end
end
@ -373,7 +373,7 @@ RSpec.describe MarkupHelper, feature_category: :team_planning do
it 'renders uploads relative to project' do
result = helper.render_wiki_content(wiki_page)
expect(result).to include("#{project.full_path}#{upload_link}")
expect(result).to include("/-/project/#{project.id}#{upload_link}")
end
end
end

View File

@ -40,7 +40,7 @@ RSpec.describe Banzai::Filter::UploadLinkFilter, feature_category: :team_plannin
let(:project_path) { project.full_path }
let(:only_path) { true }
let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
let(:relative_path) { "/-/project/#{project.id}#{upload_path}" }
it 'preserves original url in data-canonical-src attribute' do
doc = filter(link(upload_path))
@ -102,7 +102,7 @@ RSpec.describe Banzai::Filter::UploadLinkFilter, feature_category: :team_plannin
path = '/uploads/한글.png'
doc = filter(link(path))
expect(doc.at_css('a')['href']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
expect(doc.at_css('a')['href']).to eq("/-/project/#{project.id}/uploads/%ED%95%9C%EA%B8%80.png")
expect(doc.at_css('a').classes).to include('gfm')
expect(doc.at_css('a')['data-link']).to eq('true')
end
@ -112,10 +112,32 @@ RSpec.describe Banzai::Filter::UploadLinkFilter, feature_category: :team_plannin
escaped = Addressable::URI.escape(path)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
expect(doc.at_css('img')['src']).to eq("/-/project/#{project.id}/uploads/%ED%95%9C%EA%B8%80.png")
expect(doc.at_css('img').classes).to include('gfm')
expect(doc.at_css('img')['data-link']).not_to eq('true')
end
context 'when use_ids_for_markdown_upload_urls is disabled' do
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
before do
stub_feature_flags(use_ids_for_markdown_upload_urls: false)
end
it 'prepends project path to the URL' do
doc = filter(link(upload_path))
expect(doc.at_css('a')['href']).to eq(relative_path)
expect(doc.at_css('a').classes).to include('gfm')
expect(doc.at_css('a')['data-link']).to eq('true')
doc = filter(nested(link(upload_path)))
expect(doc.at_css('a')['href']).to eq(relative_path)
expect(doc.at_css('a').classes).to include('gfm')
expect(doc.at_css('a')['data-link']).to eq('true')
end
end
end
context 'to a group upload' do
@ -123,7 +145,7 @@ RSpec.describe Banzai::Filter::UploadLinkFilter, feature_category: :team_plannin
let_it_be(:group) { create(:group) }
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
let(:relative_path) { "/-/group/#{group.id}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
@ -163,6 +185,22 @@ RSpec.describe Banzai::Filter::UploadLinkFilter, feature_category: :team_plannin
expect(doc.at_css('a').classes).not_to include('gfm')
expect(doc.at_css('a')['data-link']).not_to eq('true')
end
context 'when use_ids_for_markdown_upload_urls is disabled' do
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
before do
stub_feature_flags(use_ids_for_markdown_upload_urls: false)
end
it 'prepends group path to the URL' do
doc = filter(upload_link)
expect(doc.at_css('a')['href']).to eq(relative_path)
expect(doc.at_css('a').classes).to include('gfm')
expect(doc.at_css('a')['data-link']).to eq('true')
end
end
end
context 'to a personal snippet' do

View File

@ -20,10 +20,11 @@ RSpec.describe 'CI YML Templates' do
context 'that support autodevops' do
exceptions = [
'Diffblue-Cover.gitlab-ci.yml', # no auto-devops
'Security/DAST.gitlab-ci.yml', # DAST stage is defined inside AutoDevops yml
'Security/DAST-API.gitlab-ci.yml', # no auto-devops
'Security/API-Fuzzing.gitlab-ci.yml', # no auto-devops
'Diffblue-Cover.gitlab-ci.yml', # no auto-devops
'Security/DAST.gitlab-ci.yml', # DAST stage is defined inside AutoDevops yml
'Security/DAST-API.gitlab-ci.yml', # no auto-devops
'Security/API-Fuzzing.gitlab-ci.yml', # no auto-devops
'Security/API-Security.gitlab-ci.yml', # no auto-decops
'ThemeKit.gitlab-ci.yml'
]
@ -165,6 +166,51 @@ RSpec.describe 'CI YML Templates' do
include_examples 'require default stages to be included'
end
context 'when API Security template' do
# The API Security template purposly excludes a stages
# definition.
let(:template_name) { 'Security/API-Security.gitlab-ci.yml' }
context 'with default stages' do
let(:content) do
<<~EOS
include:
- template: #{template_name}
concrete_build_implemented_by_a_user:
stage: test
script: do something
EOS
end
it { is_expected.not_to be_valid }
end
context 'with defined stages' do
let(:content) do
<<~EOS
include:
- template: #{template_name}
stages:
- build
- test
- deploy
- dast
concrete_build_implemented_by_a_user:
stage: test
script: do something
EOS
end
it { is_expected.to be_valid }
include_examples 'require default stages to be included'
end
end
end
end
end

View File

@ -65,7 +65,7 @@ RSpec.describe Gitlab::Tracking::EventDefinition, feature_category: :service_pin
it 'has event definitions for all events used in Internal Events metric definitions', :aggregate_failures do
from_metric_definitions = Gitlab::Usage::MetricDefinition.not_removed
.values
.select { |m| m.attributes[:data_source] == 'internal_events' }
.select(&:internal_events?)
.flat_map { |m| m.events&.keys }
.compact
.uniq

View File

@ -25,7 +25,7 @@ RSpec.describe Gitlab::Usage::Metric do
let(:issue_count_metric_definiton) do
double(:issue_count_metric_definiton,
attributes.merge({ attributes: attributes })
attributes.merge({ raw_attributes: attributes })
)
end
@ -49,7 +49,7 @@ RSpec.describe Gitlab::Usage::Metric do
let(:instrumentation_class) { "UnavailableMetric" }
let(:issue_count_metric_definiton) do
double(:issue_count_metric_definiton,
attributes.merge({ attributes: attributes, instrumentation_class: instrumentation_class })
attributes.merge({ raw_attributes: attributes, instrumentation_class: instrumentation_class })
)
end

View File

@ -143,7 +143,7 @@ RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_c
end
def type_cast_to_defined_type(value, metric_definition)
case metric_definition&.attributes&.fetch(:value_type)
case metric_definition&.value_type
when "string"
value.to_s
when "number"
@ -181,7 +181,7 @@ RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_c
metric_definition = metric_definitions[key_path.join('.')]
# Skip broken metrics since they are usually overriden to return -1
next if metric_definition&.attributes&.fetch(:status) == 'broken'
next if metric_definition&.broken?
value = type_cast_to_defined_type(value, metric_definition)
payload_value = service_ping_payload.dig(*key_path)

View File

@ -20,8 +20,8 @@ RSpec.describe 'Code review events' do
]
all_code_review_events = Gitlab::Usage::MetricDefinition.all.flat_map do |definition|
next [] unless definition.attributes[:key_path].include?('.code_review.') &&
definition.attributes[:status] == 'active' &&
next [] unless definition.key_path.include?('.code_review.') &&
definition.active? &&
definition.events.count == 1
definition.events.keys
@ -37,8 +37,8 @@ RSpec.describe 'Code review events' do
end
def code_review_aggregated_metric?(definition)
definition.attributes[:product_group] == 'code_review' &&
definition.attributes[:status] == 'active' &&
definition.product_group == 'code_review' &&
definition.active? &&
definition.events.count > 1
end
end

View File

@ -57,7 +57,7 @@ RSpec.describe Emails::Releases do
let(:release) { create(:release, project: project, description: "Attachment: [Test file](#{upload_path})") }
it 'renders absolute links' do
is_expected.to have_body_text(%(<a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">Test file</a>))
is_expected.to have_body_text(%(<a href="#{root_url}-/project/#{project.id}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">Test file</a>))
end
end
end

View File

@ -393,7 +393,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
end
end
let_it_be(:expected_html) { %(a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
let_it_be(:expected_html) { %(a new comment with <a href="#{root_url}-/project/#{project.id}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
let_it_be(:expected_template_html) { %(some text #{expected_html}) }
it_behaves_like 'a service desk notification email'
@ -459,7 +459,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
context 'when not all uploads processed correct' do # rubocop:disable RSpec/MultipleMemoizedHelpers -- Avoid duplication with heavy use of helpers
let(:attachments_count) { 1 }
let_it_be(:expected_html) { %(a new comment with <strong>#{filename}</strong> <a href="#{project.web_url}#{upload_path_1}" data-canonical-src="#{upload_path_1}" data-link="true" class="gfm">#{filename_1}</a>) }
let_it_be(:expected_html) { %(a new comment with <strong>#{filename}</strong> <a href="#{root_url}-/project/#{project.id}#{upload_path_1}" data-canonical-src="#{upload_path_1}" data-link="true" class="gfm">#{filename_1}</a>) }
let_it_be(:expected_template_html) { %(some text #{expected_html}) }
it_behaves_like 'a service desk notification email'
@ -476,7 +476,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(StandardError, project_id: note.project_id)
end
let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{root_url}-/project/#{project.id}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
it_behaves_like 'a service desk notification email with template content'
end
@ -489,7 +489,7 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(StandardError, project_id: note.project_id)
end
let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{project.web_url}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
let_it_be(:expected_template_html) { %(some text a new comment with <a href="#{root_url}-/project/#{project.id}#{upload_path}" data-canonical-src="#{upload_path}" data-link="true" class="gfm">#{filename}</a>) }
it_behaves_like 'a service desk notification email with template content'
end

View File

@ -2341,7 +2341,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
expect(json_response['alt']).to eq("dk")
expect(json_response['url']).to start_with("/uploads/")
expect(json_response['url']).to end_with("/dk.png")
expect(json_response['full_path']).to start_with("/#{project.namespace.path}/#{project.path}/uploads")
expect(json_response['full_path']).to start_with("/-/project/#{project.id}/uploads")
end
it "does not leave the temporary file in place after uploading, even when the tempfile reaper does not run" do

View File

@ -678,7 +678,7 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process', feature_category: :team
canonical: |
<p><a href="groups-test-file">groups-test-file</a></p>
static: |-
<p data-sourcepos="1:1-1:45" dir="auto"><a data-sourcepos="1:1-1:45" href="/groups/glfm_group/-/uploads/groups-test-file" data-canonical-src="/uploads/groups-test-file" data-link="true" class="gfm">groups-test-file</a></p>
<p data-sourcepos="1:1-1:45" dir="auto"><a data-sourcepos="1:1-1:45" href="/-/group/66666/uploads/groups-test-file" data-canonical-src="/uploads/groups-test-file" data-link="true" class="gfm">groups-test-file</a></p>
wysiwyg: |-
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="/uploads/groups-test-file">groups-test-file</a></p>
06_02_00__api_request_overrides__project_repo_link__001:

View File

@ -360,33 +360,5 @@ RSpec.describe AutoMerge::BaseService, feature_category: :code_review_workflow d
expect(available_for).to be_falsey
end
end
context 'when refactor_auto_merge is disabled' do
before do
stub_feature_flags(refactor_auto_merge: false)
allow(merge_request).to receive(:can_be_merged_by?).and_return(can_be_merged)
allow(merge_request).to receive(:open?).and_return(open)
allow(merge_request).to receive(:broken?).and_return(broken)
allow(merge_request).to receive(:draft?).and_return(draft)
allow(merge_request).to receive(:mergeable_discussions_state?).and_return(discussions)
allow(merge_request).to receive(:merge_blocked_by_other_mrs?).and_return(blocked)
end
where(:can_be_merged, :open, :broken, :discussions, :blocked, :draft, :result) do
true | true | false | true | false | false | true
false | true | false | true | false | false | false
true | false | false | true | false | false | false
true | true | true | true | false | false | false
true | true | false | false | false | false | false
true | true | false | true | true | false | false
true | true | false | true | false | true | false
end
with_them do
it 'returns the expected results' do
expect(available_for).to eq(result)
end
end
end
end
end

View File

@ -384,7 +384,7 @@ RSpec::Matchers.define :increment_usage_metrics do |*key_paths|
# to be passed to a change matcher
def metric_value_tracker(key_path, metric_definition)
proc do
stub_usage_data_connections if metric_definition.attributes[:data_source] == 'database'
stub_usage_data_connections if metric_definition.data_source == 'database'
metric = Gitlab::Usage::Metric.new(metric_definition)
instrumentation_object = stub_metric_timeframe(metric)

View File

@ -18,8 +18,8 @@ module MarkdownMatchers
link = actual.at_css('a:contains("Relative Upload Link")')
image = actual.at_css('img[alt="Relative Upload Image"]')
expect(link['href']).to eq("/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg")
expect(image['data-src']).to eq("/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg")
expect(link['href']).to eq("/-/project/#{project.id}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg")
expect(image['data-src']).to eq("/-/project/#{project.id}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg")
end
end

View File

@ -2,7 +2,7 @@
RSpec.shared_context 'with GLFM example snapshot fixtures' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, name: 'glfm_group', owners: user) }
let_it_be(:group) { create(:group, name: 'glfm_group', owners: user, id: 66666) }
let_it_be(:project) do
# NOTE: We hardcode the IDs on all fixtures to prevent variability in the