Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-05-07 15:25:20 +00:00
parent 8b88def0da
commit a46ff9290c
97 changed files with 985 additions and 1157 deletions

View File

@ -13,7 +13,6 @@ Layout/LineBreakAfterFinalMixin:
- 'app/services/batched_git_ref_updates/cleanup_scheduler_service.rb'
- 'app/services/jira_connect_subscriptions/create_service.rb'
- 'app/services/projects/transfer_service.rb'
- 'app/workers/admin_email_worker.rb'
- 'app/workers/ci/delete_unit_tests_worker.rb'
- 'app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb'
- 'app/workers/ci/schedule_delete_objects_cron_worker.rb'

View File

@ -1,23 +1,9 @@
<script>
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { visitUrl } from '~/lib/utils/url_utility';
import RunnerHeader from '../components/runner_header.vue';
import RunnerHeaderActions from '../components/runner_header_actions.vue';
import RunnerDetailsTabs from '../components/runner_details_tabs.vue';
import { I18N_FETCH_ERROR } from '../constants';
import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'AdminRunnerShowApp',
components: {
RunnerHeader,
RunnerHeaderActions,
RunnerDetailsTabs,
},
props: {
@ -29,49 +15,13 @@ export default {
type: String,
required: true,
},
},
data() {
return {
runner: null,
};
},
apollo: {
runner: {
query: runnerQuery,
variables() {
return {
id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
};
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
onDeleted({ message }) {
saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
visitUrl(this.runnersPath);
editPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div>
<runner-header v-if="runner" :runner="runner">
<template #actions>
<runner-header-actions
:runner="runner"
:edit-path="runner.editAdminUrl"
@deleted="onDeleted"
/>
</template>
</runner-header>
<runner-details-tabs v-if="runner" :runner="runner" />
</div>
<runner-details-tabs :runner-id="runnerId" :runners-path="runnersPath" :edit-path="editPath" />
</template>

View File

@ -17,7 +17,7 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
return null;
}
const { runnerId, runnersPath } = el.dataset;
const { runnerId, runnersPath, editPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@ -31,6 +31,7 @@ export const initAdminRunnerShow = (selector = '#js-admin-runner-show') => {
props: {
runnerId,
runnersPath,
editPath,
},
});
},

View File

@ -86,7 +86,7 @@ export default {
</script>
<template>
<div>
<div v-if="runner">
<div class="gl-pt-4">
<dl class="gl-mb-0 gl-grid gl-grid-cols-[auto_1fr]">
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />

View File

@ -2,8 +2,20 @@
import { GlBadge, GlTabs, GlTab } from '@gitlab/ui';
import VueRouter from 'vue-router';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { JOBS_ROUTE_PATH, I18N_DETAILS, I18N_JOBS } from '../constants';
import { visitUrl } from '~/lib/utils/url_utility';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import runnerQuery from '../graphql/show/runner.query.graphql';
import { JOBS_ROUTE_PATH, I18N_DETAILS, I18N_JOBS, I18N_FETCH_ERROR } from '../constants';
import { formatJobCount } from '../utils';
import { captureException } from '../sentry_utils';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
import RunnerHeader from './runner_header.vue';
import RunnerHeaderActions from './runner_header_actions.vue';
import RunnerDetails from './runner_details.vue';
import RunnerJobs from './runner_jobs.vue';
@ -31,15 +43,24 @@ export default {
GlTabs,
GlTab,
HelpPopover,
RunnerHeader,
RunnerHeaderActions,
},
router: new VueRouter({
routes,
}),
props: {
runner: {
type: Object,
required: false,
default: null,
runnerId: {
type: String,
required: true,
},
runnersPath: {
type: String,
required: true,
},
editPath: {
type: String,
required: true,
},
showAccessHelp: {
type: Boolean,
@ -47,6 +68,26 @@ export default {
default: false,
},
},
data() {
return {
runner: null,
};
},
apollo: {
runner: {
query: runnerQuery,
variables() {
return {
id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
};
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
computed: {
jobCount() {
return formatJobCount(this.runner?.jobCount);
@ -56,6 +97,15 @@ export default {
},
},
methods: {
onDeleted({ message }) {
if (this.runnersPath) {
saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
visitUrl(this.runnersPath);
}
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
goTo(name) {
if (this.$route.name !== name) {
this.$router.push({ name });
@ -69,22 +119,30 @@ export default {
};
</script>
<template>
<gl-tabs :value="tabIndex">
<gl-tab @click="goTo($options.ROUTE_DETAILS)">
<template #title>{{ $options.I18N_DETAILS }}</template>
</gl-tab>
<gl-tab @click="goTo($options.ROUTE_JOBS)">
<template #title>
{{ $options.I18N_JOBS }}
<gl-badge v-if="jobCount" data-testid="job-count-badge" class="gl-tab-counter-badge">
{{ jobCount }}
</gl-badge>
<help-popover v-if="showAccessHelp" class="gl-ml-3">
{{ s__('Runners|Jobs in projects you have access to.') }}
</help-popover>
<div>
<runner-header v-if="runner" :runner="runner">
<template #actions>
<runner-header-actions :runner="runner" :edit-path="editPath" @deleted="onDeleted" />
</template>
</gl-tab>
</runner-header>
<router-view v-if="runner" :runner="runner" />
</gl-tabs>
<gl-tabs :value="tabIndex">
<gl-tab @click="goTo($options.ROUTE_DETAILS)">
<template #title>{{ $options.I18N_DETAILS }}</template>
</gl-tab>
<gl-tab @click="goTo($options.ROUTE_JOBS)">
<template #title>
{{ $options.I18N_JOBS }}
<gl-badge v-if="jobCount" data-testid="job-count-badge" class="gl-tab-counter-badge">
{{ jobCount }}
</gl-badge>
<help-popover v-if="showAccessHelp" class="gl-ml-3">
{{ s__('Runners|Jobs in projects you have access to.') }}
</help-popover>
</template>
</gl-tab>
<router-view :runner-id="runnerId" :runner="runner" />
</gl-tabs>
</div>
</template>

View File

@ -1,6 +1,9 @@
<script>
import { createAlert } from '~/alert';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import runnerJobsQuery from '../graphql/show/runner_jobs.query.graphql';
import {
I18N_FETCH_ERROR,
@ -23,14 +26,15 @@ export default {
RunnerJobsEmptyState,
},
props: {
runner: {
type: Object,
runnerId: {
type: String,
required: true,
},
},
data() {
return {
jobs: {
count: '',
items: [],
pageInfo: {},
},
@ -45,6 +49,7 @@ export default {
},
update({ runner }) {
return {
count: runner?.jobCount || '',
items: runner?.jobs?.nodes || [],
pageInfo: runner?.jobs?.pageInfo || {},
};
@ -57,9 +62,8 @@ export default {
},
computed: {
variables() {
const { id } = this.runner;
return {
id,
id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
...getPaginationVariables(this.pagination, RUNNER_DETAILS_JOBS_PAGE_SIZE),
};
},
@ -81,7 +85,7 @@ export default {
<crud-component
:title="$options.I18N_JOBS"
icon="pipeline"
:count="runner.jobCount"
:count="jobs.count"
:is-loading="loading"
class="gl-mt-5"
>

View File

@ -1,9 +1,16 @@
#import "~/ci/runner/graphql/list/runner_connection.fragment.graphql"
query getProjectRunners($fullPath: ID!, $type: CiRunnerType) {
query getProjectRunners(
$fullPath: ID!
$before: String
$after: String
$first: Int
$last: Int
$type: CiRunnerType
) {
project(fullPath: $fullPath) {
id # Apollo required
runners(type: $type) {
runners(before: $before, after: $after, first: $first, last: $last, type: $type) {
...RunnerConnection
}
}

View File

@ -5,6 +5,7 @@ query getRunnerJobs($id: CiRunnerID!, $first: Int, $last: Int, $before: String,
runner(id: $id) {
id
projectCount
jobCount
jobs(before: $before, after: $after, first: $first, last: $last) {
nodes {
id

View File

@ -1,23 +1,9 @@
<script>
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { visitUrl } from '~/lib/utils/url_utility';
import RunnerHeader from '../components/runner_header.vue';
import RunnerHeaderActions from '../components/runner_header_actions.vue';
import RunnerDetailsTabs from '../components/runner_details_tabs.vue';
import { I18N_FETCH_ERROR } from '../constants';
import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils';
import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'GroupRunnerShowApp',
components: {
RunnerHeader,
RunnerHeaderActions,
RunnerDetailsTabs,
},
props: {
@ -29,55 +15,18 @@ export default {
type: String,
required: true,
},
editGroupRunnerPath: {
editPath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
runner: null,
};
},
apollo: {
runner: {
query: runnerQuery,
variables() {
return {
id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
};
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
onDeleted({ message }) {
saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS });
visitUrl(this.runnersPath);
required: true,
},
},
};
</script>
<template>
<div>
<runner-header v-if="runner" :runner="runner">
<template #actions>
<runner-header-actions
:runner="runner"
:edit-path="editGroupRunnerPath"
@deleted="onDeleted"
/>
</template>
</runner-header>
<runner-details-tabs :runner="runner" :show-access-help="true" />
</div>
<runner-details-tabs
:runner-id="runnerId"
:runners-path="runnersPath"
:edit-path="editPath"
:show-access-help="true"
/>
</template>

View File

@ -17,7 +17,7 @@ export const initGroupRunnerShow = (selector = '#js-group-runner-show') => {
return null;
}
const { runnerId, runnersPath, editGroupRunnerPath } = el.dataset;
const { runnerId, runnersPath, editPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@ -31,7 +31,7 @@ export const initGroupRunnerShow = (selector = '#js-group-runner-show') => {
props: {
runnerId,
runnersPath,
editGroupRunnerPath,
editPath,
},
});
},

View File

@ -7,12 +7,15 @@ import { runnersAppProvide } from 'ee_else_ce/ci/runner/provide';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { createLocalState } from '../graphql/list/local_state';
import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
import GroupRunnersApp from './group_runners_app.vue';
Vue.use(GlToast);
Vue.use(VueApollo);
export const initGroupRunners = (selector = '#js-group-runners') => {
showAlertFromLocalStorage();
const el = document.querySelector(selector);
if (!el) {

View File

@ -14,7 +14,7 @@ export const initProjectRunnerShow = (selector = '#js-project-runner-show') => {
return null;
}
const { runnerId } = el.dataset;
const { runnerId, runnersPath, editPath } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@ -27,6 +27,8 @@ export const initProjectRunnerShow = (selector = '#js-project-runner-show') => {
return h(ProjectRunnerShowApp, {
props: {
runnerId,
runnersPath,
editPath,
},
});
},

View File

@ -1,19 +1,9 @@
<script>
import { createAlert } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '../components/runner_header.vue';
import RunnerDetailsTabs from '../components/runner_details_tabs.vue';
import { I18N_FETCH_ERROR } from '../constants';
import runnerQuery from '../graphql/show/runner.query.graphql';
import { captureException } from '../sentry_utils';
export default {
name: 'ProjectRunnerShowApp',
components: {
RunnerHeader,
RunnerDetailsTabs,
},
props: {
@ -21,37 +11,22 @@ export default {
type: String,
required: true,
},
},
data() {
return {
runner: null,
};
},
apollo: {
runner: {
query: runnerQuery,
variables() {
return {
id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId),
};
},
error(error) {
createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
runnersPath: {
type: String,
required: true,
},
},
methods: {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
editPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div>
<runner-header v-if="runner" :runner="runner" />
<runner-details-tabs v-if="runner" :runner="runner" />
</div>
<runner-details-tabs
:runner-id="runnerId"
:runners-path="runnersPath"
:edit-path="editPath"
:show-access-help="true"
/>
</template>

View File

@ -2,8 +2,10 @@
import { GlLink, GlTab, GlBadge } from '@gitlab/ui';
import RunnerList from '~/ci/runner/components/runner_list.vue';
import RunnerName from '~/ci/runner/components/runner_name.vue';
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
import { fetchPolicies } from '~/lib/graphql';
import projectRunnersQuery from '~/ci/runner/graphql/list/project_runners.query.graphql';
import { getPaginationVariables } from '../../utils';
export default {
name: 'RunnersTab',
@ -13,6 +15,7 @@ export default {
GlBadge,
RunnerList,
RunnerName,
RunnerPagination,
},
props: {
projectFullPath: {
@ -32,9 +35,11 @@ export default {
data() {
return {
loading: 0, // Initialized to 0 as this is used by a "loadingKey". See https://apollo.vuejs.org/api/smart-query.html#options
pagination: {},
runners: {
count: null,
items: [],
pageInfo: {},
},
};
},
@ -47,12 +52,13 @@ export default {
return this.variables;
},
update(data) {
const { edges = [], count } = data?.project?.runners || {};
const { edges = [], pageInfo = {}, count } = data?.project?.runners || {};
const items = edges.map(({ node, webUrl }) => ({ ...node, webUrl }));
return {
count,
items,
pageInfo,
};
},
error(error) {
@ -65,6 +71,7 @@ export default {
return {
fullPath: this.projectFullPath,
type: this.runnerType,
...getPaginationVariables(this.pagination),
};
},
isLoading() {
@ -74,6 +81,11 @@ export default {
return !this.runners.items?.length && !this.loading;
},
},
methods: {
onPaginationInput(value) {
this.pagination = value;
},
},
};
</script>
<template>
@ -95,5 +107,12 @@ export default {
</gl-link>
</template>
</runner-list>
<runner-pagination
class="gl-border-t gl-mb-3 gl-mt-5 gl-pt-5 gl-text-center"
:disabled="isLoading"
:page-info="runners.pageInfo"
@input="onPaginationInput"
/>
</gl-tab>
</template>

View File

@ -2,11 +2,14 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
import ProjectRunnersSettingsApp from './project_runners_settings_app.vue';
Vue.use(VueApollo);
export const initProjectRunnersSettings = (selector = '#js-project-runners-settings') => {
showAlertFromLocalStorage();
const el = document.querySelector(selector);
if (!el) {

View File

@ -79,7 +79,7 @@ export default Link.extend({
return {
...this.parent?.(),
editLink:
(attrs) =>
(attrs = { href: '' }) =>
({ chain }) => {
chain().setMeta('creatingLink', true).setLink(attrs).run();
},

View File

@ -47,6 +47,10 @@ export default {
text: __('Copy contents'),
action: () => this.eventHub.$emit('dropdownAction', 'copyAsGFM'),
},
{
text: __('Reload'),
action: () => this.eventHub.$emit('dropdownAction', 'reload'),
},
].filter(identity);
},
},

View File

@ -85,6 +85,10 @@ export default {
navigator.clipboard.writeText(this.wrappedQuery);
},
reload() {
this.reloadGlqlBlock();
},
async copyAsGFM() {
await copyGLQLNodeAsGFM(this.$refs.presenter.$el);
},

View File

@ -243,6 +243,7 @@ ul.related-merge-requests > li gl-emoji {
z-index: 1;
position: sticky;
top: calc(#{$calc-application-header-height} + var(--issuable-sticky-header-height, 0px) - 2px);
margin-left: 1px;
@apply gl-bg-default;
}

View File

@ -5,6 +5,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include PageLayoutHelper
include OauthApplications
include InitializesCurrentUserMode
include ViteCSP
# Defined by the `Doorkeeper::ApplicationsController` and is redundant as we call `authenticate_user!` below. Not
# defining or skipping this will result in a `403` response to all requests.

View File

@ -5,6 +5,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include InitializesCurrentUserMode
include Gitlab::Utils::StrongMemoize
include RequestPayloadLogger
include ViteCSP
alias_method :auth_user, :current_user

View File

@ -2,6 +2,7 @@
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
include PageLayoutHelper
include ViteCSP
layout 'profile'

View File

@ -2,6 +2,8 @@
module Oauth
class DeviceAuthorizationsController < Doorkeeper::DeviceAuthorizationGrant::DeviceAuthorizationsController
include ViteCSP
layout 'minimal'
def index

View File

@ -13,6 +13,7 @@ class RegistrationsController < Devise::RegistrationsController
include Gitlab::RackLoadBalancingHelpers
include ::Gitlab::Utils::StrongMemoize
include Onboarding::Redirectable
include ViteCSP
layout 'devise'

View File

@ -7,6 +7,8 @@ class ForkNetwork < ApplicationRecord
has_many :fork_network_members
has_many :projects, through: :fork_network_members
validate :organization_match
after_create :add_root_as_member, if: :root_project
def add_root_as_member
@ -20,4 +22,13 @@ class ForkNetwork < ApplicationRecord
def merge_requests
MergeRequest.where(target_project: projects)
end
private
def organization_match
return unless root_project
return if root_project.organization_id == organization_id
errors.add(:organization_id, _("must match the root project organization's ID"))
end
end

View File

@ -24,7 +24,7 @@ class LabelNote < SyntheticNote
def events=(events)
@events = events
update_outdated_markdown
update_outdated_reference
end
def cached_html_up_to_date?(markdown_field)
@ -32,20 +32,16 @@ class LabelNote < SyntheticNote
end
def note_html
label_note_html = if Feature.enabled?(:render_label_notes_lazily, resource_parent)
Banzai::Renderer.cacheless_render_field(
self, :note,
{
group: group,
project: project,
pipeline: :label,
only_path: true,
label_url_method: label_url_method
}
)
else
note_text(html: true)
end
label_note_html = Banzai::Renderer.cacheless_render_field(
self, :note,
{
group: group,
project: project,
pipeline: :label,
only_path: true,
label_url_method: label_url_method
}
)
"<p dir=\"auto\">#{label_note_html}</p>"
end
@ -53,17 +49,17 @@ class LabelNote < SyntheticNote
private
def update_outdated_markdown
def update_outdated_reference
events.each do |event|
if event.outdated_markdown?
if event.outdated_reference?
event.refresh_invalid_reference
end
end
end
def note_text(html: false)
added = labels_str(label_refs_by_action('add', html).uniq, prefix: 'added')
removed = labels_str(label_refs_by_action('remove', html).uniq, prefix: 'removed')
added = labels_str(label_refs_by_action('add').uniq, prefix: 'added')
removed = labels_str(label_refs_by_action('remove').uniq, prefix: 'removed')
[added, removed].compact.join(' and ')
end
@ -89,10 +85,8 @@ class LabelNote < SyntheticNote
"#{prefix} #{label_list_str} #{suffix.squish}"
end
def label_refs_by_action(action, html)
field = html ? :reference_html : :reference
events.select { |e| e.action == action }.map(&field)
def label_refs_by_action(action)
events.select { |e| e.action == action }.map(&:reference)
end
def label_url_method

View File

@ -5,6 +5,7 @@ module Organizations
include Gitlab::Utils::StrongMemoize
include Gitlab::SQL::Pattern
include Gitlab::VisibilityLevel
include FeatureGate
DEFAULT_ORGANIZATION_ID = 1

View File

@ -1,12 +1,12 @@
# frozen_string_literal: true
class ResourceLabelEvent < ResourceEvent
include CacheMarkdownField
include MergeRequestResourceEvent
include Import::HasImportSource
include FromUnion
cache_markdown_field :reference
ignore_column :reference_html, remove_with: '18.2', remove_after: '2025-06-19'
ignore_column :cached_markdown_version, remove_with: '18.2', remove_after: '2025-06-19'
belongs_to :label
@ -42,22 +42,8 @@ class ResourceLabelEvent < ResourceEvent
LabelNote
end
def project
issuable.project
end
def group
issuable.resource_parent if issuable.resource_parent.is_a?(Group)
end
def outdated_markdown?
return true if label_id.nil? && reference.present?
reference.nil? || latest_cached_markdown_version != cached_markdown_version
end
def banzai_render_context(field)
super.merge(pipeline: :label, only_path: true, label_url_method: label_url_method)
def outdated_reference?
(label_id.nil? && reference.present?) || reference.nil?
end
def refresh_invalid_reference
@ -67,11 +53,7 @@ class ResourceLabelEvent < ResourceEvent
# reference is not set for events which were not rendered yet
self.reference ||= label_reference
if changed?
save
elsif invalidated_markdown_cache?
refresh_markdown_cache!
end
save if changed?
end
def self.visible_to_user?(user, events)
@ -94,12 +76,6 @@ class ResourceLabelEvent < ResourceEvent
end
end
def label_url_method
return :project_merge_requests_url if issuable.is_a?(MergeRequest)
issuable.project_id.nil? ? :group_work_items_url : :project_issues_url
end
def broadcast_notes_changed
issuable.broadcast_notes_changed
end

View File

@ -15,6 +15,8 @@ class SentNotification < ApplicationRecord
validates :in_reply_to_discussion_id, format: { with: /\A\h{40}\z/, allow_nil: true }
validate :note_valid
before_create :ensure_created_at
class << self
def reply_key
SecureRandom.hex(16)
@ -101,6 +103,12 @@ class SentNotification < ApplicationRecord
private
# TODO: Remove in 18.1 as this is only necessary while the default is loaded via the migration.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186703#note_2432949624
def ensure_created_at
self.created_at = Time.current
end
def reply_params
{
noteable_type: self.noteable_type,

View File

@ -160,7 +160,13 @@ class WikiPage
self.canonical_slug = wiki_page.slug
end
def to_reference
def gfm_reference(from = nil)
"#{container.class.name.downcase} wiki page #{to_reference(from)}"
end
def to_reference(_from = nil)
return "[[#{canonical_slug}]]" unless for_group_wiki?
canonical_slug
end

View File

@ -42,6 +42,10 @@ module Projects
return ServiceResponse.error(message: _('Target project cannot be equal to source project'), reason: :self_fork)
end
if fork_to_project.organization_id != fork_network.organization_id
return ServiceResponse.error(message: _('Target project must belong to source project organization'), reason: :fork_organization_mismatch)
end
build_fork_network_member(fork_to_project)
if link_fork_network(fork_to_project)

View File

@ -78,6 +78,7 @@ module Projects
def add_source_project_to_fork_network(source_project)
return if source_project == @project
return unless fork_network
return if fork_network.organization_id != source_project.organization_id
# Because they have moved all references in the fork network from the source_project
# we won't be able to query the database (only through its cached data),

View File

@ -5,4 +5,4 @@
- page_title runner_name
- add_to_breadcrumbs _('Runners'), admin_runners_path
#js-admin-runner-show{ data: {runner_id: @runner.id, runners_path: admin_runners_path} }
#js-admin-runner-show{ data: {runner_id: @runner.id, runners_path: admin_runners_path, edit_path: edit_admin_runner_path(@runner)} }

View File

@ -5,4 +5,4 @@
- page_title runner_name
- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
#js-group-runner-show{ data: {runner_id: @runner.id, runners_path: group_runners_path(@group), edit_group_runner_path: edit_group_runner_path(@group, @runner)} }
#js-group-runner-show{ data: {runner_id: @runner.id, runners_path: group_runners_path(@group), edit_path: edit_group_runner_path(@group, @runner)} }

View File

@ -4,4 +4,4 @@
- page_title runner_name
- add_to_breadcrumbs _('CI/CD Settings'), project_runners_path
#js-project-runner-show{ data: { runner_id: @runner.id } }
#js-project-runner-show{ data: { runner_id: @runner.id, runners_path: project_runners_path, edit_path: edit_project_runner_path(@project, @runner) } }

View File

@ -39,7 +39,7 @@
- c.with_form do
#js-new-deploy-token{ data: {
container_registry_enabled: container_registry_enabled?(group_or_project),
dependency_proxy_enabled: dependency_proxy_enabled?(group_or_project),
dependency_proxy_enabled: dependency_proxy_enabled?(group_or_project) && group_or_project.is_a?(Group),
packages_registry_enabled: packages_registry_enabled?(group_or_project),
create_new_token_path: create_deploy_token_path(group_or_project),
token_type: group_or_project.is_a?(Group) ? 'group' : 'project',

View File

@ -8,6 +8,7 @@ class AdminEmailWorker # rubocop:disable Scalability/IdempotentWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext
feature_category :source_code_management

View File

@ -1,10 +0,0 @@
---
name: render_label_notes_lazily
description: Skip usage of `resource_label_events.reference_html` so we can drop the column
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/521854
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/188576
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/536554
milestone: '18.0'
group: group::project management
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: use_staging_endpoint_for_product_usage_events
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/535911
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/190236
rollout_issue_url:
milestone: '18.0'
group: group::analytics instrumentation
type: wip
default_enabled: false

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddCreatedAtToSentNotifications < Gitlab::Database::Migration[2.3]
milestone '18.0'
def up
add_timestamps_with_timezone :sent_notifications, # rubocop:disable Migration/PreventAddingColumns -- Necessary for partitioning
columns: %i[created_at],
null: false,
default: "'2025-04-02 00:00:00.000000+00'::timestamp with time zone"
end
def down
remove_timestamps :sent_notifications, columns: %i[created_at]
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class RemoveDefaultFromSentNotificationsCreatedAt < Gitlab::Database::Migration[2.3]
milestone '18.0'
def change
change_column_default :sent_notifications,
:created_at,
from: "'2025-04-02 00:00:00+00'::timestamp with time zone",
to: nil
end
end

View File

@ -0,0 +1 @@
e9dfceb876992bb723ada83acb75310d8342e8c1273c14af7a7df7881a3aa442

View File

@ -0,0 +1 @@
4cad51507c9d37d4510067a02b0082661218381e38744577c7fe75cfe500afe7

View File

@ -22736,7 +22736,8 @@ CREATE TABLE sent_notifications (
reply_key character varying NOT NULL,
in_reply_to_discussion_id character varying,
id bigint NOT NULL,
issue_email_participant_id bigint
issue_email_participant_id bigint,
created_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE sent_notifications_id_seq

View File

@ -572,6 +572,32 @@ To delete these references:
lfs_object.destroy
```
#### Remove multiple missing LFS objects
To remove references to multiple missing LFS objects at once:
1. Open the [GitLab Rails Console](../operations/rails_console.md#starting-a-rails-console-session).
1. Run the following script:
```ruby
lfs_files_deleted = 0
LfsObject.find_each do |lfs_file|
next if lfs_file.file.file.exists?
lfs_files_deleted += 1
p "LFS file with ID #{lfs_file.id} and path #{lfs_file.file.path} is missing."
# lfs_file.lfs_objects_projects.destroy_all # Uncomment to delete parent records
# lfs_file.destroy # Uncomment to destroy the LFS object reference
end
p "Count of identified/destroyed invalid references: #{lfs_files_deleted}"
```
This script identifies all missing LFS objects in the database. Before deleting any records:
- It first prints information about missing files for verification.
- The commented lines prevent accidental deletion. If you uncomment them, the script deletes the
identified records.
- The script automatically prints a final count of deleted records for comparison.
### LFS commands fail on TLS v1.3 server
If you configure GitLab to [disable TLS v1.2](https://docs.gitlab.com/omnibus/settings/nginx.html)

View File

@ -41609,6 +41609,8 @@ Represents a Status widget definition.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgetdefinitionstatusallowedstatuses"></a>`allowedStatuses` {{< icon name="warning-solid" >}} | [`[WorkItemStatus!]`](#workitemstatus) | **Introduced** in GitLab 17.8. **Status**: Experiment. Allowed statuses for the work item type. |
| <a id="workitemwidgetdefinitionstatusdefaultclosedstatus"></a>`defaultClosedStatus` {{< icon name="warning-solid" >}} | [`WorkItemStatus`](#workitemstatus) | **Introduced** in GitLab 18.0. **Status**: Experiment. Default status for the `Closed` state for given work item type. |
| <a id="workitemwidgetdefinitionstatusdefaultopenstatus"></a>`defaultOpenStatus` {{< icon name="warning-solid" >}} | [`WorkItemStatus`](#workitemstatus) | **Introduced** in GitLab 18.0. **Status**: Experiment. Default status for the `Open` state for given work item type. |
| <a id="workitemwidgetdefinitionstatustype"></a>`type` | [`WorkItemWidgetType!`](#workitemwidgettype) | Widget type. |
### `WorkItemWidgetDefinitionWeight`

View File

@ -252,7 +252,7 @@ spec:
Based on the policy for inactive issues, this is now being closed.
If this issue requires further attention, please reopen this issue.'
If this issue requires further attention, reopen this issue.'
---
```

View File

@ -143,6 +143,8 @@ To troubleshoot this error, verify that:
- The `project`, `job`, and `ref` combination exists and results in the desired dependency.
- Any variables in use evaluate to the correct values.
If you use the `CI_JOB_TOKEN`, add the token to the project's [allowlist](ci_job_token.md#control-job-token-access-to-your-project) to pull artifacts from a different project.
### For a job configured with `needs:pipeline:job`
The `could not retrieve the needed artifacts.` error can happen for a job using

View File

@ -54,3 +54,24 @@ Only trigger multi-project pipelines with tag names that do not match branch nam
In GitLab 15.9 and later, CI/CD job tokens are scoped to the project that the pipeline executes under. Therefore, the job token in a downstream pipeline cannot be used to access an upstream project by default.
To resolve this, [add the downstream project to the job token scope allowlist](../jobs/ci_job_token.md#add-a-group-or-project-to-the-job-token-allowlist).
## Error: `needs:need pipeline should be a string`
When using [`needs:pipeline:job`](../yaml/_index.md#needspipelinejob) with dynamic child pipelines,
you might receive this error:
```plaintext
Unable to create pipeline
- jobs:<job_name>:needs:need pipeline should be a string
```
This error occurs when a pipeline ID is parsed as an integer instead of a string.
To fix this, enclose the pipeline ID in quotes:
```yaml
rspec:
needs:
- pipeline: "$UPSTREAM_PIPELINE_ID"
job: dependency-job
artifacts: true
```

View File

@ -234,7 +234,7 @@ This approach uses a real cloud license through CustomersDot, providing the most
### Future improvements
> **Note:** There are ongoing plans to streamline the configuration of AI Gateway in development environments to reduce manual setup steps. In the future, we aim to automate this process as part of the GDK setup. For now, please follow the manual configuration steps described above.
> **Note:** There are ongoing plans to streamline the configuration of AI Gateway in development environments to reduce manual setup steps. In the future, we aim to automate this process as part of the GDK setup. For now, follow the manual configuration steps described above.
## Setting up Duo on your GitLab.com staging account

View File

@ -82,7 +82,7 @@ you find a solution.
| Requests take too long to appear in UI | Consider restarting Sidekiq by running `gdk restart rails-background-jobs`. If that doesn't work, try `gdk kill` and then `gdk start`. Alternatively, you can bypass Sidekiq entirely. To do that temporary alter `Llm::CompletionWorker.perform_async` statements with `Llm::CompletionWorker.perform_inline` |
| There is no Chat button in GitLab UI when GDK is running on non-SaaS mode | You do not have cloud connector access token record or seat assigned. To create cloud connector access record, in rails console put following code: `CloudConnector::Access.new(data: { available_services: [{ name: "duo_chat", serviceStartTime: ":date_in_the_future" }] }).save`. |
Please, see also the section on [error codes](#interpreting-gitlab-duo-chat-error-codes) where you can read about codes
For more information, see [interpreting GitLab Duo Chat error codes](#interpreting-gitlab-duo-chat-error-codes).
that Chat sends to assist troubleshooting.
## Contributing to GitLab Duo Chat
@ -161,9 +161,9 @@ required parameters of the prompt and sending them to AI gateway. AI gateway is
selecting Chat tools that are available for user based on their subscription and addon.
When LLM selects the tool to use, this tool is executed on the Rails side. Tools use different endpoint to make
a request to AI gateway. When you add a new tool, please take into account that AI gateway works with different clients
and GitLab applications that have different versions. That means that old versions of GitLab won't know about a new tool,
please contact Duo Chat team if you want to add a new tool. We're working on long-term solution for this [problem](https://gitlab.com/gitlab-org/gitlab/-/issues/466247).
a request to AI gateway. When you add a new tool, take into account that AI gateway works with different clients
and GitLab applications that have different versions. That means that old versions of GitLab won't know about a new tool.
If you want to add a new tool, contact the Duo Chat team. We're working on long-term solution for this [problem](https://gitlab.com/gitlab-org/gitlab/-/issues/466247).
#### Changes in AI gateway
@ -214,7 +214,7 @@ Duo Chat supports multiple conversations. Each conversation is represented by a
- `id`: The `id` is required when replying to a thread.
- `conversation_type`: This allows for distinguishing between the different available Duo Chat conversation types. See the [thread conversation types list](../../api/graphql/reference/_index.md#aiconversationsthreadsconversationtype).
- If your feature needs its own conversation type, please contact the Duo Chat team.
- If your feature needs its own conversation type, contact the Duo Chat team.
If your feature requires calling GraphQL API directly, the following queries and mutations are available, for which you **must** specify the `conversation_type`.
@ -345,7 +345,7 @@ To view the results of these tests, open the `e2e:test-on-omnibus-ee` child pipe
The `ai-gateway` job activates a cloud license and then assigns a Duo Pro seat to a test user, before the tests are run.
For further information, please refer to the [GitLab QA documentation](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#aigateway-scenarios)
For more information, see [AiGateway Scenarios](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#aigateway-scenarios).
## GraphQL Subscription
@ -411,7 +411,7 @@ Examples of GraphQL Subscriptions in a Vue component:
},
```
Please keep in mind that the clientSubscriptionId must be unique for every request. Reusing a clientSubscriptionId will cause several unwanted side effects in the subscription responses.
Keep in mind that the `clientSubscriptionId` must be unique for every request. Reusing a `clientSubscriptionId` will cause several unwanted side effects in the subscription responses.
### Duo Chat GraphQL queries
@ -586,7 +586,7 @@ Follow the
to evaluate GitLab Duo Chat changes locally. The prompt library documentation is
the single source of truth and should be the most up-to-date.
Please, see the video ([internal link](https://drive.google.com/file/d/1X6CARf0gebFYX4Rc9ULhcfq9LLLnJ_O-)) that covers the full setup.
See the video ([internal link](https://drive.google.com/file/d/1X6CARf0gebFYX4Rc9ULhcfq9LLLnJ_O-)) that covers the full setup.
### (Deprecated) Issue and epic experiments
@ -705,7 +705,7 @@ GitLab Duo Chat has error codes with specified meanings to assist in debugging.
See the [GitLab Duo Chat troubleshooting documentation](../../user/gitlab_duo_chat/troubleshooting.md) for a list of all GitLab Duo Chat error codes.
When developing for GitLab Duo Chat, please include these error codes when returning an error and [document them](../../user/gitlab_duo_chat/troubleshooting.md), especially for user-facing errors.
When developing for GitLab Duo Chat, include these error codes when returning an error and [document them](../../user/gitlab_duo_chat/troubleshooting.md), especially for user-facing errors.
### Error Code Format

View File

@ -578,7 +578,7 @@ You can use it either for personal or business websites, such as portfolios, doc
GitLab Runner runs jobs and sends the results to GitLab.
GitLab CI/CD is the open-source continuous integration service included with GitLab that coordinates the testing. The old name of this project was `GitLab CI Multi Runner` but please use `GitLab Runner` (without CI) from now on.
GitLab CI/CD is the open-source continuous integration service included with GitLab that coordinates the testing. The old name of this project was `GitLab CI Multi Runner`, but you should use `GitLab Runner` (without CI) from now on.
#### GitLab Shell

View File

@ -77,8 +77,7 @@ docker exec gitlab cat /etc/gitlab/initial_root_password
{{< alert type="note" >}}
If you receive `cat: /etc/gitlab/initialize_root_password: No such file or directory`,
please wait for a bit for GitLab to boot and try again.
If you receive `cat: /etc/gitlab/initialize_root_password: No such file or directory`, wait for a bit for GitLab to boot and try again.
{{< /alert >}}

View File

@ -830,7 +830,7 @@ class to handle the connection.
As the Unified Backup CLI code is in a separate gem, the main codebase also contains specs to ensure the required views
return the information needed by the tool. This ensures a "contract" between the two codebases.
In case any of the columns needed by this vew needs to change, please follow those steps:
In case any of the columns needed by this vew needs to change, follow those steps:
- To drop a column
- Coordinate with Durability team (responsible for the Unified Backup) and Gitaly (responsible for `gitaly-backup`)

View File

@ -213,7 +213,7 @@ database health check framework. For more details, see
#### How to disable/enable autovacuum indicator on tables
As of GitLab 18.0, this health indicator is enabled by default. To disable it, please run the following command on the rails console:
As of GitLab 18.0, this health indicator is enabled by default. To disable it, run the following command on the rails console:
```ruby
Feature.disable(:batched_migrations_health_status_autovacuum)

View File

@ -370,4 +370,4 @@ Additionally, to view the executed ClickHouse queries in web interactions, on th
### Getting help
For additional information or specific questions, please reach out to the ClickHouse Datastore working group in the `#f_clickhouse` Slack channel, or mention `@gitlab-org/maintainers/clickhouse` in a comment on GitLab.com.
For additional information or specific questions, reach out to the ClickHouse Datastore working group in the `#f_clickhouse` Slack channel, or mention `@gitlab-org/maintainers/clickhouse` in a comment on GitLab.com.

View File

@ -451,7 +451,7 @@ allowed.
### Dropping a `NOT NULL` constraint with a check constraint on the column
First, please verify there's a constraint in place on the column. You can do this in several ways:
First, verify there's a constraint in place on the column. You can do this in several ways:
- Query the [`Gitlab::Database::PostgresConstraint`](https://gitlab.com/gitlab-org/gitlab/-/blob/71892a3c97f52ddcef819dd210ab32864e90c85c/lib/gitlab/database/postgres_constraint.rb) view in rails console
- Use `psql` to check the table itself: `\d+ table_name`
@ -468,7 +468,7 @@ CREATE TABLE labels (
{{< alert type="note" >}}
The milestone number is just an example. Please use the correct version.
The milestone number is just an example. Use the correct version.
{{< /alert >}}
@ -535,7 +535,7 @@ CREATE TABLE labels (
{{< alert type="note" >}}
The milestone number is just an example. Please use the correct version.
The milestone number is just an example. Use the correct version.
{{< /alert >}}

View File

@ -27,7 +27,7 @@ than 100 SQL queries and no exceptions are made for this rule.
## Pipeline Stability
If specs start getting a query limit error in default branch pipelines, please follow the [instruction](#disable-query-limiting) to disable the query limit.
If specs start getting a query limit error in default branch pipelines, follow the [instruction](#disable-query-limiting) to disable the query limit.
Disabling the limit should always associate and prioritize an issue, so the excessive amount of queries can be investigated.
## Disable query limiting

View File

@ -69,4 +69,4 @@ to prevent these in testing, sometimes they happen.
## Adding a required stop
If you plan to introduce a change the falls into one of the above scenarios,
please refer to [adding required stops](../avoiding_required_stops.md#adding-required-stops).
see [adding required stops](../avoiding_required_stops.md#adding-required-stops).

View File

@ -9,7 +9,7 @@ title: Pinia
**[Pilot Phase](https://gitlab.com/gitlab-org/gitlab/-/issues/479279)**: Adopt Pinia with caution.
This is a new technology at GitLab and we might not have all the necessary precautions and best practices in place yet.
If you're considering using Pinia please drop a message in the `#frontend` internal Slack channel for evaluation.
If you're considering using Pinia, drop a message in the `#frontend` internal Slack channel for evaluation.
{{< /alert >}}

View File

@ -69,7 +69,7 @@ If you're still uncertain, prefer using Apollo before Pinia.
**[Pilot Phase](https://gitlab.com/gitlab-org/gitlab/-/issues/479279)**: Adopt Pinia with caution.
This is a new technology at GitLab and we might not have all the necessary precautions and best practices in place yet.
If you're considering using Pinia please drop a message in the `#frontend` internal Slack channel for evaluation.
If you're considering using Pinia, drop a message in the `#frontend` internal Slack channel for evaluation.
{{< /alert >}}
@ -108,7 +108,7 @@ However there may be cases when it's OK to combine these two to seek specific be
- If there's a significant percentage of client-side state that would be best managed in Pinia.
- If domain-specific concerns warrant Apollo for cohesive GraphQL requests within a component.
If you have to use both Apollo and Pinia, please follow these rules:
If you have to use both Apollo and Pinia, follow these rules:
- **Never use Apollo Client in Pinia stores**. Apollo Client should only be consumed within a Vue component or a [composable](vue.md#composables).
- Do not sync data between Apollo and Pinia.

View File

@ -11,7 +11,7 @@ The policy used is based on the subject's class name - so `Ability.allowed?(user
The Ruby gem source is available in the [declarative-policy](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy) GitLab project.
For information about naming and conventions, please refer to [Permission conventions page](permissions/conventions.md).
For information about naming and conventions, see [permission conventions](permissions/conventions.md).
## Managing Permission Rules

View File

@ -127,7 +127,7 @@ Refer to [Track and Propose Sessions for Python Learning Group](https://gitlab.c
- **Bi-weekly sessions** for code review and discussion, led by experienced Python developers.
- These sessions are designed to help you improve your Python skills through practical feedback.
- Please feel free to add the office hours to your calendar.
- Feel free to add the office hours to your calendar.
---
@ -141,7 +141,7 @@ Add any uploaded videos to the [Python Resources](https://www.youtube.com/playli
### Mentorship Process
1:1 mentorship for Python is possible and encouraged. For more information on how to get started with a mentor, please refer to the [GitLab Mentoring Handbook](https://handbook.gitlab.com/handbook/engineering/careers/mentoring/#mentoring).
1:1 mentorship for Python is possible and encouraged. For more information on how to get started with a mentor, see the [GitLab Mentoring Handbook](https://handbook.gitlab.com/handbook/engineering/careers/mentoring/#mentoring).
---

View File

@ -11,7 +11,7 @@ GitLab standard [code review guidelines](../code_review.md#approval-guidelines)
There are two main approaches to set up a Python code review process at GitLab:
1. **Established Projects:** Larger Python projects typically have their own dedicated pool of reviewers through reviewer-roulette. To set this up, please refer to [Setting Up Reviewer Roulette](#setting-up-reviewer-roulette).
1. **Established Projects:** Larger Python projects typically have their own dedicated pool of reviewers through reviewer-roulette. To set this up, see [Setting Up Reviewer Roulette](#setting-up-reviewer-roulette).
1. **Smaller Projects:** For projects with fewer contributors, we maintain a shared pool of Python reviewers across GitLab.
### Setting Up Reviewer Roulette
@ -40,7 +40,7 @@ When a merge request is created, Review Roulette will randomly select qualified
### Additional recommendations
Please refer to [the documentation](../code_review.md#reviewer-roulette)
For more information, see [reviewer roulette](../code_review.md#reviewer-roulette)
### Ask for help
@ -136,6 +136,6 @@ When reviewing Python code at GitLab, consider the following areas:
### Backward Compatibility Requirements
When maintaining customer-facing services, maintainers must ensure backward compatibility across supported GitLab versions.
Please, refer to the GitLab [Statement of Support](https://about.gitlab.com/support/statement-of-support/#version-support)
See the GitLab [Statement of Support](https://about.gitlab.com/support/statement-of-support/#version-support)
and Python [deployment guidelines](deployment.md#versioning).
Before merging changes, verify that they maintain compatibility with all supported versions to prevent disruption for users on different GitLab releases.

View File

@ -286,7 +286,7 @@ Ensure you **always** set a TTL for keys when using this class
as it does not set a default TTL, unlike `Rails.cache` whose default TTL
[is 8 hours](https://gitlab.com/gitlab-org/gitlab/-/blob/a3e435da6e9f7c98dc05eccb1caa03c1aed5a2a8/lib/gitlab/redis/cache.rb#L26). Consider using an 8 hour TTL for general caching, this matches a workday and would mean that a user would generally only have one cache-miss per day for the same content.
When you anticipate adding a large workload to the cache or are in doubt about its production impact, please reach out to [`#g_durability`](https://gitlab.enterprise.slack.com/archives/C07U8G0LHEH).
When you anticipate adding a large workload to the cache or are in doubt about its production impact, reach out to [`#g_durability`](https://gitlab.enterprise.slack.com/archives/C07U8G0LHEH).
`Gitlab::Redis::SharedState` [will not be configured with a key eviction policy](https://docs.gitlab.com/omnibus/settings/redis/#setting-the-redis-cache-instance-as-an-lru).
Use this class for data that cannot be regenerated and is expected to be persisted until its set expiration time.

View File

@ -10,7 +10,7 @@ in the [CycloneDX Property Taxonomy](https://github.com/CycloneDX/cyclonedx-prop
{{< alert type="note" >}}
Before making changes to this file, please reach out to the threat insights engineering team,
Before making changes to this file, reach out to the threat insights engineering team,
`@gitlab-org/govern/threat-insights`.
{{< /alert >}}

View File

@ -388,7 +388,7 @@ end
{{< alert type="warning" >}}
In case you want to remove the middleware for a worker, please set the strategy to `:deprecated` to disable it and wait until
In case you want to remove the middleware for a worker, set the strategy to `:deprecated` to disable it and wait until
a required stop before removing it completely. That ensures that all paused jobs are resumed correctly.
{{< /alert >}}

View File

@ -1944,7 +1944,7 @@ Inside the terminal, where capybara is running, you can also execute `next` whic
### Improving execution time on the GDK
Running the Jest test suite, the number of workers is set to use 60% of the available cores of your machine; this results in faster execution times but higher memory consumption. For more benchmarks on how this works please refer to this [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/456885).
Running the Jest test suite, the number of workers is set to use 60% of the available cores of your machine; this results in faster execution times but higher memory consumption. For more benchmarks on how this works, see [issue 456885](https://gitlab.com/gitlab-org/gitlab/-/issues/456885).
### Updating ChromeDriver

View File

@ -215,7 +215,7 @@ Unless you really need to have a test disabled very fast (`< 10min`), consider [
To quickly quarantine a test without having to open a merge request and wait for pipelines,
you can follow [the fast quarantining process](https://gitlab.com/gitlab-org/quality/engineering-productivity/fast-quarantine/-/tree/main/#fast-quarantine-a-test).
**Please always proceed** to [open a long-term quarantine merge request](#long-term-quarantine) after fast-quarantining a test! This is to ensure the fast-quarantined test was correctly fixed by running tests from the CI/CD pipelines (which are not run in the context of the fast-quarantine project).
**Always proceed** to [open a long-term quarantine merge request](#long-term-quarantine) after fast-quarantining a test! This is to ensure the fast-quarantined test was correctly fixed by running tests from the CI/CD pipelines (which are not run in the context of the fast-quarantine project).
##### Long-term quarantine
@ -250,7 +250,7 @@ bin/rspec --tag ~quarantine
bin/rspec --tag \~quarantine
```
Also, please ensure that:
Also, ensure that:
1. The ~"quarantine" label is present on the merge request.
1. The MR description mentions the flaky test issue with [the usual terms to link a merge request to an issue](https://gitlab.com/gitlab-org/quality/triage-ops/-/blob/8b8621ba5c0db3c044a771ebf84887a0a07353b3/triage/triage/related_issue_finder.rb#L8-18).

View File

@ -32,7 +32,7 @@ For Secret name, specify a descriptive name for the secret. Secret names must co
For GitLab Container Registry username, specify your GitLab Container Registry username.
For GitLab Container Registry access token, specify your GitLab Container Registry access token. To follow principles of least privilege, please create a Group Access Token with the Guest role and only the read_registry scope.
For GitLab Container Registry access token, specify your GitLab Container Registry access token. To follow principles of least privilege, create a Group Access Token with the Guest role and only the `read_registry` scope.
On the Step 3: Specify a destination page, for Amazon ECR repository prefix, specify the repository namespace to use when caching images pulled from the source public registry and then choose Next.

View File

@ -56,7 +56,7 @@ The normal change process requires the change request to be approved before the
Use the `gitlab-ci-workflow1.yml` sample pipeline in the solution repository as a starting point.
Check below for the steps to enable the automatic change creation and pass the change attributes through the pipeline.
Note: for more detailed instructions, please see [the ServiceNow documentation](https://www.servicenow.com/docs/bundle/yokohama-it-service-management/page/product/enterprise-dev-ops/task/automate-devops-change-request.html)
Note: for more detailed instructions, see [Automate DevOps change request creation](https://www.servicenow.com/docs/bundle/yokohama-it-service-management/page/product/enterprise-dev-ops/task/automate-devops-change-request.html).
Below are the high-level steps:
@ -96,7 +96,7 @@ Since this is add-on to the ServiceNow DevOps Change Velocity, the above setup s
Use the `gitlab-ci-workflow2.yml` sample pipeline in this repository as an example.
1. Specify the image to use in the job. Please update the image version as needed.
1. Specify the image to use in the job. Update the image version as needed.
```yaml
image: servicenowdocker/sndevops:5.0.0

View File

@ -20,7 +20,7 @@ The instructions include a sample [**React Native**](https://reactnative.dev) ap
## Getting Started
Please follow the steps below on how to use this React Native Mobile App sample project to jump start your mobile application delivery using GitLab.
Follow the steps below on how to use this React Native Mobile App sample project to jump start your mobile application delivery using GitLab.
### Download the Solution Component
@ -34,9 +34,9 @@ Please follow the steps below on how to use this React Native Mobile App sample
1. Create a new GitLab project to host this Snyk CI/CD catalog project
1. Copy the provided files into your project
1. Configure the required CI/CD variables in your project settings
1. Make sure the project is marked as a CI/CD catalog project. Please see [the GitLab guide here](../../ci/components/_index.md#publish-a-component-project) on how to publish a component project.
1. Make sure the project is marked as a CI/CD catalog project. For more information, see [publish a component project](../../ci/components/_index.md#publish-a-component-project).
> There is a public GitLab Snyk component on GitLab.com, if you are on SaaS, and you are able to access the public GitLab Snyk component, to set up your own Snyk CI/CD catalog project is not needed, and you can follow the documentation in the public GitLab Snyk component on GitLab.com to use the component directly.
- Please use the Change Control Workflow with ServiceNow solution pack to configure the DevOps Change Velocity integration with GitLab to automate change request creation in ServiceNow for deployments require change controls. [Here](../../solutions/components/integrated_servicenow.md) is the documentation link to the change control workflow with ServiceNow solution component, and please work with your account team to get an access code to download the Change Control Workflow with ServiceNow solution package.
- Use the Change Control Workflow with ServiceNow solution pack to configure the DevOps Change Velocity integration with GitLab to automate change request creation in ServiceNow for deployments require change controls. [Here](../../solutions/components/integrated_servicenow.md) is the documentation link to the change control workflow with ServiceNow solution component, and work with your account team to get an access code to download the Change Control Workflow with ServiceNow solution package.
- Copy the CI YAML files into your project:
- `.gitlab-ci.yml`
- `build-android.yml` in the pipelines directory. You will need to update the file path in `.gitlab-ci.yml` if the `build-android.yml` file is put in a different location other than /pipeline because the main `.gitlab-ci.yml` file references the `build-android.yml` file for the build job.
@ -53,7 +53,7 @@ Please follow the steps below on how to use this React Native Mobile App sample
image: reactnativecommunity/react-native-android
```
- Configure the required CI/CD variables in your project settings. Please see below for how the pipeline works.
- Configure the required CI/CD variables in your project settings. See the following section to learn how the pipeline works.
## How the Pipeline Works
@ -89,13 +89,13 @@ The pipeline consists of the following stages and jobs:
## Prerequisites
There are multiple third party tools integrated in the mobile pipeline workflow. In order to successfully run the pipeline, please make sure the following prerequisites are in place.
There are multiple third party tools integrated in the mobile pipeline workflow. In order to successfully run the pipeline, make sure the following prerequisites are in place.
### Snyk Integration using the Component
In order to use the GitLab Snyk CI/CD component for security scans, please make sure your group or project in GitLab is already connected with Snyk, if not, please follow [this tutorial](https://docs.snyk.io/scm-ide-and-ci-cd-integrations/snyk-scm-integrations/gitlab) to configure it.
In order to use the GitLab Snyk CI/CD component for security scans, make sure your group or project in GitLab is already connected with Snyk, if not, follow [this tutorial](https://docs.snyk.io/scm-ide-and-ci-cd-integrations/snyk-scm-integrations/gitlab) to configure it.
In the mobile app project, please add the required variables for the Snyk integration.
In the mobile app project, add the required variables for the Snyk integration.
#### Required CI/CD Variables
@ -112,7 +112,7 @@ DOCKER_AUTH_CONFIG: '{"auths":{"registry.gitlab.com":{"username":"$SNYK_PROJECT_
#### Update the component path
Please update the component path in the `.gitlab-ci.yml` file so that the pipeline can successfully reference the Snyk component.
Update the component path in the `.gitlab-ci.yml` file so that the pipeline can successfully reference the Snyk component.
```yaml
- component: $CI_SERVER_FQDN/gitlab-com/product-accelerator/work-streams/packaging/snyk/snyk@1.0.0 #snky sast scan, this examples uses the component in GitLab the product accelerator group. Please update the path and stage accordingly.
@ -178,4 +178,4 @@ The mobile app project pipeline includes several external configurations and com
## Notes
Please reach out to your account team for obtaining an invitation code to access the solution component and for any additional questions.
Reach out to your account team for obtaining an invitation code to access the solution component and for any additional questions.

View File

@ -54,7 +54,7 @@ We will install GitLab, GitLab AI Gateway and Ollama each in their own separate
| **AI Gateway** | e2-medium | t2.medium | Ubuntu 24 | 20 GB |
| **Ollama** | n1-standard-4 | g4dn.xlarge | Ubuntu 24 | 50 GB |
For details on the [AI Gateway](../../user/gitlab_duo/gateway.md) component and its purpose, please refer to the documentation page.
For more information about the component and its purpose, see [AI Gateway](../../user/gitlab_duo/gateway.md).
```mermaid
flowchart LR
@ -139,7 +139,7 @@ When you host an AI model yourself, you'll also need to choose a serving platfor
In this analogy, the brain part for ChatGPT is the GPT-4 model, while in the Anthropic ecosystem, it's the Claude 3.7 Sonnet model. The serving platform acts as the vital framework that connects the brain to the world, enabling it to "think" and interact effectively.
For further information about supported serving platforms and models, please refer to the documentation for [LLM Serving Platforms](../../administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md) and [Models](../../administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md).
For further information about supported serving platforms and models, see [LLM Serving Platforms](../../administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md) and [Models](../../administration/gitlab_duo_self_hosted/supported_models_and_hardware_requirements.md).
**What is Ollama?**
@ -164,7 +164,7 @@ Designed for simplicity and performance, Ollama empowers users to harness the po
While the official installation guide is available [here](../../install/install_ai_gateway.md), here's a streamlined approach for setting up the AI Gateway. As of January 2025, the image `gitlab/model-gateway:self-hosted-v17.6.0-ee` has been verified to work with GitLab 17.7.
1. Please ensure that ...
1. Ensure that ...
- TCP port 5052 to the API Gateway VM is permitted (check security group configuration)
- You replace `GITLAB_DOMAIN` with the domain name to YOUR instance of GitLab in the following code snippet:

View File

@ -953,7 +953,6 @@ excluded_attributes:
- :group_id
resource_label_events:
- :reference
- :reference_html
- :epic_id
- :issue_id
- :merge_request_id

View File

@ -9,7 +9,8 @@ module Gitlab
extend ::Gitlab::Utils::Override
SNOWPLOW_NAMESPACE = 'gl'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT = 'events-stg.gitlab.net'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT = 'events.gitlab.net'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG = 'events-stg.gitlab.net'
DEDICATED_APP_ID = 'gitlab_dedicated'
SELF_MANAGED_APP_ID = 'gitlab_sm'
@ -59,6 +60,8 @@ module Gitlab
def hostname
if Gitlab::CurrentSettings.snowplow_enabled?
Gitlab::CurrentSettings.snowplow_collector_hostname
elsif Feature.enabled?(:use_staging_endpoint_for_product_usage_events, :instance)
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG
else
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT
end

View File

@ -60048,6 +60048,9 @@ msgstr ""
msgid "Target project cannot be equal to source project"
msgstr ""
msgid "Target project must belong to source project organization"
msgstr ""
msgid "Target roles"
msgstr ""
@ -72841,6 +72844,9 @@ msgstr ""
msgid "must match the parent organization's ID"
msgstr ""
msgid "must match the root project organization's ID"
msgstr ""
msgid "must not be a placeholder email"
msgstr ""

View File

@ -82,41 +82,41 @@
"@snowplow/browser-plugin-timezone": "^3.24.2",
"@snowplow/browser-tracker": "^3.24.2",
"@sourcegraph/code-host-integration": "0.0.95",
"@tiptap/core": "^2.10.3",
"@tiptap/extension-blockquote": "^2.10.3",
"@tiptap/extension-bold": "^2.10.3",
"@tiptap/extension-bubble-menu": "^2.10.3",
"@tiptap/extension-bullet-list": "^2.10.3",
"@tiptap/extension-code": "^2.10.3",
"@tiptap/extension-code-block": "^2.10.3",
"@tiptap/extension-code-block-lowlight": "^2.10.3",
"@tiptap/extension-document": "^2.10.3",
"@tiptap/extension-dropcursor": "^2.10.3",
"@tiptap/extension-gapcursor": "^2.10.3",
"@tiptap/extension-hard-break": "^2.10.3",
"@tiptap/extension-heading": "^2.10.3",
"@tiptap/extension-highlight": "^2.10.3",
"@tiptap/extension-history": "^2.10.3",
"@tiptap/extension-horizontal-rule": "^2.10.3",
"@tiptap/extension-image": "^2.10.3",
"@tiptap/extension-italic": "^2.10.3",
"@tiptap/extension-link": "^2.10.3",
"@tiptap/extension-list-item": "^2.10.3",
"@tiptap/extension-ordered-list": "^2.10.3",
"@tiptap/extension-paragraph": "^2.10.3",
"@tiptap/extension-strike": "^2.10.3",
"@tiptap/extension-subscript": "^2.10.3",
"@tiptap/extension-superscript": "^2.10.3",
"@tiptap/extension-table": "^2.10.3",
"@tiptap/extension-table-cell": "^2.10.3",
"@tiptap/extension-table-header": "^2.10.3",
"@tiptap/extension-table-row": "^2.10.3",
"@tiptap/extension-task-item": "^2.10.3",
"@tiptap/extension-task-list": "^2.10.3",
"@tiptap/extension-text": "^2.10.3",
"@tiptap/pm": "^2.10.3",
"@tiptap/suggestion": "^2.10.3",
"@tiptap/vue-2": "^2.10.3",
"@tiptap/core": "^2.11.7",
"@tiptap/extension-blockquote": "^2.11.7",
"@tiptap/extension-bold": "^2.11.7",
"@tiptap/extension-bubble-menu": "^2.11.7",
"@tiptap/extension-bullet-list": "^2.11.7",
"@tiptap/extension-code": "^2.11.7",
"@tiptap/extension-code-block": "^2.11.7",
"@tiptap/extension-code-block-lowlight": "^2.11.7",
"@tiptap/extension-document": "^2.11.7",
"@tiptap/extension-dropcursor": "^2.11.7",
"@tiptap/extension-gapcursor": "^2.11.7",
"@tiptap/extension-hard-break": "^2.11.7",
"@tiptap/extension-heading": "^2.11.7",
"@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-history": "^2.11.7",
"@tiptap/extension-horizontal-rule": "^2.11.7",
"@tiptap/extension-image": "^2.11.7",
"@tiptap/extension-italic": "^2.11.7",
"@tiptap/extension-link": "^2.11.7",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-ordered-list": "^2.11.7",
"@tiptap/extension-paragraph": "^2.11.7",
"@tiptap/extension-strike": "^2.11.7",
"@tiptap/extension-subscript": "^2.11.7",
"@tiptap/extension-superscript": "^2.11.7",
"@tiptap/extension-table": "^2.11.7",
"@tiptap/extension-table-cell": "^2.11.7",
"@tiptap/extension-table-header": "^2.11.7",
"@tiptap/extension-table-row": "^2.11.7",
"@tiptap/extension-task-item": "^2.11.7",
"@tiptap/extension-task-list": "^2.11.7",
"@tiptap/extension-text": "^2.11.7",
"@tiptap/pm": "^2.11.7",
"@tiptap/suggestion": "^2.11.7",
"@tiptap/vue-2": "^2.11.7",
"@vue/apollo-components": "^4.0.0-beta.4",
"@vue/apollo-option": "^4.0.0-beta.4",
"apollo-upload-client": "15.0.0",

View File

@ -1,181 +1,31 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { visitUrl } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
import RunnerHeaderActions from '~/ci/runner/components/runner_header_actions.vue';
import RunnerDetails from '~/ci/runner/components/runner_details.vue';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import RunnersJobs from '~/ci/runner/components/runner_jobs.vue';
import runnerQuery from '~/ci/runner/graphql/show/runner.query.graphql';
import { shallowMount } from '@vue/test-utils';
import AdminRunnerShowApp from '~/ci/runner/admin_runner_show/admin_runner_show_app.vue';
import { captureException } from '~/ci/runner/sentry_utils';
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import { runnerData } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
const mockRunner = runnerData.data.runner;
const mockRunnerGraphqlId = mockRunner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
const mockRunnerSha = mockRunner.shortSha;
const mockRunnersPath = '/admin/runners';
Vue.use(VueApollo);
Vue.use(VueRouter);
const mockRunnerId = '1';
const mockRunnersPath = '/runners';
const mockEditPath = '/runners/1/edit';
describe('AdminRunnerShowApp', () => {
let wrapper;
let mockRunnerQuery;
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerHeaderActions = () => wrapper.findComponent(RunnerHeaderActions);
const findRunnerDetailsTabs = () => wrapper.findComponent(RunnerDetailsTabs);
const findRunnersJobs = () => wrapper.findComponent(RunnersJobs);
const mockRunnerQueryResult = (runner = {}) => {
mockRunnerQuery = jest.fn().mockResolvedValue({
data: {
runner: { ...mockRunner, ...runner },
},
});
};
const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
wrapper = mountFn(AdminRunnerShowApp, {
apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]),
beforeEach(() => {
wrapper = shallowMount(AdminRunnerShowApp, {
propsData: {
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
...props,
editPath: mockEditPath,
},
...options,
});
return waitForPromises();
};
afterEach(() => {
mockRunnerQuery.mockReset();
});
describe('When showing runner details', () => {
beforeEach(async () => {
mockRunnerQueryResult();
await createComponent({ mountFn: mountExtended });
});
it('expect GraphQL ID to be requested', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
it('displays the runner header', () => {
expect(findRunnerHeader().text()).toContain(`#${mockRunnerId} (${mockRunnerSha})`);
});
it('displays the runner edit and pause buttons', () => {
expect(findRunnerHeaderActions().props()).toEqual({
runner: mockRunner,
editPath: mockRunner.editAdminUrl,
});
});
it('shows runner details', () => {
expect(findRunnerDetailsTabs().props('runner')).toEqual(mockRunner);
});
it('shows basic runner details', async () => {
await createComponent({
mountFn: mountExtended,
stubs: {
HelpPopover: {
template: '<div/>',
},
},
});
const expected = `Description My Runner
Last contact Never contacted
Configuration Runs untagged jobs
Maximum job timeout None
Token expiry Never expires
Tags None`.replace(/\s+/g, ' ');
expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected);
});
describe('when runner is deleted', () => {
beforeEach(async () => {
await createComponent({
mountFn: mountExtended,
});
});
it('redirects to the runner list page', () => {
findRunnerHeaderActions().vm.$emit('deleted', { message: 'Runner deleted' });
expect(saveAlertToLocalStorage).toHaveBeenCalledWith({
message: 'Runner deleted',
variant: VARIANT_SUCCESS,
});
expect(visitUrl).toHaveBeenCalledWith(mockRunnersPath);
});
});
});
describe('When loading', () => {
it('does not show runner details', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnerDetails().exists()).toBe(false);
});
it('does not show runner jobs', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnersJobs().exists()).toBe(false);
});
});
describe('When there is an error', () => {
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
component: 'AdminRunnerShowApp',
});
});
it('error is shown to the user', () => {
expect(createAlert).toHaveBeenCalled();
it('passes the correct props', () => {
expect(findRunnerDetailsTabs().props()).toMatchObject({
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
editPath: mockEditPath,
});
});
});

View File

@ -41,6 +41,12 @@ describe('RunnerDetails', () => {
});
};
it('shows no content if no runner is provided', () => {
createComponent();
expect(wrapper.text()).toBe('');
});
describe('Details tab', () => {
describe.each`
field | runner | expectedValue

View File

@ -1,57 +1,67 @@
import Vue from 'vue';
import { GlTab, GlTabs } from '@gitlab/ui';
import VueRouter from 'vue-router';
import VueApollo from 'vue-apollo';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { JOBS_ROUTE_PATH, I18N_DETAILS, I18N_JOBS } from '~/ci/runner/constants';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import runnerQuery from '~/ci/runner/graphql/show/runner.query.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { visitUrl } from '~/lib/utils/url_utility';
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
import { captureException } from '~/ci/runner/sentry_utils';
import { JOBS_ROUTE_PATH } from '~/ci/runner/constants';
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
import RunnerHeaderActions from '~/ci/runner/components/runner_header_actions.vue';
import RunnerDetails from '~/ci/runner/components/runner_details.vue';
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import { runnerData } from '../mock_data';
// Vue Test Utils `stubs` option does not stub components mounted
// in <router-view>. Use mocking instead:
jest.mock('~/ci/runner/components/runner_jobs.vue', () => {
const { props } = jest.requireActual('~/ci/runner/components/runner_jobs.vue').default;
return {
props,
render() {},
};
});
jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/ci/runner/components/runner_managers_detail.vue', () => {
const { props } = jest.requireActual('~/ci/runner/components/runner_managers_detail.vue').default;
return {
props,
render() {},
};
});
const mockRunner = runnerData.data.runner;
Vue.use(VueApollo);
Vue.use(VueRouter);
Vue.use(VueApollo);
const mockRunnerId = '1';
const mockRunnersPath = '/runners';
const mockEditPath = '/runners/1/edit';
const mockRunner = runnerData.data.runner;
describe('RunnerDetailsTabs', () => {
let wrapper;
let mockApollo;
let routerPush;
let runnerQueryHandler;
const findTabs = () => wrapper.findComponent(GlTabs);
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerJobs = () => wrapper.findComponent(RunnerJobs);
const findJobCountBadge = () => wrapper.findByTestId('job-count-badge');
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
const findRunnerHeaderActions = () => wrapper.findComponent(RunnerHeaderActions);
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
mockApollo = createMockApollo([[runnerQuery, runnerQueryHandler]]);
const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
wrapper = mountFn(RunnerDetailsTabs, {
apolloProvider: mockApollo,
propsData: {
runner: mockRunner,
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
editPath: mockEditPath,
...props,
},
...options,
});
routerPush = jest.spyOn(wrapper.vm.$router, 'push');
@ -59,10 +69,98 @@ describe('RunnerDetailsTabs', () => {
return waitForPromises();
};
beforeEach(() => {
runnerQueryHandler = jest.fn().mockResolvedValue({
data: {
runner: mockRunner,
},
});
});
it('fetches runner data with the correct ID', async () => {
await createComponent();
expect(runnerQueryHandler).toHaveBeenCalledWith({
id: expect.stringContaining(`/${mockRunnerId}`),
});
});
it('passes the correct props to RunnerHeader', async () => {
await createComponent();
expect(findRunnerHeader().props('runner')).toEqual(mockRunner);
expect(findRunnerHeaderActions().props()).toEqual({
runner: mockRunner,
editPath: mockEditPath,
});
});
it('redirects to runners path when runner is deleted', async () => {
await createComponent();
const message = 'Runner deleted successfully';
findRunnerHeaderActions().vm.$emit('deleted', { message });
expect(saveAlertToLocalStorage).toHaveBeenCalledWith({
message,
variant: 'success',
});
expect(visitUrl).toHaveBeenCalledWith(mockRunnersPath);
});
it('shows an alert when fetching runner data fails', async () => {
const error = new Error('Network error');
runnerQueryHandler.mockRejectedValue(error);
await createComponent();
expect(findRunnerHeader().exists()).toEqual(false);
expect(createAlert).toHaveBeenCalledWith({
message: 'Something went wrong while fetching runner data.',
});
expect(captureException).toHaveBeenCalledWith({
error,
component: 'RunnerDetailsTabs',
});
});
it('does not redirect when runnersPath is not provided', async () => {
await createComponent({
props: {
runnersPath: '',
},
});
const message = 'Runner deleted successfully';
wrapper.findComponent(RunnerHeaderActions).vm.$emit('deleted', { message });
expect(saveAlertToLocalStorage).not.toHaveBeenCalled();
expect(visitUrl).not.toHaveBeenCalled();
});
describe('access help', () => {
it('show popover when showAccessHelp is true', async () => {
await createComponent({
props: {
showAccessHelp: true,
},
});
expect(findHelpPopover().exists()).toBe(true);
});
it('hides popover when showAccessHelp is not added', async () => {
await createComponent();
expect(findHelpPopover().exists()).toBe(false);
});
});
it('shows basic runner details', async () => {
await createComponent({ mountFn: mountExtended });
expect(findRunnerDetails().props('runner')).toBe(mockRunner);
expect(findRunnerDetails().props('runner')).toEqual(mockRunner);
expect(findRunnerJobs().exists()).toBe(false);
});
@ -71,7 +169,7 @@ describe('RunnerDetailsTabs', () => {
await wrapper.vm.$router.push({ path: JOBS_ROUTE_PATH });
expect(findRunnerDetails().exists()).toBe(false);
expect(findRunnerJobs().props('runner')).toBe(mockRunner);
expect(findRunnerJobs().props('runnerId')).toBe(mockRunnerId);
});
it.each`
@ -81,18 +179,14 @@ describe('RunnerDetailsTabs', () => {
${1000} | ${'1,000'}
${1001} | ${'1,000+'}
`('shows runner jobs count', async ({ jobCount, badgeText }) => {
await createComponent({
stubs: {
GlTab,
},
props: {
runner: {
...mockRunner,
jobCount,
},
runnerQueryHandler = jest.fn().mockResolvedValue({
data: {
runner: { ...mockRunner, jobCount },
},
});
await createComponent();
if (!badgeText) {
expect(findJobCountBadge().exists()).toBe(false);
} else {
@ -104,17 +198,16 @@ describe('RunnerDetailsTabs', () => {
createComponent({ mountFn: mountExtended });
await wrapper.vm.$router.push({ path });
expect(findTabs().props('value')).toBe(0);
expect(findRunnerDetails().exists()).toBe(true);
expect(findRunnerJobs().exists()).toBe(false);
});
describe.each`
location | tab | navigatedTo
${'#/details'} | ${I18N_DETAILS} | ${[]}
${'#/details'} | ${I18N_JOBS} | ${[[{ name: 'jobs' }]]}
${'#/jobs'} | ${I18N_JOBS} | ${[]}
${'#/jobs'} | ${I18N_DETAILS} | ${[[{ name: 'details' }]]}
location | tab | navigatedTo
${'#/details'} | ${'Details'} | ${[]}
${'#/details'} | ${'Jobs'} | ${[[{ name: 'jobs' }]]}
${'#/jobs'} | ${'Jobs'} | ${[]}
${'#/jobs'} | ${'Details'} | ${[[{ name: 'details' }]]}
`('When at $location', ({ location, tab, navigatedTo }) => {
beforeEach(async () => {
setWindowLocation(location);

View File

@ -4,6 +4,8 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
import RunnerJobsTable from '~/ci/runner/components/runner_jobs_table.vue';
@ -14,12 +16,13 @@ import { RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/ci/runner/constants';
import runnerJobsQuery from '~/ci/runner/graphql/show/runner_jobs.query.graphql';
import { runnerData, runnerJobsData } from '../mock_data';
import { runnerJobsData } from '../mock_data';
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
const mockRunnerId = '1';
const mockRunnerGraphQLId = convertToGraphQLId(TYPENAME_CI_RUNNER, mockRunnerId);
const mockRunnerWithJobs = runnerJobsData.data.runner;
const mockJobs = mockRunnerWithJobs.jobs.nodes;
@ -37,7 +40,7 @@ describe('RunnerJobs', () => {
wrapper = mountFn(RunnerJobs, {
apolloProvider: createMockApollo([[runnerJobsQuery, mockRunnerJobsQuery]]),
propsData: {
runner: mockRunner,
runnerId: mockRunnerId,
},
stubs: {
CrudComponent,
@ -60,7 +63,7 @@ describe('RunnerJobs', () => {
expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(1);
expect(mockRunnerJobsQuery).toHaveBeenCalledWith({
id: mockRunner.id,
id: mockRunnerGraphQLId,
first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
});
});
@ -73,6 +76,10 @@ describe('RunnerJobs', () => {
await waitForPromises();
});
it('shows count', () => {
expect(findCrudComponent().props('count')).toBe(1);
});
it('Shows jobs', () => {
const jobs = findRunnerJobsTable().props('jobs');
@ -89,7 +96,7 @@ describe('RunnerJobs', () => {
it('A new page is requested', () => {
expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(2);
expect(mockRunnerJobsQuery).toHaveBeenLastCalledWith({
id: mockRunner.id,
id: mockRunnerGraphQLId,
first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
after: 'AFTER_CURSOR',
});
@ -112,8 +119,9 @@ describe('RunnerJobs', () => {
mockRunnerJobsQuery.mockResolvedValueOnce({
data: {
runner: {
id: mockRunner.id,
id: mockRunnerId,
projectCount: 0,
jobCount: 0,
jobs: {
nodes: [],
pageInfo: {
@ -131,6 +139,10 @@ describe('RunnerJobs', () => {
await waitForPromises();
});
it('shows no count', () => {
expect(findCrudComponent().props('count')).toBe('');
});
it('should render empty state', () => {
expect(findEmptyState().exists()).toBe(true);
});
@ -144,6 +156,10 @@ describe('RunnerJobs', () => {
await waitForPromises();
});
it('shows no count', () => {
expect(findCrudComponent().props('count')).toBe('');
});
it('shows an error', () => {
expect(createAlert).toHaveBeenCalled();
});

View File

@ -1,186 +1,32 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { visitUrl } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
import RunnerHeaderActions from '~/ci/runner/components/runner_header_actions.vue';
import RunnerDetails from '~/ci/runner/components/runner_details.vue';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import RunnersJobs from '~/ci/runner/components/runner_jobs.vue';
import runnerQuery from '~/ci/runner/graphql/show/runner.query.graphql';
import { shallowMount } from '@vue/test-utils';
import GroupRunnerShowApp from '~/ci/runner/group_runner_show/group_runner_show_app.vue';
import { captureException } from '~/ci/runner/sentry_utils';
import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import { runnerData } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
const mockRunner = runnerData.data.runner;
const mockRunnerGraphqlId = mockRunner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
const mockRunnerSha = mockRunner.shortSha;
const mockRunnersPath = '/groups/group1/-/runners';
const mockEditGroupRunnerPath = `/groups/group1/-/runners/${mockRunnerId}/edit`;
Vue.use(VueApollo);
Vue.use(VueRouter);
const mockRunnerId = '1';
const mockRunnersPath = '/runners';
const mockEditPath = '/runners/1/edit';
describe('GroupRunnerShowApp', () => {
let wrapper;
let mockRunnerQuery;
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerHeaderActions = () => wrapper.findComponent(RunnerHeaderActions);
const findRunnerDetailsTabs = () => wrapper.findComponent(RunnerDetailsTabs);
const findRunnersJobs = () => wrapper.findComponent(RunnersJobs);
const mockRunnerQueryResult = (runner = {}) => {
mockRunnerQuery = jest.fn().mockResolvedValue({
data: {
runner: { ...mockRunner, ...runner },
},
});
};
const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
wrapper = mountFn(GroupRunnerShowApp, {
apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]),
beforeEach(() => {
wrapper = shallowMount(GroupRunnerShowApp, {
propsData: {
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
editGroupRunnerPath: mockEditGroupRunnerPath,
...props,
editPath: mockEditPath,
},
...options,
});
return waitForPromises();
};
afterEach(() => {
mockRunnerQuery.mockReset();
});
describe('When showing runner details', () => {
beforeEach(async () => {
mockRunnerQueryResult();
await createComponent({ mountFn: mountExtended });
});
it('expect GraphQL ID to be requested', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
it('displays the runner header', () => {
expect(findRunnerHeader().text()).toContain(`#${mockRunnerId} (${mockRunnerSha})`);
});
it('displays the runner buttons', () => {
expect(findRunnerHeaderActions().props()).toEqual({
runner: mockRunner,
editPath: mockEditGroupRunnerPath,
});
});
it('shows runner details', () => {
expect(findRunnerDetailsTabs().props()).toEqual({
runner: mockRunner,
showAccessHelp: true,
});
});
it('shows basic runner details', async () => {
await createComponent({
mountFn: mountExtended,
stubs: {
HelpPopover: {
template: '<div/>',
},
},
});
const expected = `Description My Runner
Last contact Never contacted
Configuration Runs untagged jobs
Maximum job timeout None
Token expiry Never expires
Tags None`.replace(/\s+/g, ' ');
expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected);
});
describe('when runner is deleted', () => {
beforeEach(async () => {
await createComponent({
mountFn: mountExtended,
});
});
it('redirects to the runner list page', () => {
findRunnerHeaderActions().vm.$emit('deleted', { message: 'Runner deleted' });
expect(saveAlertToLocalStorage).toHaveBeenCalledWith({
message: 'Runner deleted',
variant: VARIANT_SUCCESS,
});
expect(visitUrl).toHaveBeenCalledWith(mockRunnersPath);
});
});
});
describe('When loading', () => {
it('does not show runner details', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnerDetails().exists()).toBe(false);
});
it('does not show runner jobs', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnersJobs().exists()).toBe(false);
});
});
describe('When there is an error', () => {
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
component: 'GroupRunnerShowApp',
});
});
it('error is shown to the user', () => {
expect(createAlert).toHaveBeenCalled();
it('passes the correct props', () => {
expect(findRunnerDetailsTabs().props()).toEqual({
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
editPath: mockEditPath,
showAccessHelp: true,
});
});
});

View File

@ -1,145 +1,32 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
import RunnerDetails from '~/ci/runner/components/runner_details.vue';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import RunnersJobs from '~/ci/runner/components/runner_jobs.vue';
import runnerQuery from '~/ci/runner/graphql/show/runner.query.graphql';
import { shallowMount } from '@vue/test-utils';
import ProjectRunnerShowApp from '~/ci/runner/project_runner_show/project_runner_show_app.vue';
import { captureException } from '~/ci/runner/sentry_utils';
import RunnerDetailsTabs from '~/ci/runner/components/runner_details_tabs.vue';
import { runnerData } from '../mock_data';
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
const mockRunnerGraphqlId = mockRunner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
const mockRunnerSha = mockRunner.shortSha;
Vue.use(VueApollo);
Vue.use(VueRouter);
const mockRunnerId = '1';
const mockRunnersPath = '/runners';
const mockEditPath = '/runners/1/edit';
describe('ProjectRunnerShowApp', () => {
let wrapper;
let mockRunnerQuery;
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerDetailsTabs = () => wrapper.findComponent(RunnerDetailsTabs);
const findRunnersJobs = () => wrapper.findComponent(RunnersJobs);
const mockRunnerQueryResult = (runner = {}) => {
mockRunnerQuery = jest.fn().mockResolvedValue({
data: {
runner: { ...mockRunner, ...runner },
},
});
};
const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
wrapper = mountFn(ProjectRunnerShowApp, {
apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]),
beforeEach(() => {
wrapper = shallowMount(ProjectRunnerShowApp, {
propsData: {
runnerId: mockRunnerId,
...props,
runnersPath: mockRunnersPath,
editPath: mockEditPath,
},
...options,
});
return waitForPromises();
};
afterEach(() => {
mockRunnerQuery.mockReset();
});
describe('When showing runner details', () => {
beforeEach(async () => {
mockRunnerQueryResult();
await createComponent({ mountFn: mountExtended });
});
it('expect GraphQL ID to be requested', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
it('displays the runner header', () => {
expect(findRunnerHeader().text()).toContain(`#${mockRunnerId} (${mockRunnerSha})`);
});
it('shows runner details', () => {
expect(findRunnerDetailsTabs().props('runner')).toEqual(mockRunner);
});
it('shows basic runner details', async () => {
await createComponent({
mountFn: mountExtended,
stubs: {
HelpPopover: {
template: '<div/>',
},
},
});
const expected = `Description My Runner
Last contact Never contacted
Configuration Runs untagged jobs
Maximum job timeout None
Token expiry Never expires
Tags None`.replace(/\s+/g, ' ');
expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected);
});
});
describe('When loading', () => {
it('does not show runner details', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnerDetails().exists()).toBe(false);
});
it('does not show runner jobs', () => {
mockRunnerQueryResult();
createComponent();
expect(findRunnersJobs().exists()).toBe(false);
});
});
describe('When there is an error', () => {
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent();
});
it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
component: 'ProjectRunnerShowApp',
});
});
it('error is shown to the user', () => {
expect(createAlert).toHaveBeenCalled();
it('passes the correct props', () => {
expect(findRunnerDetailsTabs().props()).toEqual({
runnerId: mockRunnerId,
runnersPath: mockRunnersPath,
editPath: mockEditPath,
showAccessHelp: true,
});
});
});

View File

@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo';
import { GlTab, GlBadge } from '@gitlab/ui';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import RunnerList from '~/ci/runner/components/runner_list.vue';
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
import { PROJECT_TYPE } from '~/ci/runner/constants';
import { projectRunnersData, runnerJobCountData } from 'jest/ci/runner/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
@ -16,6 +17,7 @@ import RunnersTab from '~/ci/runner/project_runners_settings/components/runners_
Vue.use(VueApollo);
const mockRunners = projectRunnersData.data.project.runners.edges;
const mockPageInfo = projectRunnersData.data.project.runners.pageInfo;
const mockRunnerId = getIdFromGraphQLId(mockRunners[0].node.id);
const mockRunnerSha = mockRunners[0].node.shortSha;
@ -55,6 +57,7 @@ describe('RunnersTab', () => {
const findTab = () => wrapper.findComponent(GlTab);
const findBadge = () => wrapper.findComponent(GlBadge);
const findRunnerList = () => wrapper.findComponent(RunnerList);
const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
const findEmptyMessage = () => wrapper.findByTestId('empty-message');
describe('when rendered', () => {
@ -67,6 +70,7 @@ describe('RunnersTab', () => {
expect(projectRunnersHandler).toHaveBeenCalledWith({
fullPath: 'group/project',
type: PROJECT_TYPE,
first: 10,
});
});
@ -85,6 +89,10 @@ describe('RunnersTab', () => {
it('shows runner list in loading state', () => {
expect(findRunnerList().props('loading')).toBe(true);
});
it('shows a disabled pagination', () => {
expect(findRunnerPagination().attributes('disabled')).toBeDefined();
});
});
describe('when data is fetched', () => {
@ -116,6 +124,27 @@ describe('RunnersTab', () => {
`#${mockRunnerId} (${mockRunnerSha})`,
);
});
describe('pagination', () => {
it('shows pagination', () => {
expect(findRunnerPagination().attributes('disabled')).toBeUndefined();
expect(findRunnerPagination().props('pageInfo')).toEqual({ ...mockPageInfo });
});
it('changes page', async () => {
findRunnerPagination().vm.$emit('input', { after: mockPageInfo.endCursor });
await waitForPromises();
expect(projectRunnersHandler).toHaveBeenCalledTimes(2);
expect(projectRunnersHandler).toHaveBeenLastCalledWith({
fullPath: 'group/project',
type: PROJECT_TYPE,
first: 10,
after: mockPageInfo.endCursor,
});
});
});
});
it('shows empty message with no runners', async () => {

View File

@ -43,24 +43,22 @@ describe('GlqlActions', () => {
expect(findDropdown().props('toggleText')).toBe('GLQL view options');
});
it('passes two items to dropdown when showCopyContents is false', () => {
createComponent({ showCopyContents: false });
it.each`
actionsCount | availableActions | showCopyContents
${'three'} | ${['View source', 'Copy source', 'Reload']} | ${false}
${'four'} | ${['View source', 'Copy source', 'Copy contents', 'Reload']} | ${true}
`(
'passes $actionsCount items to dropdown when showCopyContents is $showCopyContents',
({ availableActions, showCopyContents }) => {
createComponent({ showCopyContents });
const items = findDropdown().props('items');
expect(items).toHaveLength(2);
expect(items[0].text).toBe('View source');
expect(items[1].text).toBe('Copy source');
});
it('passes three items to dropdown when showCopyContents is true', () => {
createComponent({ showCopyContents: true });
const items = findDropdown().props('items');
expect(items).toHaveLength(3);
expect(items[0].text).toBe('View source');
expect(items[1].text).toBe('Copy source');
expect(items[2].text).toBe('Copy contents');
});
const items = findDropdown().props('items');
expect(items).toHaveLength(availableActions.length);
items.forEach((item, index) => {
expect(item.text).toBe(availableActions[index]);
});
},
);
describe('dropdown actions', () => {
it('emits viewSource event with title when clicked', async () => {
@ -89,5 +87,12 @@ describe('GlqlActions', () => {
expect(mockEventHub.$emit).toHaveBeenCalledWith('dropdownAction', 'copyAsGFM');
});
it('emits reload event when clicked', async () => {
findDropdown().props('items')[2].action();
await nextTick();
expect(mockEventHub.$emit).toHaveBeenCalledWith('dropdownAction', 'reload');
});
});
});

View File

@ -7,11 +7,14 @@ import { stubCrypto } from 'helpers/crypto';
import GlqlFacade from '~/glql/components/common/facade.vue';
import { executeAndPresentQuery, presentPreview } from '~/glql/core';
import Counter from '~/glql/utils/counter';
import { eventHubByKey } from '~/glql/utils/event_hub_factory';
jest.mock('~/glql/core');
describe('GlqlFacade', () => {
let wrapper;
const mockQueryKey = 'glql_key';
const mockEventHub = eventHubByKey(mockQueryKey);
const { bindInternalEventDocument } = useMockInternalEventsTracking();
const createComponent = async (props = {}, glFeatures = {}) => {
@ -22,7 +25,7 @@ describe('GlqlFacade', () => {
},
provide: {
glFeatures,
queryKey: 'glql_key',
queryKey: mockQueryKey,
},
});
await nextTick();
@ -84,6 +87,16 @@ describe('GlqlFacade', () => {
undefined,
);
});
it('reloads the query when reload event is emitted on event hub', async () => {
jest.spyOn(wrapper.vm, 'reloadGlqlBlock');
mockEventHub.$emit('dropdownAction', 'reload');
await nextTick();
expect(wrapper.vm.reloadGlqlBlock).toHaveBeenCalled();
});
});
describe('when the query results in a timeout (503) error', () => {

View File

@ -277,10 +277,23 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
context 'when snowplow is disabled' do
before do
stub_application_setting(snowplow_enabled?: false)
stub_feature_flags(use_staging_endpoint_for_product_usage_events: enable_stg_events)
end
it 'returns product usage event collection hostname' do
expect(subject.hostname).to eq('events-stg.gitlab.net')
context "with use_staging_endpoint_for_product_usage_events FF disabled" do
let(:enable_stg_events) { false }
it 'returns product usage event collection hostname' do
expect(subject.hostname).to eq('events.gitlab.net')
end
end
context "with use_staging_endpoint_for_product_usage_events FF enabled" do
let(:enable_stg_events) { true }
it 'returns product usage event collection hostname' do
expect(subject.hostname).to eq('events-stg.gitlab.net')
end
end
end

View File

@ -842,7 +842,7 @@ RSpec.describe Notify, feature_category: :code_review_workflow do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_referable_subject(wiki_page_meta, reply: true)
is_expected.to have_subject('Re: a-known-name | Page 1 (slug)')
end
it 'contains a link to the wiki page note' do

View File

@ -16,6 +16,7 @@ RSpec.describe FeatureGate do
Namespace | 5 | 'Namespace:5'
Namespaces::ProjectNamespace | 6 | 'Namespaces::ProjectNamespace:6'
Namespaces::UserNamespace | 7 | 'Namespaces::UserNamespace:7'
Organizations::Organization | 8 | 'Organizations::Organization:8'
end
with_them do

View File

@ -7,6 +7,38 @@ RSpec.describe ForkNetwork, feature_category: :source_code_management do
describe "validations" do
it { is_expected.to belong_to(:organization) }
describe "#organization_match" do
let_it_be(:organization) { create(:organization) }
let_it_be(:project) { create(:project, organization: organization) }
context "when organization_id matches root_project's organization_id" do
let(:fork_network) { build(:fork_network, root_project: project, organization: organization) }
it "is valid" do
expect(fork_network).to be_valid
end
end
context "when organization_id does not match root_project's organization_id" do
let_it_be(:different_organization) { create(:organization) }
let(:fork_network) { build(:fork_network, root_project: project, organization: different_organization) }
it "is not valid" do
expect(fork_network).not_to be_valid
expect(fork_network.errors[:organization_id]).to include("must match the root project organization's ID")
end
end
context "when root_project is nil" do
let(:fork_network) { build(:fork_network, root_project: nil, organization: organization) }
it "is valid" do
expect(fork_network).to be_valid
end
end
end
end
describe '#add_root_as_member' do

View File

@ -25,23 +25,6 @@ RSpec.describe LabelNote, feature_category: :team_planning do
expect(note.note_html).to include(project_issues_path(project, label_name: label.title))
end
context 'when render_label_notes_lazily is disabled' do
before do
stub_feature_flags(render_label_notes_lazily: false)
end
it_behaves_like 'label note created from events'
it 'includes a link to the list of issues filtered by the label' do
note = described_class.from_events(
[
create(:resource_label_event, label: label, issue: resource)
], resource: resource, resource_parent: resource.resource_parent)
expect(note.note_html).to include(project_issues_path(project, label_name: label.title))
end
end
end
context 'when resource is merge request' do
@ -57,22 +40,5 @@ RSpec.describe LabelNote, feature_category: :team_planning do
expect(note.note_html).to include(project_merge_requests_path(project, label_name: label.title))
end
context 'when render_label_notes_lazily is disabled' do
before do
stub_feature_flags(render_label_notes_lazily: false)
end
it_behaves_like 'label note created from events'
it 'includes a link to the list of merge requests filtered by the label' do
note = described_class.from_events(
[
create(:resource_label_event, label: label, merge_request: resource)
], resource: resource, resource_parent: resource.resource_parent)
expect(note.note_html).to include(project_merge_requests_path(project, label_name: label.title))
end
end
end
end

View File

@ -71,86 +71,17 @@ RSpec.describe ResourceLabelEvent, feature_category: :team_planning, type: :mode
end
end
describe '#outdated_markdown?' do
describe '#outdated_reference?' do
it 'returns true if label is missing and reference is not empty' do
subject.attributes = { reference: 'ref', label_id: nil }
expect(subject.outdated_markdown?).to be true
expect(subject.outdated_reference?).to be true
end
it 'returns true if reference is not set yet' do
subject.attributes = { reference: nil }
expect(subject.outdated_markdown?).to be true
end
it 'returns true if markdown is outdated' do
subject.attributes = { cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION_SHIFTED + 1 }
expect(subject.outdated_markdown?).to be true
end
it 'returns false if label and reference are set' do
subject.attributes = { reference: 'whatever', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION_SHIFTED }
expect(subject.outdated_markdown?).to be false
end
end
describe '#reference_html' do
subject { Nokogiri::HTML.fragment(label_event.reference_html).css('a').first.attr('href') }
before do
label_event.refresh_invalid_reference
end
context 'when resource event belongs to a group level issue' do
let(:group_label) { create(:group_label, group: group) }
let(:label_event) do
group_issue = create(:issue, :group_level, namespace: group)
create(:resource_label_event, issue: group_issue, label: group_label)
end
it { is_expected.to eq(Gitlab::Routing.url_helpers.group_work_items_path(group, label_name: group_label.title)) }
end
context 'when resource event belongs to a project level issue' do
let(:label_event) { resource_label_event }
it { is_expected.to eq(Gitlab::Routing.url_helpers.project_issues_path(project, label_name: label.title)) }
end
context 'when resource event belongs to a merge request' do
let(:label_event) { create(:resource_label_event, merge_request: merge_request, label: label) }
it do
is_expected.to eq(Gitlab::Routing.url_helpers.project_merge_requests_path(project, label_name: label.title))
end
end
end
describe '#group' do
subject { build_stubbed(:resource_label_event, **issuable_attributes).group }
context 'when issuable is a merge request' do
let(:issuable_attributes) { { merge_request: merge_request } }
it { is_expected.to be_nil }
end
context 'when issuable is an issue' do
context 'when issue exists at the project level' do
let(:issuable_attributes) { { issue: issue } }
it { is_expected.to be_nil }
end
context 'when issue exists at the group level' do
let(:issuable_attributes) { { issue: build_stubbed(:issue, :group_level, namespace: group) } }
it { is_expected.to eq(group) }
end
expect(subject.outdated_reference?).to be true
end
end

View File

@ -65,7 +65,15 @@ RSpec.describe WikiPage::Meta, feature_category: :wiki do
it 'returns a canonical slug as reference to the object' do
meta = create(:wiki_page_meta, canonical_slug: 'foo')
expect(meta.to_reference).to eq('foo')
expect(meta.to_reference).to eq('[[foo]]')
end
end
describe '#gfm_reference' do
it 'returns class name with canonical slug as reference to the object' do
meta = create(:wiki_page_meta, canonical_slug: 'foo', container: project)
expect(meta.gfm_reference).to eq('project wiki page [[foo]]')
end
end

View File

@ -3635,6 +3635,18 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
expect(json_response['message']).to eq 'Target project cannot be equal to source project'
end
end
context 'when fork target and source project organization are not the same' do
let_it_be(:organization) { create(:organization) }
let_it_be(:project_fork_target_different_organization) { create(:project, organization: organization) }
it 'returns an error' do
post api("/projects/#{project_fork_target_different_organization.id}/fork/#{project_fork_source.id}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq 'Target project must belong to source project organization'
end
end
end
end

View File

@ -245,5 +245,49 @@ RSpec.describe Projects::OverwriteProjectService, feature_category: :groups_and_
expect(project_from.fork_network_member).to be_nil
end
end
context 'fork network membership behavior' do
shared_examples 'does not modify ForkNetworkMember' do
it 'does not add the source project to the fork network' do
expect { subject.execute(project_from) }.not_to change {
ForkNetworkMember.count
}
end
end
context 'when fork network conditions are met' do
it 'adds the source project to the fork network' do
expect { subject.execute(project_from) }.to change {
ForkNetworkMember.count
}.by(1)
end
end
context 'when source project is the same as target project' do
it_behaves_like 'does not modify ForkNetworkMember' do
let(:project_from) { project_to }
end
end
context 'when fork_network does not exist' do
it_behaves_like 'does not modify ForkNetworkMember' do
before do
allow(subject).to receive(:fork_network).and_return(nil)
end
end
end
context 'when organizations do not match' do
it_behaves_like 'does not modify ForkNetworkMember' do
before do
other_organization = create(:organization)
allow(project_from).to receive(:organization_id).and_return(other_organization.id)
# Stub rename_project to not interrupt test flow
allow(subject).to receive(:rename_project).and_return({ status: :success })
end
end
end
end
end
end

View File

@ -8,7 +8,7 @@ RSpec.shared_context 'with scan result policy' do
let(:default_branch) { policy_project.default_branch }
let(:policy_yaml) do
build(:orchestration_policy_yaml, scan_execution_policy: [], scan_result_policy: scan_result_policies)
build(:orchestration_policy_yaml, scan_execution_policy: [], approval_policy: scan_result_policies)
end
let(:scan_result_policies) { [scan_result_policy] }
@ -54,7 +54,7 @@ RSpec.shared_context 'with scan result policy preventing force pushing' do
end
let(:policy_yaml) do
build(:orchestration_policy_yaml, scan_result_policy: [scan_result_policy])
build(:orchestration_policy_yaml, approval_policy: [scan_result_policy])
end
end

344
yarn.lock
View File

@ -3170,181 +3170,181 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
"@tiptap/core@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.10.3.tgz#7744abd4a954f35265af351f1be9b545e819c66d"
integrity sha512-wAG/0/UsLeZLmshWb6rtWNXKJftcmnned91/HLccHVQAuQZ1UWH+wXeQKu/mtodxEO7JcU2mVPR9mLGQkK0McQ==
"@tiptap/core@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.11.7.tgz#38600a7dabc42ea84e8dfb7a74c19df10db95d14"
integrity sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==
"@tiptap/extension-blockquote@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.10.3.tgz#ee29925930ac9a5b129d3ad262bb45afcc23b318"
integrity sha512-u9Mq4r8KzoeGVT8ms6FQDIMN95dTh3TYcT7fZpwcVM96mIl2Oyt+Bk66mL8z4zuFptfRI57Cu9QdnHEeILd//w==
"@tiptap/extension-blockquote@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.11.7.tgz#ccbabedd2e19581424730613f00e971b527198ee"
integrity sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==
"@tiptap/extension-bold@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.10.3.tgz#6ffdeed5d1b2c7bd2a248b327083f3db89c02f87"
integrity sha512-xnF1tS2BsORenr11qyybW120gHaeHKiKq+ZOP14cGA0MsriKvWDnaCSocXP/xMEYHy7+2uUhJ0MsKkHVj4bPzQ==
"@tiptap/extension-bold@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.11.7.tgz#de45d9ab47b7342d83bb38823e57d1d4de3f3ae0"
integrity sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==
"@tiptap/extension-bubble-menu@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.10.3.tgz#e7f2fdddd6ef4310f9a18bc8df79f910ab26c688"
integrity sha512-e9a4yMjQezuKy0rtyyzxbV2IAE1bm1PY3yoZEFrcaY0o47g1CMUn2Hwe+9As2HdntEjQpWR7NO1mZeKxHlBPYA==
"@tiptap/extension-bubble-menu@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.7.tgz#12f4d10e340cbd32f3319bda18d2518aa92fd02e"
integrity sha512-0vYqSUSSap3kk3/VT4tFE1/6StX70I3/NKQ4J68ZSFgkgyB3ZVlYv7/dY3AkEukjsEp3yN7m8Gw8ei2eEwyzwg==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-bullet-list@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.10.3.tgz#0a17343aaf64679327de87785918bcdb04744edf"
integrity sha512-PTkwJOVlHi4RR4Wrs044tKMceweXwNmWA6EoQ93hPUVtQcwQL990Es5Izp+i88twTPLuGD9dH+o9QDyH9SkWdA==
"@tiptap/extension-bullet-list@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.7.tgz#33d965074108ea8dd1ba558b525bb25e12e54876"
integrity sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==
"@tiptap/extension-code-block-lowlight@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.10.3.tgz#35e7921a9ac23ca82c1d3d306307be7e043bec5c"
integrity sha512-ieRSdfDW06pmKcsh73N506/EWNJrpMrZzyuFx3YGJtfM+Os0a9hMLy2TSuNleyRsihBi5mb+zvdeqeGdaJm7Ng==
"@tiptap/extension-code-block-lowlight@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.11.7.tgz#9c41617f0bb1f0faebf596637abea0b7a66679e6"
integrity sha512-+eUMxvDgoYmAvkuJ2ljV2COyeH6HwH8LqCNWma+mFZCRDAoXNeqSHbBtI0Vzy4PqchfmxcmKERc99xEzoS9XUQ==
"@tiptap/extension-code-block@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.10.3.tgz#5ff1b1e563c4eda44677df444c523de1e5258fa4"
integrity sha512-yiDVNg22fYkzsFk5kBlDSHcjwVJgajvO/M5fDXA+Hfxwo2oNcG6aJyyHXFe+UaXTVjdkPej0J6kcMKrTMCiFug==
"@tiptap/extension-code-block@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.11.7.tgz#1aaaadd231317a0e1277aa987854a077cb83f0f7"
integrity sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==
"@tiptap/extension-code@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.10.3.tgz#b9fb04be2d51760f011ec7a060d4e2e3eefe392c"
integrity sha512-JyLbfyY3cPctq9sVdpcRWTcoUOoq3/MnGE1eP6eBNyMTHyBPcM9TPhOkgj+xkD1zW/884jfelB+wa70RT/AMxQ==
"@tiptap/extension-code@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.11.7.tgz#d9cb080992080d3a576482d8beee280d29d8a9df"
integrity sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==
"@tiptap/extension-document@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.10.3.tgz#2ba039932af67c85475870697495b099d9983a1d"
integrity sha512-6i8+xbS2zB6t8iFzli1O/QB01MmwyI5Hqiiv4m5lOxqavmJwLss2sRhoMC2hB3CyFg5UmeODy/f/RnI6q5Vixg==
"@tiptap/extension-document@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.11.7.tgz#07e3fcf42069fda02b63758b2f236822a7a14acc"
integrity sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==
"@tiptap/extension-dropcursor@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.10.3.tgz#c5484e16df98f3c43c9f585c5686b38808c4615b"
integrity sha512-wzWf82ixWzZQr0hxcf/A0ul8NNxgy1N63O+c56st6OomoLuKUJWOXF+cs9O7V+/5rZKWdbdYYoRB5QLvnDBAlQ==
"@tiptap/extension-dropcursor@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.7.tgz#389229b91ccce4d8ab2139acd9dafadbfedac7c0"
integrity sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==
"@tiptap/extension-floating-menu@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.10.3.tgz#5ce1613d79d80182b9b92f77ef7c429a8dc6b88a"
integrity sha512-Prg8rYLxeyzHxfzVu1mDkkUWMnD9ZN3y370O/1qy55e+XKVw9jFkTSuz0y0+OhMJG6bulYpDUMtb+N3+2xOWlQ==
"@tiptap/extension-floating-menu@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.7.tgz#cf674103730391dabd947c05c38f4e4b73d59842"
integrity sha512-DG54WoUu2vxHRVzKZiR5I5RMOYj45IlxQMkBAx1wjS0ch41W8DUYEeipvMMjCeKtEI+emz03xYUcOAP9LRmg+w==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-gapcursor@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.10.3.tgz#6a68027d41fb77707104551b2886146253a9461b"
integrity sha512-FskZi2DqDSTH1WkgLF2OLy0xU7qj3AgHsKhVsryeAtld4jAK5EsonneWgaipbz0e/MxuIvc1oyacfZKABpLaNg==
"@tiptap/extension-gapcursor@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.7.tgz#82110b499ba7ae9a76cd57f42d535af3989abd98"
integrity sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==
"@tiptap/extension-hard-break@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.10.3.tgz#1fd1ea91e57c018747e54516eaca963e578af6a4"
integrity sha512-2rFlimUKAgKDwT6nqAMtPBjkrknQY8S7oBNyIcDOUGyFkvbDUl3Jd0PiC929S5F3XStJRppnMqhpNDAlWmvBLA==
"@tiptap/extension-hard-break@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.11.7.tgz#6344893e9ac938684cecabad4ea037cc729f6cd7"
integrity sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==
"@tiptap/extension-heading@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.10.3.tgz#bf8efb3a580c75b86dce505a63f1ca7450a9aaea"
integrity sha512-AlxXXPCWIvw8hQUDFRskasj32iMNB8Sb19VgyFWqwvntGs2/UffNu8VdsVqxD2HpZ0g5rLYCYtSW4wigs9R3og==
"@tiptap/extension-heading@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.11.7.tgz#0e29db2a5dcd3456c7ab1a40817b56b926a073ec"
integrity sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==
"@tiptap/extension-highlight@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.10.3.tgz#d94667d435d9dc556b06e7b764449dc2a6c18743"
integrity sha512-srMOdpUTcp1yPGmUqgKOkbmTpCYOF6Q/8CnquDkhrvK7Gyphj+n8TocrKiloaRYZKcoQWtmb+kcVPaHhHMzsWQ==
"@tiptap/extension-highlight@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.11.7.tgz#2c92a6b8de96edfbdb2d3cdbd9eb8cf9c8011da8"
integrity sha512-c/NH4kIpNOWCUQv8RkFNDyOcgt+2pYFpDf0QBJmzhAuv4BIeS2bDmDtuNS7VgoWRZH+xxCNXfvm2BG+kjtipEg==
"@tiptap/extension-history@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.10.3.tgz#86ddbdaaa1573d4461c3a925185c4ebd9aec8079"
integrity sha512-HaSiMdx9Im9Pb9qGlVud7W8bweRDRMez33Uzs5a2x0n1RWkelfH7TwYs41Y3wus8Ujs7kw6qh7jyhvPpQBKaSA==
"@tiptap/extension-history@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.11.7.tgz#428d3a4e11f1c261ec34b68c2d3d84f1377ed81e"
integrity sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==
"@tiptap/extension-horizontal-rule@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.10.3.tgz#b2b6e47896ad12ef6747816bc11b388a53480614"
integrity sha512-1a2IWhD00tgUNg/91RLnBvfENL7DLCui5L245+smcaLu+OXOOEpoBHawx59/M4hEpsjqvRRM79TzO9YXfopsPw==
"@tiptap/extension-horizontal-rule@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.7.tgz#36e33184064f844aeb3950011c346643926e682b"
integrity sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==
"@tiptap/extension-image@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.10.3.tgz#88f18344bcb8878cd47504e3eb592ced5da1e32f"
integrity sha512-YIjAF5CwDkMe28OQ5pvnmdRgbJ9JcGMIHY1kyqNunSf2iwphK+6SWz9UEIkDFiT7AsRZySqxFSq93iK1XyTifw==
"@tiptap/extension-image@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.11.7.tgz#999e309cf769c5730727551c8563793b690c3af6"
integrity sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg==
"@tiptap/extension-italic@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.10.3.tgz#0efb940c572e47bd03e7e50a7ce745b60040771d"
integrity sha512-wAiO6ZxoHx2H90phnKttLWGPjPZXrfKxhOCsqYrK8BpRByhr48godOFRuGwYnKaiwoVjpxc63t+kDJDWvqmgMw==
"@tiptap/extension-italic@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.11.7.tgz#f3830c6ec8c7a12e20c64f9b7f100eff1a5382d9"
integrity sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==
"@tiptap/extension-link@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.10.3.tgz#ddf8c99cc2ce664c3b53d11184cbbde70120ee78"
integrity sha512-8esKlkZBzEiNcpt7I8Cd6l1mWmCc/66pPbUq9LfnIniDXE3U+ahBf4m3TJltYFBGbiiTR/xqMtJyVHOpuLDtAw==
"@tiptap/extension-link@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.11.7.tgz#315588c536a7effe0fa2470c458a9734021a0b88"
integrity sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==
dependencies:
linkifyjs "^4.1.0"
linkifyjs "^4.2.0"
"@tiptap/extension-list-item@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.10.3.tgz#a35c10a579acff63bb46a2fa9491b5f4edf00188"
integrity sha512-9sok81gvZfSta2K1Dwrq5/HSz1jk4zHBpFqCx0oydzodGslx6X1bNxdca+eXJpXZmQIWALK7zEr4X8kg3WZsgw==
"@tiptap/extension-list-item@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.11.7.tgz#06d9ea69dadaa09d260cfde81d5c77b56d8b0937"
integrity sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==
"@tiptap/extension-ordered-list@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.10.3.tgz#5ee0feb2f06a59c50e413160840f244215cc4026"
integrity sha512-/SFuEDnbJxy3jvi72LeyiPHWkV+uFc0LUHTUHSh20vwyy+tLrzncJfXohGbTIv5YxYhzExQYZDRD4VbSghKdlw==
"@tiptap/extension-ordered-list@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.7.tgz#3310176a22ed6010b646ccf9c4fe6c25d05553dc"
integrity sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==
"@tiptap/extension-paragraph@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.10.3.tgz#128c8fcd46d2e854d214c7f566e6212f2ebff6f1"
integrity sha512-sNkTX/iN+YoleDiTJsrWSBw9D7c4vsYwnW5y/G5ydfuJMIRQMF78pWSIWZFDRNOMkgK5UHkhu9anrbCFYgBfaA==
"@tiptap/extension-paragraph@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.11.7.tgz#de7fba01ebd35bddf4d6225fff708eb223276bc5"
integrity sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==
"@tiptap/extension-strike@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.10.3.tgz#99e39c8156cad7a9dc88504ac43aa839c54ef7af"
integrity sha512-jYoPy6F6njYp3txF3u23bgdRy/S5ATcWDO9LPZLHSeikwQfJ47nqb+EUNo5M8jIOgFBTn4MEbhuZ6OGyhnxopA==
"@tiptap/extension-strike@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.11.7.tgz#311b55f06bb3d1f93c02b809844cc8d27d110422"
integrity sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==
"@tiptap/extension-subscript@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.10.3.tgz#3d74cdbcd1dcd661791708559e88c7313202b577"
integrity sha512-GkOwXIruM7QksmlfqLTKTC6JBpWSBDN2eeoPwggxXuqetqYs4sIx1ul3LEGDQy0vglcFKGkbbO2IiHCO/0fSWA==
"@tiptap/extension-subscript@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.11.7.tgz#ec9fc2c61ab98c13f5263598563fb46f8ebf29bf"
integrity sha512-I25ZexCddFJ9701DCCtQbX3Vtxzj5d9ss2GAXVweIUCdATCScaebsznyUQoN5papmhTxXsw5OD+K2ZHxP82pew==
"@tiptap/extension-superscript@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.10.3.tgz#2b4f186ddb179bcdf2af68d4aebc5e1f3220de2b"
integrity sha512-4bXDPyT10ByVCLXFR8A70TcpFJ0H3PicRsxKJcQ+KZIauNUo5BBUpkF2cK+IOUp4UZ1W5ZBeuMQG5HWMuV9T1A==
"@tiptap/extension-superscript@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.11.7.tgz#4ad097717c590172a97187311e294087e628f36a"
integrity sha512-dNRpCcRJs0Qvv0sZRgbH7Y5hDVbWsGSZjtwFCs/mysPrvHqmXjzo7568kYWTggxEYxnXw6n0FfkCAEHlt0N90Q==
"@tiptap/extension-table-cell@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.10.3.tgz#3d090c7b758428abc484e0c229318bce8bd08b5b"
integrity sha512-EYzBrnq7KUAcRhshIoTmC4ED8YoF4Ei5m8ZMPOctKX+QMAagKdcrw2UxuOf4tP2xgBYx+qDsKCautepZXQiL2g==
"@tiptap/extension-table-cell@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.11.7.tgz#8a9b5d224ef1af1c3ee27b4a343719d9f2be4173"
integrity sha512-JMOkSYRckc5SJP86yGGiHzCxCR8ecrRENvTWAKib6qer2tutxs5u42W+Z8uTcHC2dRz7Fv54snOkDoqPwkf6cw==
"@tiptap/extension-table-header@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.10.3.tgz#76f0f1b3eb1d8c01b0355fa704ad75a74cd2102b"
integrity sha512-zJqzivz+VITYIFXNH09leBbkwAPuvp504rCAFL2PMa1uaME6+oiiRqZvXQrOiRkjNpOWEXH4dqvVLwkSMZoWaw==
"@tiptap/extension-table-header@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.11.7.tgz#2ebfba675e1bd6e545a504fb9d830c0a08d87823"
integrity sha512-wPRKpliS5QQXgsp//ZjXrHMdLICMkjg2fUrQinOiBa7wDL5C7Y+SehtuK4s2tjeAkyAdj+nepfftyBRIlUSMXg==
"@tiptap/extension-table-row@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.10.3.tgz#66302d52a02b675b7cb674d1a586e3c2c5ff119a"
integrity sha512-l6P6BAE4SuIFdPmsRd+zGP2Ks9AhLAua7nfDlHFMWDnfOeaJu7g/t4oG++9xTojDcVDHhcIe8TJYUXfhOt2anw==
"@tiptap/extension-table-row@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.11.7.tgz#01ca80eca98043858e422f9e50a481d07ab2f75c"
integrity sha512-K254RiXWGXGjz5Cm835hqfQiwnYXm8aw6oOa3isDh4A1B+1Ev4DB2vEDKMrgaOor3nbTsSYmAx2iEMrZSbpaRg==
"@tiptap/extension-table@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.10.3.tgz#6aaecabd7f2b58baef5082e17f1907cf45998bb7"
integrity sha512-XAvq0ptpHfuN7lQhTeew4Sqo8aKYHTqroa7cHL8I+gWJqYqKJSTGb4FAqdGIFEzHvnSsMCFbTL//kAHXvTdsHg==
"@tiptap/extension-table@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.11.7.tgz#4a8e477be809c06b43092de7db96fac6c3739ae8"
integrity sha512-rfwWkNXz/EZuhc8lylsCWPbx0Xr5FlIhreWFyeoXYrDEO3x4ytYcVOpNmbabJYP2semfM0PvPR5o84zfFkLZyg==
"@tiptap/extension-task-item@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.10.3.tgz#ff64d8620198796266b46e5a27c48ccd31b90b94"
integrity sha512-vE4qxGrZTdwynHq6l5xN0jI0ahDZpmKeoD6yuCMNyN831dgHXEjNrV8oBtZUvvqChFRc/LiSmUbrTInUn5xeNg==
"@tiptap/extension-task-item@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.11.7.tgz#d50c1d5790fa7634e647caf6e77202114cdbc7a2"
integrity sha512-m+UyE85nnqhQ4epLMYqdwaQj6DoqGGUNE0gyJOtJB1qhBi7GM7yPEDoiX82ByaQetWjoZIduRuQSRfgkD0MEeA==
"@tiptap/extension-task-list@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.10.3.tgz#8a6f2c009f844e1a98395d26140f1045d3efbd5c"
integrity sha512-Zj1pj+6VrL8VXlFYWdcLlCMykzARsvdqdU8cGVnBuC0H0vrSSfLGl+GxGnQwxTnqiNtxR4t70DLi/UjFBvzlqw==
"@tiptap/extension-task-list@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.11.7.tgz#f7b5d42ff191f369846df554679d5fb80db4313a"
integrity sha512-rgpkLvKxeSWibMpZazR5PkISSwz90Wnpe/KqIWLu/s3UuRE0Sc5kA8ZOva4ZAvcpSWEJ1cNn1OqllwHsj0NxwQ==
"@tiptap/extension-text@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.10.3.tgz#f985ebd37a2c86d621068927bceca0f05e842865"
integrity sha512-7p9XiRprsRZm8y9jvF/sS929FCELJ5N9FQnbzikOiyGNUx5mdI+exVZlfvBr9xOD5s7fBLg6jj9Vs0fXPNRkPg==
"@tiptap/extension-text@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.11.7.tgz#bbd32763a5db1e6c0ad4239233a9f86e27d15179"
integrity sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==
"@tiptap/pm@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.10.3.tgz#c6925bafd23868800bc71e06cdfe12add7bf4943"
integrity sha512-771p53aU0KFvujvKpngvq2uAxThlEsjYaXcVVmwrhf0vxSSg+psKQEvqvWvHv/3BwkPVCGwmEKNVJZjaXFKu4g==
"@tiptap/pm@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.11.7.tgz#34e1dbe1f27ea978bc740c9144ae8195948609e3"
integrity sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==
dependencies:
prosemirror-changeset "^2.2.1"
prosemirror-collab "^1.3.1"
@ -3360,23 +3360,23 @@
prosemirror-schema-basic "^1.2.3"
prosemirror-schema-list "^1.4.1"
prosemirror-state "^1.4.3"
prosemirror-tables "^1.6.1"
prosemirror-tables "^1.6.4"
prosemirror-trailing-node "^3.0.0"
prosemirror-transform "^1.10.2"
prosemirror-view "^1.37.0"
"@tiptap/suggestion@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.10.3.tgz#b8ad4516a6d074bda6e1aeeed0d8eb5df3262773"
integrity sha512-ReEwiPQoDTXn3RuWnj9D7Aod9dbNQz0QAoLRftWUTdbj3O2ohbvTNX6tlcfS+7x48Q+fAALiJGpp5BtctODlsA==
"@tiptap/suggestion@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.11.7.tgz#c94201d7d41b7bbef29e50eb1bf6a810c519cf85"
integrity sha512-I1ckVAEErpErPn/H9ZdDmTb5zuPNPiKj3krxCtJDUU4+3we0cgJY9NQFXl9//mrug3UIngH0ZQO+arbZfIk75A==
"@tiptap/vue-2@^2.10.3":
version "2.10.3"
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.10.3.tgz#7726e3a17f290e35236d6211d9a25e8e3802b271"
integrity sha512-LNOSfrp1cOKNZGmzTUCctmUcjSKHnW10jiL82qQOHqecFHLre7Cyn73KKn4cjAg4th51F8LRQ6yNyht4O+geLw==
"@tiptap/vue-2@^2.11.7":
version "2.11.7"
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.11.7.tgz#e954ff4e67d31d548c6a057761e12f5adf8afc2f"
integrity sha512-yr80TUCZVhgn233K1TE6dBYc8bMzPihl0vrK5IY8f87qbYhWmzJha4CHiRLRh8ZoK4vFWA0o13vi3G7JjPEpbA==
dependencies:
"@tiptap/extension-bubble-menu" "^2.10.3"
"@tiptap/extension-floating-menu" "^2.10.3"
"@tiptap/extension-bubble-menu" "^2.11.7"
"@tiptap/extension-floating-menu" "^2.11.7"
vue-ts-types "1.6.2"
"@tootallnate/once@2":
@ -10194,10 +10194,10 @@ linkify-it@^5.0.0:
dependencies:
uc.micro "^2.0.0"
linkifyjs@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.0.tgz#0460bfcc37d3348fa80e078d92e7bbc82588db15"
integrity sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==
linkifyjs@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08"
integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==
loader-runner@^2.4.0:
version "2.4.0"
@ -12379,7 +12379,7 @@ prosemirror-inputrules@^1.4.0:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.0.0"
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.2:
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz#14a54763a29c7b2704f561088ccf3384d14eb77e"
integrity sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==
@ -12406,7 +12406,7 @@ prosemirror-menu@^1.2.4:
prosemirror-history "^1.0.0"
prosemirror-state "^1.0.0"
prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.23.0, prosemirror-model@^1.25.0, prosemirror-model@^1.8.1:
prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.23.0, prosemirror-model@^1.25.0:
version "1.25.0"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.0.tgz#c147113edc0718a14f03881e4c20367d0221f7af"
integrity sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==
@ -12429,7 +12429,7 @@ prosemirror-schema-list@^1.0.0, prosemirror-schema-list@^1.4.1:
prosemirror-state "^1.0.0"
prosemirror-transform "^1.7.3"
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.3:
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080"
integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==
@ -12438,16 +12438,16 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, pr
prosemirror-transform "^1.0.0"
prosemirror-view "^1.27.0"
prosemirror-tables@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz#8df27facbf7632a574afb32a665aaadf7f2ed69a"
integrity sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==
prosemirror-tables@^1.6.4:
version "1.7.1"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz#df2507f285c6c7563097b4904cb7c4b9e0cd724b"
integrity sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==
dependencies:
prosemirror-keymap "^1.1.2"
prosemirror-model "^1.8.1"
prosemirror-state "^1.3.1"
prosemirror-transform "^1.2.1"
prosemirror-view "^1.13.3"
prosemirror-keymap "^1.2.2"
prosemirror-model "^1.25.0"
prosemirror-state "^1.4.3"
prosemirror-transform "^1.10.3"
prosemirror-view "^1.39.1"
prosemirror-test-builder@^1.1.1:
version "1.1.1"
@ -12466,17 +12466,17 @@ prosemirror-trailing-node@^3.0.0:
"@remirror/core-constants" "3.0.0"
escape-string-regexp "^4.0.0"
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.3:
version "1.10.2"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz#8ebac4e305b586cd96595aa028118c9191bbf052"
integrity sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.3, prosemirror-transform@^1.7.3:
version "1.10.4"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz#56419eac14f9f56612c806ae46f9238648f3f02e"
integrity sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==
dependencies:
prosemirror-model "^1.21.0"
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.37.0:
version "1.37.0"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.37.0.tgz#4bc5486d70c546490733197d4bbf4579bfc3d84d"
integrity sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.37.0, prosemirror-view@^1.39.1:
version "1.39.2"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.39.2.tgz#178743c9694fec5ed498d48e46d4a31bc1ef0936"
integrity sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==
dependencies:
prosemirror-model "^1.20.0"
prosemirror-state "^1.0.0"