Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8b88def0da
commit
a46ff9290c
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export default Link.extend({
|
|||
return {
|
||||
...this.parent?.(),
|
||||
editLink:
|
||||
(attrs) =>
|
||||
(attrs = { href: '' }) =>
|
||||
({ chain }) => {
|
||||
chain().setMeta('creatingLink', true).setLink(attrs).run();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ export default {
|
|||
navigator.clipboard.writeText(this.wrappedQuery);
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.reloadGlqlBlock();
|
||||
},
|
||||
|
||||
async copyAsGFM() {
|
||||
await copyGLQLNodeAsGFM(this.$refs.presenter.$el);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
|
||||
include PageLayoutHelper
|
||||
include ViteCSP
|
||||
|
||||
layout 'profile'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Oauth
|
||||
class DeviceAuthorizationsController < Doorkeeper::DeviceAuthorizationGrant::DeviceAuthorizationsController
|
||||
include ViteCSP
|
||||
|
||||
layout 'minimal'
|
||||
|
||||
def index
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
include Gitlab::RackLoadBalancingHelpers
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
include Onboarding::Redirectable
|
||||
include ViteCSP
|
||||
|
||||
layout 'devise'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module Organizations
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
include Gitlab::SQL::Pattern
|
||||
include Gitlab::VisibilityLevel
|
||||
include FeatureGate
|
||||
|
||||
DEFAULT_ORGANIZATION_ID = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)} }
|
||||
|
|
|
|||
|
|
@ -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)} }
|
||||
|
|
|
|||
|
|
@ -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) } }
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
e9dfceb876992bb723ada83acb75310d8342e8c1273c14af7a7df7881a3aa442
|
||||
|
|
@ -0,0 +1 @@
|
|||
4cad51507c9d37d4510067a02b0082661218381e38744577c7fe75cfe500afe7
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
---
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -953,7 +953,6 @@ excluded_attributes:
|
|||
- :group_id
|
||||
resource_label_events:
|
||||
- :reference
|
||||
- :reference_html
|
||||
- :epic_id
|
||||
- :issue_id
|
||||
- :merge_request_id
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
70
package.json
70
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
344
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue