Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-14 21:13:24 +00:00
parent 5b62f8e3ee
commit 6df3cf6b4a
76 changed files with 1633 additions and 589 deletions

View File

@ -11,7 +11,7 @@ include:
- local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml
- local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml
- project: gitlab-org/quality/pipeline-common
ref: 3.0.0
ref: 3.1.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml

View File

@ -1,6 +1,6 @@
include:
- project: gitlab-org/quality/pipeline-common
ref: 3.0.0
ref: 3.1.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml

View File

@ -4,31 +4,35 @@ The title should be something that is easily understood that quickly communicate
A proposal title should combine the beneficiary of the feature/UI, the job it will allow them to accomplish, and their expected outcome when the work is delivered. Well-defined statements are concise without sacrificing the substance of the proposal so that anyone can understand it at a glance. (e.g.🤖 {Reduce the effort} + {for security teams} + {when prioritizing business-critical risks in their assets}) -->
# Experiment/Prototype
# [Experiment](https://docs.gitlab.com/ee/policy/alpha-beta-support.html#experiment)
## Problem to be solved
### User problem
_What user problem will this solve?_
#### Solution hypothesis
### Solution hypothesis
_Why do you believe this AI solution is a good way to solve this problem?_
### Assumption
_What assumptions are you making about this problem and the solution?_
### [Personas](https://about.gitlab.com/handbook/product/personas/#list-of-user-personas)
_What personas have this problem, who is the intended user?_
### Personas
_What [personas](https://about.gitlab.com/handbook/product/personas/#list-of-user-personas) have this problem, who is the intended user?_
## Proposal
<!-- Use this section to explain the proposed changes, including details around usage and business drivers. -->
### Success
_How will you measure whether this experiment is a success?_
# Feature release
### Main Job story
# [General Availability](https://docs.gitlab.com/ee/policy/alpha-beta-support.html#generally-available-ga)
## Main Job story
_What job to be done will this solve?_
<!-- What is the [Main Job story](https://about.gitlab.com/handbook/product/ux/jobs-to-be-done/#how-to-write-a-jtbd) that this proposal was derived from? (e.g. When I am on triage rotation, I want to address all the business-critical risks in my assets, So I can minimize the likelihood of my organization being compromised by a security breach.) -->
## Proposal updates/additions
### Proposal updates/additions
<!-- Use this section to explain any changes or updates to the original proposal, including details around usage, business drivers, and reasonings that drove the updates/additions. -->
### Problem validation
@ -42,15 +46,15 @@ _What business objective will be achieved with this proposal?_
_Has this proposal been derived from research?_
<!-- How well do we understand the user's problem and their need? Refer to https://about.gitlab.com/handbook/product/ux/product-design/ux-roadmaps/#confidence to assess confidence -->
| Confidence | Research |
| --- | --- |
| Confidence | Research |
| ----------------- | ------------------------------ |
| [High/Medium/Low] | [research/insight issue](Link) |
### Requirements
_What tasks or actions should the user be capable of performing with this feature?_
<!-- Requirements can be taken from existing features or design issues used to build this proposal. Any related issues should be linked with this issue in the Feature/solution issues section below. They are more granular validated needs, goals, and additional details that the proposal encompasses. -->
>⚠️ Related feature and research issues should be linked in the related issues section (Delete this line when this is done)
> ⚠️ Related feature and research issues should be linked in the related issues section (Delete this line when this is done)
#### The user needs to be able to:
- ...
@ -58,50 +62,64 @@ _What tasks or actions should the user be capable of performing with this featur
- ...
## Checklist
### Experiment
<details> <summary> Issue information </summary>
- [ ] Add information to the issue body about:
- [ ] The user problem being solved
- [ ] Your assumptions
- [ ] Who it's for, list of personas impacted
- [ ] Your proposal
- [ ] Add relevant designs to the Design Management area of the issue if available
- [ ] Ensure this issue has the ~wg-ai-integration label to ensure visibility to various teams working on this
<details>
<summary> Issue information </summary>
- [ ] Add information to the issue body about:
- [ ] The user problem being solved
- [ ] Your assumptions
- [ ] Who it's for, list of personas impacted
- [ ] Your proposal
- [ ] Add relevant designs to the Design Management area of the issue if available
- [ ] Ensure this issue has the ~wg-ai-integration label to ensure visibility to various teams working on this
</details>
### Feature release
<details> <summary> Issue information </summary>
- [ ] Add information to the issue body about:
- [ ] Your proposal
- [ ] The Job Statement it's expected to satisfy
- [ ] Details about the user problem and provide any research or problem validation
- [ ] List the personas impacted by the proposal.
- [ ] Add all relevant solution validation issues to the Linked items section that shows this proposal will solve the customer problem, or details explaining why it's not possible to provide that validation.
- [ ] Add relevant designs to the Design Management area of the issue.
- [ ] You have adhered to our [Definition of Done](https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done) standards
- [ ] Ensure this issue has the ~wg-ai-integration label to ensure visibility to various teams working on this
### General Availability
<details>
<summary>Issue information</summary>
- [ ] Add information to the issue body about:
- [ ] Your proposal
- [ ] The Job Statement it's expected to satisfy
- [ ] Details about the user problem and provide any research or problem validation
- [ ] List the personas impacted by the proposal.
- [ ] Add all relevant solution validation issues to the Linked items section that shows this proposal will solve the customer problem, or details explaining why it's not possible to provide that validation.
- [ ] Add relevant designs to the Design Management area of the issue.
- [ ] You have adhered to our [Definition of Done](https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done) standards
- [ ] Ensure this issue has the ~wg-ai-integration label to ensure visibility to various teams working on this
</details>
<details> <summary> Technical needs </summary>
- [ ] [https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985](https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985)+s
1. Work estimate and skills needs to build an ML viable feature.
- To build any ML feature depending on the work, there are many personas that contribute including, Data Scientist, NLP engineer, ML Engineer, MLOps Engineer, ML Infra engineers, and Fullstack engineer to integrate the ML Services with Gitlab. Post-prototype we would assess the skills needed to build a production-grade ML feature for the prototype
2. Data Limitation
- We would like to upfront validate if we have viable data for the feature including whether we can use the DataOps pipeline of ModelOps or create a custom one. We would want to understand the training data, test data, and feedback data to dial up the accuracy and the limitations of the data.
3. Model Limitation
-We would want to understand if we can use an open-source pre-trained model, tune and customize it or start a model from scratch as well. Further, we would asses based on the ModelOps model evaluation framework which would be the right model to use based on the use case.
4. Cost, Scalability, Reliability
-We would want to estimate the cost of hosting, serving, inference of the model, and the full end-to-end infrastructure including monitoring and observability.
5. Legal and Ethical Framework
-We would want to align with legal and ethical framework like any other ModelOps features to cover across the nine principles of responsible ML and any legal support needed.
<details>
<summary>Technical needs</summary>
- [ ] [Operational Requirements Review - Checklist - #note_1337519985](https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985)
1. **Work estimate and skills needs to build an ML viable feature:** To build any ML feature depending on the work, there are many personas that contribute including, Data Scientist, NLP engineer, ML Engineer, MLOps Engineer, ML Infra engineers, and Fullstack engineer to integrate the ML Services with Gitlab. Post-prototype we would assess the skills needed to build a production-grade ML feature for the prototype
2. **Data Limitation:** We would like to upfront validate if we have viable data for the feature including whether we can use the DataOps pipeline of ModelOps or create a custom one. We would want to understand the training data, test data, and feedback data to dial up the accuracy and the limitations of the data.
3. **Model Limitation:** We would want to understand if we can use an open-source pre-trained model, tune and customize it or start a model from scratch as well. Further, we would asses based on the ModelOps model evaluation framework which would be the right model to use based on the use case.
4. **Cost, Scalability, Reliability:** We would want to estimate the cost of hosting, serving, inference of the model, and the full end-to-end infrastructure including monitoring and observability.
5. **Legal and Ethical Framework:** We would want to align with legal and ethical framework like any other ModelOps features to cover across the nine principles of responsible ML and any legal support needed.
</details>
<details> <summary> Dependency needs </summary>
- [ ] [https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985](https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985)+s
<details>
<summary>Dependency needs</summary>
- [ ] [Operational Requirements Review - Checklist - #note_1337519985](https://gitlab.com/gitlab-org/gitlab/-/issues/403859#note_1337519985)
</details>
<details> <summary> Legal needs </summary>
- [ ] TBD
<details>
<summary>Legal needs</summary>
- [ ] TBD
</details>
## Additional resources
@ -117,4 +135,4 @@ _What tasks or actions should the user be capable of performing with this featur
/cc @tmccaslin @hbenson @wayne @pedroms @jmandell
/confidential
Make change to this template here: https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/AI%20Project%20Proposal.md
[Make change to this template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/AI%20Project%20Proposal.md)

View File

@ -1,14 +1,20 @@
<script>
import { GlCollapse, GlButton } from '@gitlab/ui';
import { GlCollapse, GlButton, GlAlert } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import csrf from '~/lib/utils/csrf';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import KubernetesAgentInfo from './kubernetes_agent_info.vue';
import KubernetesPods from './kubernetes_pods.vue';
export default {
components: {
GlCollapse,
GlButton,
GlAlert,
KubernetesAgentInfo,
KubernetesPods,
},
inject: ['kasTunnelUrl'],
props: {
agentName: {
required: true,
@ -22,10 +28,16 @@ export default {
required: true,
type: String,
},
namespace: {
required: false,
type: String,
default: '',
},
},
data() {
return {
isVisible: false,
error: '',
};
},
computed: {
@ -35,11 +47,26 @@ export default {
label() {
return this.isVisible ? this.$options.i18n.collapse : this.$options.i18n.expand;
},
gitlabAgentId() {
const id = isGid(this.agentId) ? getIdFromGraphQLId(this.agentId) : this.agentId;
return id.toString();
},
k8sAccessConfiguration() {
return {
basePath: this.kasTunnelUrl,
baseOptions: {
headers: { 'GitLab-Agent-Id': this.gitlabAgentId, ...csrf.headers },
},
};
},
},
methods: {
toggleCollapse() {
this.isVisible = !this.isVisible;
},
onClusterError(message) {
this.error = message;
},
},
i18n: {
collapse: __('Collapse'),
@ -66,7 +93,17 @@ export default {
:agent-name="agentName"
:agent-id="agentId"
:agent-project-path="agentProjectPath"
class="gl-mb-5" />
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5">
{{ error }}
</gl-alert>
<kubernetes-pods
:configuration="k8sAccessConfiguration"
:namespace="namespace"
class="gl-mb-5"
@cluster-error="onClusterError"
/></template>
</gl-collapse>
</div>

View File

@ -0,0 +1,111 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { s__ } from '~/locale';
import k8sPodsQuery from '../graphql/queries/k8s_pods.query.graphql';
export default {
components: {
GlLoadingIcon,
GlSingleStat,
},
apollo: {
k8sPods: {
query: k8sPodsQuery,
variables() {
return {
configuration: this.configuration,
namespace: this.namespace,
};
},
update(data) {
return data?.k8sPods || [];
},
error(error) {
this.error = error;
this.$emit('cluster-error', this.error);
},
},
},
props: {
configuration: {
required: true,
type: Object,
},
namespace: {
required: true,
type: String,
},
},
data() {
return {
error: '',
};
},
computed: {
podStats() {
if (!this.k8sPods) return null;
return [
{
// eslint-disable-next-line @gitlab/require-i18n-strings
value: this.getPodsByPhase('Running'),
title: this.$options.i18n.runningPods,
},
{
// eslint-disable-next-line @gitlab/require-i18n-strings
value: this.getPodsByPhase('Pending'),
title: this.$options.i18n.pendingPods,
},
{
// eslint-disable-next-line @gitlab/require-i18n-strings
value: this.getPodsByPhase('Succeeded'),
title: this.$options.i18n.succeededPods,
},
{
// eslint-disable-next-line @gitlab/require-i18n-strings
value: this.getPodsByPhase('Failed'),
title: this.$options.i18n.failedPods,
},
];
},
loading() {
return this.$apollo.queries.k8sPods.loading;
},
},
methods: {
getPodsByPhase(phase) {
const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase);
return filteredPods.length;
},
},
i18n: {
podsTitle: s__('Environment|Pods'),
runningPods: s__('Environment|Running'),
pendingPods: s__('Environment|Pending'),
succeededPods: s__('Environment|Succeeded'),
failedPods: s__('Environment|Failed'),
},
};
</script>
<template>
<div>
<p class="gl-text-gray-500">{{ $options.i18n.podsTitle }}</p>
<gl-loading-icon v-if="loading" />
<div
v-else-if="podStats && !error"
class="gl-display-flex gl-flex-wrap-wrap gl-sm-flex-wrap-nowrap gl-mx-n3 gl-mt-n3"
>
<gl-single-stat
v-for="(stat, index) in podStats"
:key="index"
class="gl-w-full gl-flex-direction-column gl-align-items-center gl-justify-content-center gl-bg-white gl-border gl-border-gray-a-08 gl-mx-3 gl-p-3 gl-mt-3"
:value="stat.value"
:title="stat.title"
/>
</div>
</div>
</template>

View File

@ -173,7 +173,8 @@ export default {
return this.glFeatures?.kasUserAccessProject;
},
hasRequiredAgentData() {
return this.agent.project && this.agent.id && this.agent.name;
const { project, id, name } = this.agent || {};
return project && id && name;
},
showKubernetesOverview() {
return this.isKubernetesOverviewAvailable && this.hasRequiredAgentData;
@ -367,6 +368,7 @@ export default {
:agent-project-path="agent.project"
:agent-name="agent.name"
:agent-id="agent.id"
:namespace="agent.kubernetesNamespace"
/>
</div>
<div v-if="rolloutStatus" :class="$options.deployBoardClasses">

View File

@ -5,6 +5,7 @@ import pageInfoQuery from './queries/page_info.query.graphql';
import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
import k8sPodsQuery from './queries/k8s_pods.query.graphql';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
@ -82,6 +83,14 @@ export const apolloProvider = (endpoint) => {
},
},
});
cache.writeQuery({
query: k8sPodsQuery,
data: {
status: {
phase: '',
},
},
});
return new VueApollo({
defaultClient,
});

View File

@ -0,0 +1,7 @@
query getK8sPods($configuration: Object, $namespace: String) {
k8sPods(configuration: $configuration, namespace: $namespace) @client {
status {
phase
}
}
}

View File

@ -1,3 +1,4 @@
import { CoreV1Api, Configuration } from '@gitlab/cluster-client';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import {
@ -71,6 +72,19 @@ export const resolvers = (endpoint) => ({
isLastDeployment(_, { environment }) {
return environment?.lastDeployment?.isLast;
},
k8sPods(_, { configuration, namespace }) {
const coreV1Api = new CoreV1Api(new Configuration(configuration));
const podsApi = namespace
? coreV1Api.listCoreV1NamespacedPod(namespace)
: coreV1Api.listCoreV1PodForAllNamespaces();
return podsApi
.then((res) => res?.data?.items || [])
.catch((err) => {
const error = err?.response?.data?.message ? new Error(err.response.data.message) : err;
throw error;
});
},
},
Mutation: {
stopEnvironment(_, { environment }, { client }) {

View File

@ -62,6 +62,19 @@ type LocalPageInfo {
previousPage: Int!
}
type k8sPodStatus {
phase: String
}
type LocalK8sPods {
status: k8sPodStatus
}
input LocalConfiguration {
basePath: String
baseOptions: JSON
}
extend type Query {
environmentApp(page: Int, scope: String): LocalEnvironmentApp
folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
@ -71,6 +84,7 @@ extend type Query {
environmentToStop: LocalEnvironment
isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
isLastDeployment(environment: LocalEnvironmentInput): Boolean
k8sPods(configuration: LocalConfiguration, namespace: String): [LocalK8sPods]
}
extend type Mutation {

View File

@ -1,5 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility';
import { parseBoolean } from '../lib/utils/common_utils';
import { apolloProvider } from './graphql/client';
import EnvironmentsApp from './components/environments_app.vue';
@ -16,6 +17,7 @@ export default (el) => {
projectPath,
defaultBranchName,
projectId,
kasTunnelUrl,
} = el.dataset;
return new Vue({
@ -28,6 +30,7 @@ export default (el) => {
newEnvironmentPath,
helpPagePath,
projectId,
kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
canCreateEnvironment: parseBoolean(canCreateEnvironment),
},
render(h) {

View File

@ -2,6 +2,7 @@
import { GlLink } from '@gitlab/ui';
import { FEATURE_NAME, FEATURE_FEEDBACK_ISSUE } from '~/ml/experiment_tracking/constants';
import IncubationAlert from '~/vue_shared/components/incubation/incubation_alert.vue';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import {
TITLE_LABEL,
INFO_LABEL,
@ -12,12 +13,16 @@ import {
PARAMETERS_LABEL,
METRICS_LABEL,
METADATA_LABEL,
DELETE_CANDIDATE_CONFIRMATION_MESSAGE,
DELETE_CANDIDATE_PRIMARY_ACTION_LABEL,
DELETE_CANDIDATE_MODAL_TITLE,
} from './translations';
export default {
name: 'MlCandidatesShow',
components: {
IncubationAlert,
DeleteButton,
GlLink,
},
props: {
@ -36,6 +41,9 @@ export default {
PARAMETERS_LABEL,
METRICS_LABEL,
METADATA_LABEL,
DELETE_CANDIDATE_CONFIRMATION_MESSAGE,
DELETE_CANDIDATE_PRIMARY_ACTION_LABEL,
DELETE_CANDIDATE_MODAL_TITLE,
},
computed: {
sections() {
@ -67,11 +75,22 @@ export default {
:link-to-feedback-issue="$options.FEATURE_FEEDBACK_ISSUE"
/>
<h3>
{{ $options.i18n.TITLE_LABEL }}
</h3>
<div class="detail-page-header gl-flex-wrap">
<div class="detail-page-header-body">
<h1 class="page-title gl-font-size-h-display flex-fill">
{{ $options.i18n.TITLE_LABEL }}
</h1>
<table class="candidate-details">
<delete-button
:delete-path="candidate.info.path"
:delete-confirmation-text="$options.i18n.DELETE_CANDIDATE_CONFIRMATION_MESSAGE"
:action-primary-text="$options.i18n.DELETE_CANDIDATE_PRIMARY_ACTION_LABEL"
:modal-title="$options.i18n.DELETE_CANDIDATE_MODAL_TITLE"
/>
</div>
</div>
<table class="candidate-details gl-w-full">
<tbody>
<tr class="divider"></tr>

View File

@ -9,3 +9,8 @@ export const ARTIFACTS_LABEL = s__('MlExperimentTracking|Artifacts');
export const PARAMETERS_LABEL = s__('MlExperimentTracking|Parameters');
export const METRICS_LABEL = s__('MlExperimentTracking|Metrics');
export const METADATA_LABEL = s__('MlExperimentTracking|Metadata');
export const DELETE_CANDIDATE_CONFIRMATION_MESSAGE = s__(
'MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata.',
);
export const DELETE_CANDIDATE_PRIMARY_ACTION_LABEL = s__('MlExperimentTracking|Delete candidate');
export const DELETE_CANDIDATE_MODAL_TITLE = s__('MLExperimentTracking|Delete candidate?');

View File

@ -1,4 +1,5 @@
import { s__, __ } from '~/locale';
import { DEFAULT_FIELDS } from '~/jobs/components/table/constants';
export const CANCEL_JOBS_MODAL_ID = 'cancel-jobs-modal';
export const CANCEL_JOBS_MODAL_TITLE = s__('AdminArea|Are you sure?');
@ -10,3 +11,11 @@ export const PRIMARY_ACTION_TEXT = s__('AdminArea|Yes, proceed');
export const CANCEL_JOBS_WARNING = s__(
"AdminArea|You're about to cancel all running and pending jobs across this instance. Do you want to proceed?",
);
/* Admin Table constants */
export const DEFAULT_FIELDS_ADMIN = [
...DEFAULT_FIELDS.slice(0, 2),
{ key: 'project', label: __('Project'), columnClass: 'gl-w-20p' },
{ key: 'runner', label: __('Runner'), columnClass: 'gl-w-15p' },
...DEFAULT_FIELDS.slice(2),
];

View File

@ -1,5 +1,16 @@
<script>
import { queryToObject } from '~/lib/utils/url_utility';
import { validateQueryString } from '~/jobs/components/filtered_search/utils';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue';
import { DEFAULT_FIELDS_ADMIN } from '../constants';
import GetAllJobs from './graphql/queries/get_all_jobs.query.graphql';
export default {
components: {
JobsTable,
JobsTableTabs,
},
inject: {
jobStatuses: {
default: null,
@ -11,9 +22,69 @@ export default {
default: '',
},
},
apollo: {
jobs: {
query: GetAllJobs,
variables() {
return this.variables;
},
update(data) {
const { jobs: { nodes: list = [], pageInfo = {}, count } = {} } = data || {};
return {
list,
pageInfo,
count,
};
},
error() {
this.hasError = true;
},
},
},
data() {
return {
jobs: {
list: [],
},
hasError: false,
count: 0,
scope: null,
infiniteScrollingTriggered: false,
DEFAULT_FIELDS_ADMIN,
};
},
computed: {
loading() {
return this.$apollo.queries.jobs.loading;
},
variables() {
return { ...this.validatedQueryString };
},
validatedQueryString() {
const queryStringObject = queryToObject(window.location.search);
return validateQueryString(queryStringObject);
},
jobsCount() {
return this.jobs.count;
},
},
watch: {
// this watcher ensures that the count on the all tab
// is not updated when switching to the finished tab
jobsCount(newCount) {
if (this.scope) return;
this.count = newCount;
},
},
};
</script>
<template>
<div>{{ __('Jobs') }}</div>
<div>
<jobs-table-tabs :all-jobs-count="count" :loading="loading" />
<jobs-table :jobs="jobs.list" :table-fields="DEFAULT_FIELDS_ADMIN" />
</div>
</template>

View File

@ -0,0 +1,62 @@
import { isEqual } from 'lodash';
export default {
typePolicies: {
Query: {
fields: {
jobs: {
keyArgs: ['statuses'],
},
},
},
CiJobConnection: {
merge(existing = {}, incoming, { args = {} }) {
if (incoming.nodes) {
let nodes;
const areNodesEqual = isEqual(existing.nodes, incoming.nodes);
const statuses = Array.isArray(args.statuses) ? [...args.statuses] : args.statuses;
const { pageInfo } = incoming;
if (Object.keys(existing).length !== 0 && isEqual(existing?.statuses, args?.statuses)) {
if (areNodesEqual) {
if (incoming.pageInfo.hasNextPage) {
nodes = [...existing.nodes, ...incoming.nodes];
} else {
nodes = [...incoming.nodes];
}
} else {
if (!existing.pageInfo?.hasNextPage) {
nodes = [...incoming.nodes];
return {
nodes,
statuses,
pageInfo,
count: incoming.count,
};
}
nodes = [...existing.nodes, ...incoming.nodes];
}
} else {
nodes = [...incoming.nodes];
}
return {
nodes,
statuses,
pageInfo,
count: incoming.count,
};
}
return {
nodes: existing.nodes,
pageInfo: existing.pageInfo,
statuses: args.statuses,
};
},
},
},
};

View File

@ -0,0 +1,81 @@
query getAllJobs($after: String, $first: Int = 50, $statuses: [CiJobStatus!]) {
jobs(after: $after, first: $first, statuses: $statuses) {
count
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
nodes {
artifacts {
nodes {
id
downloadPath
fileType
}
}
allowFailure
status
scheduledAt
manualJob
triggered
createdByTag
detailedStatus {
id
detailsPath
group
icon
label
text
tooltip
action {
id
buttonTitle
icon
method
path
title
}
}
id
refName
refPath
tags
shortSha
commitPath
pipeline {
id
project {
id
fullPath
webUrl
}
path
user {
id
webPath
avatarUrl
}
}
stage {
id
name
}
name
duration
finishedAt
coverage
retryable
playable
cancelable
active
stuck
userPermissions {
readBuild
readJobArtifacts
updateBuild
}
}
}
}

View File

@ -1,11 +1,21 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import Translate from '~/vue_shared/translate';
import createDefaultClient from '~/lib/graphql';
import { CANCEL_JOBS_MODAL_ID } from '../components/constants';
import CancelJobsModal from '../components/cancel_jobs_modal.vue';
import AdminJobsTableApp from '../components/table/admin_jobs_table_app.vue';
import cacheConfig from '../components/table/graphql/cache_config';
Vue.use(Translate);
Vue.use(VueApollo);
const client = createDefaultClient({}, { cacheConfig });
const apolloProvider = new VueApollo({
defaultClient: client,
});
function initJobs() {
const buttonId = 'js-stop-jobs-button';
@ -44,6 +54,7 @@ export function initAdminJobsApp() {
return new Vue({
el: containerEl,
apolloProvider,
provide: {
url,
emptyStateSvgPath,

View File

@ -10,9 +10,10 @@ module Projects
def show; end
def destroy
@experiment = @candidate.experiment
@candidate.destroy!
redirect_to project_ml_experiments_path(@project),
redirect_to project_ml_experiment_path(@project, @experiment.iid),
status: :found,
notice: s_("MlExperimentTracking|Candidate removed")
end

View File

@ -15,6 +15,7 @@ module Projects
path_to_artifact: link_to_artifact(candidate),
experiment_name: candidate.experiment.name,
path_to_experiment: link_to_experiment(candidate.project, candidate.experiment),
path: link_to_details(candidate),
status: candidate.status
},
metadata: candidate.metadata

View File

@ -39,6 +39,8 @@ class Issue < ApplicationRecord
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
IssueTypeOutOfSyncError = Class.new(StandardError)
SORTING_PREFERENCE_FIELD = :issues_sort
MAX_BRANCH_TEMPLATE = 255
@ -233,6 +235,7 @@ class Issue < ApplicationRecord
scope :with_projects_matching_search_data, -> { where('issue_search_data.project_id = issues.project_id') }
before_validation :ensure_namespace_id, :ensure_work_item_type
before_save :check_issue_type_in_sync!
after_save :ensure_metrics!, unless: :importing?
after_commit :expire_etag_cache, unless: :importing?
@ -724,6 +727,33 @@ class Issue < ApplicationRecord
private
def check_issue_type_in_sync!
# We might have existing records out of sync, so we need to skip this check unless the value is changed
# so those records can still be updated until we fix them and remove the issue_type column
# https://gitlab.com/gitlab-org/gitlab/-/work_items/403158?iid_path=true
return unless (changes.keys & %w[issue_type work_item_type_id]).any?
if issue_type != work_item_type.base_type
error = IssueTypeOutOfSyncError.new(
<<~ERROR
Issue `issue_type` out of sync with `work_item_type_id` column.
`issue_type` must be equal to `work_item.base_type`.
You can assign the correct work_item_type like this for example:
Issue.new(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident))
More details in https://gitlab.com/gitlab-org/gitlab/-/issues/338005
ERROR
)
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
error,
issue_type: issue_type,
work_item_type_id: work_item_type_id
)
end
end
def due_date_after_start_date
return unless start_date.present? && due_date.present?

View File

@ -124,11 +124,6 @@ module Issues
def update_project_counter_caches?(issue)
super || issue.confidential_changed?
end
override :allowed_create_params
def allowed_create_params(params)
super(params).except(:work_item_type_id, :work_item_type)
end
end
end

View File

@ -70,18 +70,6 @@ module Issues
def issue_params
@issue_params ||= build_issue_params
if @issue_params[:work_item_type].present?
@issue_params[:issue_type] = @issue_params[:work_item_type].base_type
else
# If :issue_type is nil then params[:issue_type] was either nil
# or not permitted. Either way, the :issue_type will default
# to the column default of `issue`. And that means we need to
# ensure the work_item_type_id is set
@issue_params[:work_item_type_id] = get_work_item_type_id(@issue_params[:issue_type])
end
@issue_params
end
private
@ -98,11 +86,7 @@ module Issues
:confidential
]
params[:work_item_type] = WorkItems::Type.find_by(id: params[:work_item_type_id]) if params[:work_item_type_id].present? # rubocop: disable CodeReuse/ActiveRecord
public_issue_params << :issue_type if create_issue_type_allowed?(container, params[:issue_type])
base_type = params[:work_item_type]&.base_type
public_issue_params << :work_item_type if create_issue_type_allowed?(container, base_type)
params.slice(*public_issue_params)
end
@ -113,10 +97,6 @@ module Issues
.merge(public_params)
.with_indifferent_access
end
def get_work_item_type_id(issue_type = :issue)
find_work_item_type_id(issue_type)
end
end
end

View File

@ -27,6 +27,7 @@ module Issues
# We should not initialize the callback classes during the build service execution because these will be
# initialized when we call #create below
@issue = @build_service.execute(initialize_callbacks: false)
set_work_item_type(@issue)
# issue_type is set in BuildService, so we can delete it from params, in later phase
# it can be set also from quick actions - in that case work_item_id is synced later again
@ -76,7 +77,6 @@ module Issues
handle_escalation_status_change(issue)
create_timeline_event(issue)
try_to_associate_contacts(issue)
change_additional_attributes(issue)
super
end
@ -105,12 +105,24 @@ module Issues
private
def handle_quick_actions(issue)
# Do not handle quick actions unless the work item is the default Issue.
# The available quick actions for a work item depend on its type and widgets.
return if @params[:work_item_type].present? && @params[:work_item_type] != WorkItems::Type.default_by_type(:issue)
def set_work_item_type(issue)
work_item_type = if params[:work_item_type_id].present?
params.delete(:work_item_type)
WorkItems::Type.find_by(id: params.delete(:work_item_type_id)) # rubocop: disable CodeReuse/ActiveRecord
else
params.delete(:work_item_type)
end
super
base_type = work_item_type&.base_type
if create_issue_type_allowed?(container, base_type)
issue.work_item_type = work_item_type
# Up to this point issue_type might be set to the default, so we need to sync if a work item type is provided
issue.issue_type = work_item_type.base_type
end
# If no work item type was provided, we need to set it to whatever issue_type was up to this point,
# and that includes the column default
issue.work_item_type = WorkItems::Type.default_by_type(issue.issue_type)
end
def authorization_action
@ -144,15 +156,6 @@ module Issues
set_crm_contacts(issue, contacts)
end
override :change_additional_attributes
def change_additional_attributes(issue)
super
# issue_type can be still set through quick actions, in that case
# we have to make sure to re-sync work_item_type with it
issue.work_item_type_id = find_work_item_type_id(params[:issue_type]) if params[:issue_type]
end
end
end

View File

@ -115,14 +115,6 @@ module Issues
attr_reader :spam_params
def handle_quick_actions(issue)
# Do not handle quick actions unless the work item is the default Issue.
# The available quick actions for a work item depend on its type and widgets.
return unless issue.work_item_type.default_issue?
super
end
def handle_date_changes(issue)
return unless issue.previous_changes.slice('due_date', 'start_date').any?

View File

@ -2,6 +2,7 @@
module WorkItems
class CreateService < Issues::CreateService
extend ::Gitlab::Utils::Override
include WidgetableService
def initialize(container:, spam_params:, current_user: nil, params: {}, widget_params: {})
@ -48,6 +49,15 @@ module WorkItems
private
override :handle_quick_actions
def handle_quick_actions(work_item)
# Do not handle quick actions unless the work item is the default Issue.
# The available quick actions for a work item depend on its type and widgets.
return if work_item.work_item_type != WorkItems::Type.default_by_type(:issue)
super
end
def authorization_action
:create_work_item
end

View File

@ -2,6 +2,7 @@
module WorkItems
class UpdateService < ::Issues::UpdateService
extend Gitlab::Utils::Override
include WidgetableService
def initialize(container:, current_user: nil, params: {}, spam_params: nil, widget_params: {})
@ -26,6 +27,15 @@ module WorkItems
private
override :handle_quick_actions
def handle_quick_actions(work_item)
# Do not handle quick actions unless the work item is the default Issue.
# The available quick actions for a work item depend on its type and widgets.
return unless work_item.work_item_type.default_issue?
super
end
def prepare_update_params(work_item)
execute_widgets(
work_item: work_item,

View File

@ -8,4 +8,5 @@
"help-page-path" => help_page_path("ci/environments/index.md"),
"project-path" => @project.full_path,
"project-id" => @project.id,
"default-branch-name" => @project.default_branch_or_main } }
"default-branch-name" => @project.default_branch_or_main,
"kas-tunnel-url" => ::Gitlab::Kas.tunnel_url } }

View File

@ -1,7 +1,7 @@
- display_issuable_type = issuable_display_type(@merge_request)
.btn-group.gl-md-ml-3.gl-display-flex.dropdown.gl-dropdown.gl-md-w-auto.gl-w-full
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { toggle: 'dropdown', title: _('Merge request actions'), testid: 'merge-request-actions', 'aria-label': _('Merge request actions') } do
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret gl-display-none! gl-md-display-inline-flex!", title: _('Merge request actions'), 'aria-label': _('Merge request actions'), data: { toggle: 'dropdown', testid: 'merge-request-actions' } do
= sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon"
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do
%span.gl-dropdown-button-text= _('Merge request actions')

View File

@ -0,0 +1,8 @@
---
name: summarize_diff_quick_action
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117458
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/407256
milestone: '15.11'
type: development
group: group::code review
default_enabled: false

View File

@ -34,7 +34,7 @@ GET /projects/:id/dependencies?package_manager=yarn,bundler
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `gradle`, `maven`, `npm`, `nuget`, `pip`, `pipenv`, `yarn`, `sbt`, or `setuptools`. |
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `gradle`, `maven`, `npm`, `nuget`, `pip`, `pipenv`, `pnpm`, `yarn`, `sbt`, or `setuptools`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/dependencies"

View File

@ -199,6 +199,7 @@ module.exports = (path, options = {}) => {
'vue-test-utils-compat',
'@gitlab/ui',
'@gitlab/favicon-overlay',
'@gitlab/cluster-client',
'bootstrap-vue',
'three',
'monaco-editor',

View File

@ -259,7 +259,8 @@ module Gitlab
current_user.can?(:"set_#{quick_action_target.issue_type}_metadata", quick_action_target)
end
command :promote_to_incident do
@updates[:issue_type] = "incident"
@updates[:issue_type] = :incident
@updates[:work_item_type] = ::WorkItems::Type.default_by_type(:incident)
end
desc { _('Add customer relation contacts') }

View File

@ -87,6 +87,8 @@
aggregation: weekly
- name: i_quickactions_subscribe
aggregation: weekly
- name: i_quickactions_summarize_diff
aggregation: weekly
- name: i_quickactions_tableflip
aggregation: weekly
- name: i_quickactions_tag

View File

@ -12185,6 +12185,9 @@ msgstr ""
msgid "Create %{workspace} label"
msgstr ""
msgid "Create LLM-generated summary from diff(s)"
msgstr ""
msgid "Create New Directory"
msgstr ""
@ -12629,6 +12632,9 @@ msgstr ""
msgid "Created on:"
msgstr ""
msgid "Creates a LLM-generated summary from diff(s)."
msgstr ""
msgid "Creates a branch and a merge request to resolve this issue."
msgstr ""
@ -16684,9 +16690,24 @@ msgstr ""
msgid "Environment|Deployment tier"
msgstr ""
msgid "Environment|Failed"
msgstr ""
msgid "Environment|Kubernetes overview"
msgstr ""
msgid "Environment|Pending"
msgstr ""
msgid "Environment|Pods"
msgstr ""
msgid "Environment|Running"
msgstr ""
msgid "Environment|Succeeded"
msgstr ""
msgid "Epic"
msgstr ""
@ -26433,6 +26454,9 @@ msgstr ""
msgid "MD5"
msgstr ""
msgid "MLExperimentTracking|Delete candidate?"
msgstr ""
msgid "MLExperimentTracking|Delete experiment?"
msgstr ""
@ -28307,9 +28331,15 @@ msgstr ""
msgid "MlExperimentTracking|Created at"
msgstr ""
msgid "MlExperimentTracking|Delete candidate"
msgstr ""
msgid "MlExperimentTracking|Delete experiment"
msgstr ""
msgid "MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata."
msgstr ""
msgid "MlExperimentTracking|Deleting this experiment will also delete its candidates and their associated metadata."
msgstr ""
@ -37441,6 +37471,9 @@ msgstr ""
msgid "Request details"
msgstr ""
msgid "Request for summary queued."
msgstr ""
msgid "Request parameter %{param} is missing."
msgstr ""
@ -45059,6 +45092,9 @@ msgstr ""
msgid "This comment changed after you started editing it. Review the %{startTag}updated comment%{endTag} to ensure information is not lost."
msgstr ""
msgid "This comment was generated using OpenAI"
msgstr ""
msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
msgstr ""

View File

@ -53,6 +53,7 @@
"@cubejs-client/core": "^0.32.17",
"@cubejs-client/vue": "^0.32.17",
"@gitlab/at.js": "1.5.7",
"@gitlab/cluster-client": "^1.2.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0",
"@gitlab/svgs": "3.38.0",
@ -64,41 +65,41 @@
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
"@sourcegraph/code-host-integration": "0.0.84",
"@tiptap/core": "^2.0.0-beta.220",
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
"@tiptap/extension-bold": "^2.0.0-beta.220",
"@tiptap/extension-bubble-menu": "2.0.0-beta.220",
"@tiptap/extension-bullet-list": "^2.0.0-beta.220",
"@tiptap/extension-code": "^2.0.0-beta.220",
"@tiptap/extension-code-block": "^2.0.0-beta.220",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.220",
"@tiptap/extension-document": "^2.0.0-beta.220",
"@tiptap/extension-dropcursor": "^2.0.0-beta.220",
"@tiptap/extension-gapcursor": "^2.0.0-beta.220",
"@tiptap/extension-hard-break": "^2.0.0-beta.220",
"@tiptap/extension-heading": "^2.0.0-beta.220",
"@tiptap/extension-highlight": "^2.0.0-beta.220",
"@tiptap/extension-history": "^2.0.0-beta.220",
"@tiptap/extension-horizontal-rule": "^2.0.0-beta.220",
"@tiptap/extension-image": "^2.0.0-beta.220",
"@tiptap/extension-italic": "^2.0.0-beta.220",
"@tiptap/extension-link": "^2.0.0-beta.220",
"@tiptap/extension-list-item": "^2.0.0-beta.220",
"@tiptap/extension-ordered-list": "^2.0.0-beta.220",
"@tiptap/extension-paragraph": "^2.0.0-beta.220",
"@tiptap/extension-strike": "^2.0.0-beta.220",
"@tiptap/extension-subscript": "^2.0.0-beta.220",
"@tiptap/extension-superscript": "^2.0.0-beta.220",
"@tiptap/extension-table": "^2.0.0-beta.220",
"@tiptap/extension-table-cell": "^2.0.0-beta.220",
"@tiptap/extension-table-header": "^2.0.0-beta.220",
"@tiptap/extension-table-row": "^2.0.0-beta.220",
"@tiptap/extension-task-item": "^2.0.0-beta.220",
"@tiptap/extension-task-list": "^2.0.0-beta.220",
"@tiptap/extension-text": "^2.0.0-beta.220",
"@tiptap/pm": "^2.0.0-beta.220",
"@tiptap/suggestion": "^2.0.0-beta.220",
"@tiptap/vue-2": "2.0.0-beta.220",
"@tiptap/core": "^2.0.3",
"@tiptap/extension-blockquote": "^2.0.3",
"@tiptap/extension-bold": "^2.0.3",
"@tiptap/extension-bubble-menu": "2.0.3",
"@tiptap/extension-bullet-list": "^2.0.3",
"@tiptap/extension-code": "^2.0.3",
"@tiptap/extension-code-block": "^2.0.3",
"@tiptap/extension-code-block-lowlight": "2.0.3",
"@tiptap/extension-document": "^2.0.3",
"@tiptap/extension-dropcursor": "^2.0.3",
"@tiptap/extension-gapcursor": "^2.0.3",
"@tiptap/extension-hard-break": "^2.0.3",
"@tiptap/extension-heading": "^2.0.3",
"@tiptap/extension-highlight": "^2.0.3",
"@tiptap/extension-history": "^2.0.3",
"@tiptap/extension-horizontal-rule": "^2.0.3",
"@tiptap/extension-image": "^2.0.3",
"@tiptap/extension-italic": "^2.0.3",
"@tiptap/extension-link": "^2.0.3",
"@tiptap/extension-list-item": "^2.0.3",
"@tiptap/extension-ordered-list": "^2.0.3",
"@tiptap/extension-paragraph": "^2.0.3",
"@tiptap/extension-strike": "^2.0.3",
"@tiptap/extension-subscript": "^2.0.3",
"@tiptap/extension-superscript": "^2.0.3",
"@tiptap/extension-table": "^2.0.3",
"@tiptap/extension-table-cell": "^2.0.3",
"@tiptap/extension-table-header": "^2.0.3",
"@tiptap/extension-table-row": "^2.0.3",
"@tiptap/extension-task-item": "^2.0.3",
"@tiptap/extension-task-list": "^2.0.3",
"@tiptap/extension-text": "^2.0.3",
"@tiptap/pm": "^2.0.3",
"@tiptap/suggestion": "^2.0.3",
"@tiptap/vue-2": "2.0.3",
"@vue/apollo-components": "^4.0.0-beta.4",
"@vue/apollo-option": "^4.0.0-beta.4",
"apollo-upload-client": "15.0.0",

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 10', '>= 10.2.0', require: 'gitlab/qa'
gem 'gitlab-qa', '~> 10', '>= 10.2.1', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.20.0'
gem 'capybara', '~> 3.39.0'

View File

@ -102,7 +102,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
gitlab-qa (10.2.0)
gitlab-qa (10.2.1)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@ -318,7 +318,7 @@ DEPENDENCIES
faraday-retry (~> 2.1)
fog-core (= 2.1.0)
fog-google (~> 1.19)
gitlab-qa (~> 10, >= 10.2.0)
gitlab-qa (~> 10, >= 10.2.1)
influxdb-client (~> 2.9)
knapsack (~> 4.0)
nokogiri (~> 1.14, >= 1.14.3)
@ -343,4 +343,4 @@ DEPENDENCIES
zeitwerk (~> 2.6, >= 2.6.7)
BUNDLED WITH
2.4.11
2.4.12

View File

@ -86,6 +86,16 @@ FactoryBot.define do
association :work_item_type, :default, :key_result
end
trait :incident do
issue_type { :incident }
association :work_item_type, :default, :incident
end
trait :test_case do
issue_type { :test_case }
association :work_item_type, :default, :test_case
end
factory :incident do
issue_type { :incident }
association :work_item_type, :default, :incident

View File

@ -16,7 +16,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
it 'when merge method is set to merge commit' do
visit(merge_request_path(merge_request))
click_button('Merge')
click_merge_button
puts merge_request.short_merged_commit_sha
@ -31,7 +31,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
visit(merge_request_path(merge_request))
click_button('Merge')
click_merge_button
expect(page).to have_content("Changes merged into #{merge_request.target_branch} with #{merge_request.short_merged_commit_sha}")
end
@ -41,7 +41,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
visit(merge_request_path(merge_request))
click_button('Merge')
click_merge_button
expect(page).to have_content("Changes merged into #{merge_request.target_branch} with #{merge_request.short_merged_commit_sha}")
end
@ -55,7 +55,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
it 'accepts a merge request' do
check('Delete source branch')
click_button('Merge')
click_merge_button
expect(page).to have_content('Changes merged into')
expect(page).not_to have_selector('.js-remove-branch-button')
@ -72,7 +72,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
end
it 'accepts a merge request' do
click_button('Merge')
click_merge_button
expect(page).to have_content('Changes merged into')
expect(page).to have_selector('.js-remove-branch-button')
@ -90,7 +90,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
it 'accepts a merge request' do
check('Delete source branch')
click_button('Merge')
click_merge_button
expect(page).to have_content('Changes merged into')
expect(page).not_to have_selector('.js-remove-branch-button')
@ -112,9 +112,15 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
find('[data-testid="widget_edit_commit_message"]').click
fill_in('merge-message-edit', with: 'wow such merge')
click_button('Merge')
click_merge_button
expect(page).to have_selector('.gl-badge', text: 'Merged')
end
end
def click_merge_button
page.within('.mr-state-widget') do
click_button 'Merge'
end
end
end

View File

@ -17,7 +17,9 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).to have_button 'Merge'
page.within('.mr-state-widget') do
expect(page).to have_button 'Merge'
end
end
end
@ -56,7 +58,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).not_to have_button('Merge')
expect(page).not_to have_button('Merge', exact: true)
expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.')
end
end
@ -69,7 +71,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).not_to have_button 'Merge'
expect(page).not_to have_button('Merge', exact: true)
expect(page).to have_content('Merge blocked: pipeline must succeed. Push a commit that fixes the failure or learn about other solutions.')
end
end
@ -82,7 +84,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).to have_button 'Merge'
expect(page).to have_button('Merge', exact: true)
end
end
@ -94,7 +96,7 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).not_to have_button 'Merge'
expect(page).not_to have_button('Merge', exact: true)
end
end
end
@ -126,8 +128,9 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
visit project_merge_request_path(project, merge_request)
wait_for_requests
expect(page).to have_button 'Merge'
page.within('.mr-state-widget') do
expect(page).to have_button 'Merge'
end
end
end
@ -139,7 +142,9 @@ RSpec.describe 'Merge request > User merges only if pipeline succeeds', :js, fea
wait_for_requests
expect(page).to have_button 'Merge'
page.within('.mr-state-widget') do
expect(page).to have_button 'Merge'
end
end
end
end

View File

@ -13,7 +13,9 @@ RSpec.describe 'User reverts a merge request', :js, feature_category: :code_revi
visit(merge_request_path(merge_request))
click_button('Merge')
page.within('.mr-state-widget') do
click_button 'Merge'
end
wait_for_requests

View File

@ -21,7 +21,7 @@ feature_category: :code_review_workflow do
context 'with unresolved threads' do
it 'does not allow to merge' do
expect(page).not_to have_button 'Merge'
expect(page).not_to have_button('Merge', exact: true)
expect(page).to have_content('all threads must be resolved')
end
end
@ -33,7 +33,7 @@ feature_category: :code_review_workflow do
end
it 'allows MR to be merged' do
expect(page).to have_button 'Merge'
expect(page).to have_button('Merge', exact: true)
end
end
end
@ -46,7 +46,7 @@ feature_category: :code_review_workflow do
context 'with unresolved threads' do
it 'does not allow to merge' do
expect(page).to have_button 'Merge'
expect(page).to have_button('Merge', exact: true)
end
end
@ -57,7 +57,7 @@ feature_category: :code_review_workflow do
end
it 'allows MR to be merged' do
expect(page).to have_button 'Merge'
expect(page).to have_button('Merge', exact: true)
end
end
end

View File

@ -396,7 +396,9 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
end
it 'updates the MR widget', :sidekiq_might_not_need_inline do
click_button 'Merge'
page.within('.mr-state-widget') do
click_button 'Merge'
end
expect(page).to have_content('An error occurred while merging')
end
@ -452,7 +454,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js, feature_category:
wait_for_requests
expect(page).not_to have_button('Merge')
expect(page).not_to have_button('Merge', exact: true)
expect(page).to have_content('Merging!')
end
end

View File

@ -21,7 +21,9 @@ feature_category: :code_review_workflow do
before do
sign_in(user)
visit(project_merge_request_path(project, merge_request))
click_button('Merge')
page.within('.mr-state-widget') do
click_button 'Merge'
end
wait_for_requests
end

View File

@ -801,6 +801,14 @@ export const resolvedDeploymentDetails = {
export const agent = {
project: 'agent-project',
id: '1',
id: 'gid://gitlab/ClusterAgent/1',
name: 'agent-name',
kubernetesNamespace: 'agent-namespace',
};
const runningPod = { status: { phase: 'Running' } };
const pendingPod = { status: { phase: 'Pending' } };
const succeededPod = { status: { phase: 'Succeeded' } };
const failedPod = { status: { phase: 'Failed' } };
export const k8sPodsMock = [runningPod, runningPod, pendingPod, succeededPod, failedPod, failedPod];

View File

@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import { CoreV1Api } from '@gitlab/cluster-client';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@ -17,6 +18,7 @@ import {
resolvedEnvironment,
folder,
resolvedFolder,
k8sPodsMock,
} from './mock_data';
const ENDPOINT = `${TEST_HOST}/environments`;
@ -143,6 +145,61 @@ describe('~/frontend/environments/graphql/resolvers', () => {
expect(environmentFolder).toEqual(resolvedFolder);
});
});
describe('k8sPods', () => {
const namespace = 'default';
const configuration = {
basePath: 'kas-proxy/',
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
},
};
const mockPodsListFn = jest.fn().mockImplementation(() => {
return Promise.resolve({
data: {
items: k8sPodsMock,
},
});
});
const mockNamespacedPodsListFn = jest.fn().mockImplementation(mockPodsListFn);
const mockAllPodsListFn = jest.fn().mockImplementation(mockPodsListFn);
beforeEach(() => {
jest
.spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedPod')
.mockImplementation(mockNamespacedPodsListFn);
jest
.spyOn(CoreV1Api.prototype, 'listCoreV1PodForAllNamespaces')
.mockImplementation(mockAllPodsListFn);
});
it('should request namespaced pods from the cluster_client library if namespace is specified', async () => {
const pods = await mockResolvers.Query.k8sPods(null, { configuration, namespace });
expect(mockNamespacedPodsListFn).toHaveBeenCalledWith(namespace);
expect(mockAllPodsListFn).not.toHaveBeenCalled();
expect(pods).toEqual(k8sPodsMock);
});
it('should request all pods from the cluster_client library if namespace is not specified', async () => {
const pods = await mockResolvers.Query.k8sPods(null, { configuration, namespace: '' });
expect(mockAllPodsListFn).toHaveBeenCalled();
expect(mockNamespacedPodsListFn).not.toHaveBeenCalled();
expect(pods).toEqual(k8sPodsMock);
});
it('should throw an error if the API call fails', async () => {
jest
.spyOn(CoreV1Api.prototype, 'listCoreV1PodForAllNamespaces')
.mockRejectedValue(new Error('API error'));
await expect(mockResolvers.Query.k8sPods(null, { configuration })).rejects.toThrow(
'API error',
);
});
});
describe('stopEnvironment', () => {
it('should post to the stop environment path', async () => {
mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);

View File

@ -1,19 +1,28 @@
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlCollapse, GlButton } from '@gitlab/ui';
import { GlCollapse, GlButton, GlAlert } from '@gitlab/ui';
import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
const agent = {
project: 'agent-project',
id: '1',
name: 'agent-name',
};
import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
import { agent } from './graphql/mock_data';
import { mockKasTunnelUrl } from './mock_data';
const propsData = {
agentId: agent.id,
agentName: agent.name,
agentProjectPath: agent.project,
namespace: agent.kubernetesNamespace,
};
const provide = {
kasTunnelUrl: mockKasTunnelUrl,
};
const configuration = {
basePath: provide.kasTunnelUrl.replace(/\/$/, ''),
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
},
};
describe('~/environments/components/kubernetes_overview.vue', () => {
@ -22,10 +31,13 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
const findCollapse = () => wrapper.findComponent(GlCollapse);
const findCollapseButton = () => wrapper.findComponent(GlButton);
const findAgentInfo = () => wrapper.findComponent(KubernetesAgentInfo);
const findKubernetesPods = () => wrapper.findComponent(KubernetesPods);
const findAlert = () => wrapper.findComponent(GlAlert);
const createWrapper = () => {
wrapper = shallowMount(KubernetesOverview, {
propsData,
provide,
});
};
@ -57,6 +69,7 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
it("doesn't render components when the collapse is not visible", () => {
expect(findAgentInfo().exists()).toBe(false);
expect(findKubernetesPods().exists()).toBe(false);
});
it('opens on click', async () => {
@ -70,15 +83,40 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
});
describe('when section is expanded', () => {
it('renders kubernetes agent info', async () => {
beforeEach(() => {
createWrapper();
await toggleCollapse();
toggleCollapse();
});
it('renders kubernetes agent info', () => {
expect(findAgentInfo().props()).toEqual({
agentName: agent.name,
agentId: agent.id,
agentProjectPath: agent.project,
});
});
it('renders kubernetes pods', () => {
expect(findKubernetesPods().props()).toEqual({
namespace: agent.kubernetesNamespace,
configuration,
});
});
});
describe('on cluster error', () => {
beforeEach(() => {
createWrapper();
toggleCollapse();
});
it('shows alert with the error message', async () => {
const error = 'Error message from pods';
findKubernetesPods().vm.$emit('cluster-error', error);
await nextTick();
expect(findAlert().text()).toBe(error);
});
});
});

View File

@ -0,0 +1,114 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
import { mockKasTunnelUrl } from './mock_data';
import { k8sPodsMock } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/kubernetes_pods.vue', () => {
let wrapper;
const namespace = 'my-kubernetes-namespace';
const configuration = {
basePath: mockKasTunnelUrl,
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
},
};
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAllStats = () => wrapper.findAllComponents(GlSingleStat);
const findSingleStat = (at) => findAllStats().at(at);
const createApolloProvider = () => {
const mockResolvers = {
Query: {
k8sPods: jest.fn().mockReturnValue(k8sPodsMock),
},
};
return createMockApollo([], mockResolvers);
};
const createWrapper = (apolloProvider = createApolloProvider()) => {
wrapper = shallowMount(KubernetesPods, {
propsData: { namespace, configuration },
apolloProvider,
});
};
describe('mounted', () => {
it('shows the loading icon', () => {
createWrapper();
expect(findLoadingIcon().exists()).toBe(true);
});
it('hides the loading icon when the list of pods loaded', async () => {
createWrapper();
await waitForPromises();
expect(findLoadingIcon().exists()).toBe(false);
});
});
describe('when gets pods data', () => {
it('renders stats', async () => {
createWrapper();
await waitForPromises();
expect(findAllStats()).toHaveLength(4);
});
it.each`
count | title | index
${2} | ${KubernetesPods.i18n.runningPods} | ${0}
${1} | ${KubernetesPods.i18n.pendingPods} | ${1}
${1} | ${KubernetesPods.i18n.succeededPods} | ${2}
${2} | ${KubernetesPods.i18n.failedPods} | ${3}
`(
'renders stat with title "$title" and count "$count" at index $index',
async ({ count, title, index }) => {
createWrapper();
await waitForPromises();
expect(findSingleStat(index).props()).toMatchObject({
value: count,
title,
});
},
);
});
describe('when gets an error from the cluster_client API', () => {
const error = new Error('Error from the cluster_client API');
const createErroredApolloProvider = () => {
const mockResolvers = {
Query: {
k8sPods: jest.fn().mockRejectedValueOnce(error),
},
};
return createMockApollo([], mockResolvers);
};
beforeEach(async () => {
createWrapper(createErroredApolloProvider());
await waitForPromises();
});
it("doesn't show pods stats", () => {
expect(findAllStats()).toHaveLength(0);
});
it('emits an error message', () => {
expect(wrapper.emitted('cluster-error')).toMatchObject([[error]]);
});
});
});

View File

@ -313,6 +313,8 @@ const createEnvironment = (data = {}) => ({
...data,
});
const mockKasTunnelUrl = 'https://kas.gitlab.com/k8s-proxy';
export {
environment,
environmentsList,
@ -321,4 +323,5 @@ export {
tableData,
deployBoardMockData,
createEnvironment,
mockKasTunnelUrl,
};

View File

@ -12,6 +12,7 @@ import Deployment from '~/environments/components/deployment.vue';
import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
import { resolvedEnvironment, rolloutStatus, agent } from './graphql/mock_data';
import { mockKasTunnelUrl } from './mock_data';
Vue.use(VueApollo);
@ -26,7 +27,13 @@ describe('~/environments/components/new_environment_item.vue', () => {
mountExtended(EnvironmentItem, {
apolloProvider,
propsData: { environment: resolvedEnvironment, ...propsData },
provide: { helpPagePath: '/help', projectId: '1', projectPath: '/1', ...provideData },
provide: {
helpPagePath: '/help',
projectId: '1',
projectPath: '/1',
kasTunnelUrl: mockKasTunnelUrl,
...provideData,
},
stubs: { transition: stubTransition() },
});
@ -536,6 +543,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
agentProjectPath: agent.project,
agentName: agent.name,
agentId: agent.id,
namespace: agent.kubernetesNamespace,
});
});

View File

@ -48,50 +48,48 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
let!(:with_artifact) { create(:ci_build, :success, name: 'with_artifact', job_artifacts: [artifact], pipeline: pipeline) }
let!(:with_coverage) { create(:ci_build, :success, name: 'with_coverage', coverage: 40.0, pipeline: pipeline) }
fixtures_path = 'graphql/jobs/'
get_jobs_query = 'get_jobs.query.graphql'
full_path = 'frontend-fixtures/builds-project'
shared_examples 'graphql queries' do |path, jobs_query|
let_it_be(:variables) { {} }
let_it_be(:query) do
get_graphql_query_as_string("jobs/components/table/graphql/queries/#{get_jobs_query}")
let_it_be(:query) do
get_graphql_query_as_string("#{path}/#{jobs_query}")
end
fixtures_path = 'graphql/jobs/'
it "#{fixtures_path}#{jobs_query}.json" do
post_graphql(query, current_user: user, variables: variables)
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{jobs_query}.as_guest.json" do
guest = create(:user)
project.add_guest(guest)
post_graphql(query, current_user: guest, variables: variables)
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{jobs_query}.paginated.json" do
post_graphql(query, current_user: user, variables: variables.merge({ first: 2 }))
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{jobs_query}.empty.json" do
post_graphql(query, current_user: user, variables: variables.merge({ first: 0 }))
expect_graphql_errors_to_be_empty
end
end
it "#{fixtures_path}#{get_jobs_query}.json" do
post_graphql(query, current_user: user, variables: {
fullPath: full_path
})
expect_graphql_errors_to_be_empty
it_behaves_like 'graphql queries', 'jobs/components/table/graphql/queries', 'get_jobs.query.graphql' do
let(:variables) { { fullPath: 'frontend-fixtures/builds-project' } }
end
it "#{fixtures_path}#{get_jobs_query}.as_guest.json" do
guest = create(:user)
project.add_guest(guest)
post_graphql(query, current_user: guest, variables: {
fullPath: full_path
})
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{get_jobs_query}.paginated.json" do
post_graphql(query, current_user: user, variables: {
fullPath: full_path,
first: 2
})
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{get_jobs_query}.empty.json" do
post_graphql(query, current_user: user, variables: {
fullPath: full_path,
first: 0
})
expect_graphql_errors_to_be_empty
end
it_behaves_like 'graphql queries', 'pages/admin/jobs/components/table/graphql/queries', 'get_all_jobs.query.graphql'
end
describe 'get_jobs_count.query.graphql', type: :request do

View File

@ -1,7 +1,9 @@
import mockJobsCount from 'test_fixtures/graphql/jobs/get_jobs_count.query.graphql.json';
import mockJobsEmpty from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.empty.json';
import mockJobsPaginated from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.paginated.json';
import mockAllJobsPaginated from 'test_fixtures/graphql/jobs/get_all_jobs.query.graphql.paginated.json';
import mockJobs from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.json';
import mockAllJobs from 'test_fixtures/graphql/jobs/get_all_jobs.query.graphql.json';
import mockJobsAsGuest from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.as_guest.json';
import { TEST_HOST } from 'spec/test_constants';
import { TOKEN_TYPE_STATUS } from '~/vue_shared/components/filtered_search_bar/constants';
@ -11,8 +13,10 @@ threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
// Fixtures generated at spec/frontend/fixtures/jobs.rb
export const mockJobsResponsePaginated = mockJobsPaginated;
export const mockAllJobsResponsePaginated = mockAllJobsPaginated;
export const mockJobsResponseEmpty = mockJobsEmpty;
export const mockJobsNodes = mockJobs.data.project.jobs.nodes;
export const mockAllJobsNodes = mockAllJobs.data.jobs.nodes;
export const mockJobsNodesAsGuest = mockJobsAsGuest.data.project.jobs.nodes;
export const mockJobsCountResponse = mockJobsCount;
@ -922,6 +926,14 @@ export const stages = [
},
];
export const statuses = {
success: 'SUCCESS',
failed: 'FAILED',
canceled: 'CANCELED',
pending: 'PENDING',
running: 'RUNNING',
};
export default {
id: 4757,
artifact: {

View File

@ -2,99 +2,36 @@
exports[`MlCandidatesShow renders correctly 1`] = `
<div>
<incubation-alert-stub
featurename="Machine learning experiment tracking"
linktofeedbackissue="https://gitlab.com/gitlab-org/gitlab/-/issues/381660"
/>
<div
class="gl-alert gl-alert-warning"
class="detail-page-header gl-flex-wrap"
>
<svg
aria-hidden="true"
class="gl-icon s16 gl-alert-icon"
data-testid="warning-icon"
role="img"
>
<use
href="#warning"
/>
</svg>
<div
aria-live="assertive"
class="gl-alert-content"
role="alert"
class="detail-page-header-body"
>
<h2
class="gl-alert-title"
>
Machine learning experiment tracking is in incubating phase
</h2>
<div
class="gl-alert-body"
<h1
class="page-title gl-font-size-h-display flex-fill"
>
GitLab incubates features to explore new use cases. These features are updated regularly, and support is limited.
<a
class="gl-link"
href="https://about.gitlab.com/handbook/engineering/incubation/"
rel="noopener noreferrer"
target="_blank"
>
Learn more about incubating features
</a>
</div>
Model candidate details
</h1>
<div
class="gl-alert-actions"
>
<a
class="btn gl-alert-action btn-confirm btn-md gl-button"
href="https://gitlab.com/gitlab-org/gitlab/-/issues/381660"
>
<!---->
<!---->
<span
class="gl-button-text"
>
Give feedback on this feature
</span>
</a>
</div>
<delete-button-stub
actionprimarytext="Delete candidate"
deleteconfirmationtext="Deleting this candidate will delete the associated parameters, metrics, and metadata."
deletepath="path_to_candidate"
modaltitle="Delete candidate?"
/>
</div>
<button
aria-label="Dismiss"
class="btn gl-dismiss-btn btn-default btn-sm gl-button btn-default-tertiary btn-icon"
type="button"
>
<!---->
<svg
aria-hidden="true"
class="gl-button-icon gl-icon s16"
data-testid="close-icon"
role="img"
>
<use
href="#close"
/>
</svg>
<!---->
</button>
</div>
<h3>
Model candidate details
</h3>
<table
class="candidate-details"
class="candidate-details gl-w-full"
>
<tbody>
<tr
@ -143,12 +80,9 @@ exports[`MlCandidatesShow renders correctly 1`] = `
</td>
<td>
<a
class="gl-link"
href="#"
>
<gl-link-stub>
The Experiment
</a>
</gl-link-stub>
</td>
</tr>
@ -162,12 +96,11 @@ exports[`MlCandidatesShow renders correctly 1`] = `
</td>
<td>
<a
class="gl-link"
<gl-link-stub
href="path_to_artifact"
>
Artifacts
</a>
</gl-link-stub>
</td>
</tr>

View File

@ -1,6 +1,7 @@
import { GlAlert } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import IncubationAlert from '~/vue_shared/components/incubation/incubation_alert.vue';
describe('MlCandidatesShow', () => {
let wrapper;
@ -25,23 +26,31 @@ describe('MlCandidatesShow', () => {
experiment_name: 'The Experiment',
experiment_path: 'path/to/experiment',
status: 'SUCCESS',
path: 'path_to_candidate',
},
};
return mountExtended(MlCandidatesShow, { propsData: { candidate } });
wrapper = shallowMountExtended(MlCandidatesShow, { propsData: { candidate } });
};
const findAlert = () => wrapper.findComponent(GlAlert);
beforeEach(createWrapper);
const findAlert = () => wrapper.findComponent(IncubationAlert);
const findDeleteButton = () => wrapper.findComponent(DeleteButton);
it('shows incubation warning', () => {
wrapper = createWrapper();
expect(findAlert().exists()).toBe(true);
});
it('renders correctly', () => {
wrapper = createWrapper();
it('shows delete button', () => {
expect(findDeleteButton().exists()).toBe(true);
});
it('passes the delete path to delete button', () => {
expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate');
});
it('renders correctly', () => {
expect(wrapper.element).toMatchSnapshot();
});
});

View File

@ -0,0 +1,61 @@
import { GlSkeletonLoader, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import getJobsQuery from '~/pages/admin/jobs/components/table/graphql/queries/get_all_jobs.query.graphql';
import AdminJobsTableApp from '~/pages/admin/jobs/components/table/admin_jobs_table_app.vue';
import { mockAllJobsResponsePaginated, statuses } from '../../../../../jobs/mock_data';
Vue.use(VueApollo);
describe('Job table app', () => {
let wrapper;
const successHandler = jest.fn().mockResolvedValue(mockAllJobsResponsePaginated);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(JobsTable);
const createMockApolloProvider = (handler) => {
const requestHandlers = [[getJobsQuery, handler]];
return createMockApollo(requestHandlers);
};
const createComponent = ({
handler = successHandler,
mountFn = shallowMount,
data = {},
} = {}) => {
wrapper = mountFn(AdminJobsTableApp, {
data() {
return {
...data,
};
},
provide: {
jobStatuses: statuses,
},
apolloProvider: createMockApolloProvider(handler),
});
};
describe('loaded state', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('should display the jobs table with data', () => {
expect(findTable().exists()).toBe(true);
expect(findSkeletonLoader().exists()).toBe(false);
expect(findLoadingSpinner().exists()).toBe(false);
});
});
});

View File

@ -0,0 +1,106 @@
import cacheConfig from '~/pages/admin/jobs/components/table/graphql/cache_config';
import {
CIJobConnectionExistingCache,
CIJobConnectionIncomingCache,
CIJobConnectionIncomingCacheRunningStatus,
} from '../../../../../../jobs/mock_data';
const firstLoadArgs = { first: 3, statuses: 'PENDING' };
const runningArgs = { first: 3, statuses: 'RUNNING' };
describe('jobs/components/table/graphql/cache_config', () => {
describe('when fetching data with the same statuses', () => {
it('should contain cache nodes and a status when merging caches on first load', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge({}, CIJobConnectionIncomingCache, {
args: firstLoadArgs,
});
expect(res.nodes).toHaveLength(CIJobConnectionIncomingCache.nodes.length);
expect(res.statuses).toBe('PENDING');
});
it('should add to existing caches when merging caches after first load', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge(
CIJobConnectionExistingCache,
CIJobConnectionIncomingCache,
{
args: firstLoadArgs,
},
);
expect(res.nodes).toHaveLength(
CIJobConnectionIncomingCache.nodes.length + CIJobConnectionExistingCache.nodes.length,
);
});
it('should not add to existing cache if the incoming elements are the same', () => {
// simulate that this is the last page
const finalExistingCache = {
...CIJobConnectionExistingCache,
pageInfo: {
hasNextPage: false,
},
};
const res = cacheConfig.typePolicies.CiJobConnection.merge(
CIJobConnectionExistingCache,
finalExistingCache,
{
args: firstLoadArgs,
},
);
expect(res.nodes).toHaveLength(CIJobConnectionExistingCache.nodes.length);
});
it('should contain the pageInfo key as part of the result', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge({}, CIJobConnectionIncomingCache, {
args: firstLoadArgs,
});
expect(res.pageInfo).toEqual(
expect.objectContaining({
__typename: 'PageInfo',
endCursor: 'eyJpZCI6IjIwNTEifQ',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'eyJpZCI6IjIxNzMifQ',
}),
);
});
});
describe('when fetching data with different statuses', () => {
it('should reset cache when a cache already exists', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge(
CIJobConnectionExistingCache,
CIJobConnectionIncomingCacheRunningStatus,
{
args: runningArgs,
},
);
expect(res.nodes).not.toEqual(CIJobConnectionExistingCache.nodes);
expect(res.nodes).toHaveLength(CIJobConnectionIncomingCacheRunningStatus.nodes.length);
});
});
describe('when incoming data has no nodes', () => {
it('should return existing cache', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge(
CIJobConnectionExistingCache,
{ __typename: 'CiJobConnection', count: 500 },
{
args: { statuses: 'SUCCESS' },
},
);
const expectedResponse = {
...CIJobConnectionExistingCache,
statuses: 'SUCCESS',
};
expect(res).toEqual(expectedResponse);
});
});
});

View File

@ -265,7 +265,10 @@ RSpec.describe GitlabSchema.types['Issue'] do
context 'for an incident' do
before do
issue.update!(issue_type: Issue.issue_types[:incident])
issue.update!(
issue_type: Issue.issue_types[:incident],
work_item_type: WorkItems::Type.default_by_type(:incident)
)
end
it { is_expected.to be_nil }

View File

@ -165,7 +165,8 @@ RSpec.describe IntegrationsHelper do
with_them do
before do
issue.update!(issue_type: issue_type)
issue.assign_attributes(issue_type: issue_type, work_item_type: WorkItems::Type.default_by_type(issue_type))
issue.save!(validate: false)
end
it "return the correct i18n issue type" do

View File

@ -114,7 +114,8 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do
'path_to_artifact' => "/#{project.full_path}/-/packages/#{candidate.artifact.id}",
'experiment_name' => candidate.experiment.name,
'path_to_experiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}",
'status' => 'running'
'status' => 'running',
'path' => "/#{project.full_path}/-/ml/candidates/#{candidate.iid}"
}
expect(subject['info']).to include(expected_info)

View File

@ -160,7 +160,7 @@ RSpec.describe Issue, feature_category: :team_planning do
it 'is possible to change type only between selected types' do
issue = create(:issue, old_type, project: reusable_project)
issue.work_item_type_id = WorkItems::Type.default_by_type(new_type).id
issue.assign_attributes(work_item_type: WorkItems::Type.default_by_type(new_type), issue_type: new_type)
expect(issue.valid?).to eq(is_valid)
end
@ -254,7 +254,7 @@ RSpec.describe Issue, feature_category: :team_planning do
describe '#ensure_work_item_type' do
let_it_be(:issue_type) { create(:work_item_type, :issue, :default) }
let_it_be(:task_type) { create(:work_item_type, :issue, :default) }
let_it_be(:incident_type) { create(:work_item_type, :incident, :default) }
let_it_be(:project) { create(:project) }
context 'when a type was already set' do
@ -271,9 +271,9 @@ RSpec.describe Issue, feature_category: :team_planning do
expect(issue.work_item_type_id).to eq(issue_type.id)
expect(WorkItems::Type).not_to receive(:default_by_type)
issue.update!(work_item_type: task_type, issue_type: 'task')
issue.update!(work_item_type: incident_type, issue_type: :incident)
expect(issue.work_item_type_id).to eq(task_type.id)
expect(issue.work_item_type_id).to eq(incident_type.id)
end
it 'ensures a work item type if updated to nil' do
@ -300,13 +300,23 @@ RSpec.describe Issue, feature_category: :team_planning do
expect(issue.work_item_type_id).to be_nil
expect(WorkItems::Type).not_to receive(:default_by_type)
issue.update!(work_item_type: task_type, issue_type: 'task')
issue.update!(work_item_type: incident_type, issue_type: :incident)
expect(issue.work_item_type_id).to eq(task_type.id)
expect(issue.work_item_type_id).to eq(incident_type.id)
end
end
end
describe '#check_issue_type_in_sync' do
it 'raises an error if issue_type is out of sync' do
issue = build(:issue, issue_type: :issue, work_item_type: WorkItems::Type.default_by_type(:task))
expect do
issue.save!
end.to raise_error(Issue::IssueTypeOutOfSyncError)
end
end
describe '#record_create_action' do
it 'records the creation action after saving' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_created_action)
@ -1816,7 +1826,7 @@ RSpec.describe Issue, feature_category: :team_planning do
with_them do
before do
issue.update!(issue_type: issue_type)
issue.update!(issue_type: issue_type, work_item_type: WorkItems::Type.default_by_type(issue_type))
end
it do
@ -1836,7 +1846,7 @@ RSpec.describe Issue, feature_category: :team_planning do
with_them do
before do
issue.update!(issue_type: issue_type)
issue.update!(issue_type: issue_type, work_item_type: WorkItems::Type.default_by_type(issue_type))
end
it do

View File

@ -1223,17 +1223,16 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
end
context 'when unsupported widget input is sent' do
let_it_be(:test_case) { create(:work_item_type, :default, :test_case) }
let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
let_it_be(:work_item) { create(:work_item, :incident, project: project) }
let(:input) do
{
'hierarchyWidget' => {}
'assigneesWidget' => { 'assigneeIds' => [developer.to_gid.to_s] }
}
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ["Following widget keys are not supported by Test Case type: [:hierarchy_widget]"]
errors: ["Following widget keys are not supported by Incident type: [:assignees_widget]"]
end
end
end

View File

@ -64,7 +64,7 @@ RSpec.describe Projects::Ml::CandidatesController, feature_category: :mlops do
describe 'DELETE #destroy' do
let_it_be(:candidate_for_deletion) do
create(:ml_candidates, experiment: experiment, user: user)
create(:ml_candidates, project: project, experiment: experiment, user: user)
end
let(:candidate_iid) { candidate_for_deletion.iid }
@ -76,6 +76,7 @@ RSpec.describe Projects::Ml::CandidatesController, feature_category: :mlops do
it 'deletes the experiment', :aggregate_failures do
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to eq('Candidate removed')
expect(response).to redirect_to("/#{project.full_path}/-/ml/experiments/#{experiment.iid}")
expect { Ml::Candidate.find(id: candidate_for_deletion.id) }.to raise_error(ActiveRecord::RecordNotFound)
end

View File

@ -44,7 +44,10 @@ RSpec.describe IssueSidebarBasicEntity do
context 'for an incident issue' do
before do
issue.update!(issue_type: Issue.issue_types[:incident])
issue.update!(
issue_type: Issue.issue_types[:incident],
work_item_type: WorkItems::Type.default_by_type(:incident)
)
end
it 'is present and true' do

View File

@ -57,7 +57,15 @@ RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
end
context 'when filtering' do
let_it_be(:incident) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1], issue_type: 'incident') }
let_it_be(:incident) do
create(
:labeled_issue,
:incident,
project: project,
milestone: m1,
labels: [development, p1]
)
end
context 'when filtering by type' do
it 'only returns the specified type' do

View File

@ -0,0 +1,31 @@
config:
test1:
stage: test
script: exit 0
needs: []
test2:
stage: test
when: on_failure
script: exit 0
needs: []
init:
expect:
pipeline: pending
stages:
test: pending
jobs:
test1: pending
test2: skipped
transitions:
- event: success
jobs: [test1]
expect:
pipeline: success
stages:
test: success
jobs:
test1: success
test2: skipped

View File

@ -0,0 +1,29 @@
config:
test1:
stage: test
script: exit 0
test2:
stage: test
when: on_failure
script: exit 0
init:
expect:
pipeline: pending
stages:
test: pending
jobs:
test1: pending
test2: skipped
transitions:
- event: success
jobs: [test1]
expect:
pipeline: success
stages:
test: success
jobs:
test1: success
test2: skipped

View File

@ -172,37 +172,5 @@ RSpec.describe Issues::BuildService, feature_category: :team_planning do
end
end
end
describe 'setting issue type' do
context 'with a corresponding WorkItems::Type' do
let_it_be(:type_issue_id) { WorkItems::Type.default_issue_type.id }
let_it_be(:type_incident_id) { WorkItems::Type.default_by_type(:incident).id }
where(:issue_type, :current_user, :work_item_type_id, :resulting_issue_type) do
nil | ref(:guest) | ref(:type_issue_id) | 'issue'
'issue' | ref(:guest) | ref(:type_issue_id) | 'issue'
'incident' | ref(:guest) | ref(:type_issue_id) | 'issue'
'incident' | ref(:reporter) | ref(:type_incident_id) | 'incident'
# update once support for test_case is enabled
'test_case' | ref(:guest) | ref(:type_issue_id) | 'issue'
# update once support for requirement is enabled
'requirement' | ref(:guest) | ref(:type_issue_id) | 'issue'
'invalid' | ref(:guest) | ref(:type_issue_id) | 'issue'
# ensure that we don't set a value which has a permission check but is an invalid issue type
'project' | ref(:guest) | ref(:type_issue_id) | 'issue'
end
with_them do
let(:user) { current_user }
it 'builds an issue' do
issue = build_issue(issue_type: issue_type)
expect(issue.issue_type).to eq(resulting_issue_type)
expect(issue.work_item_type_id).to eq(work_item_type_id)
end
end
end
end
end
end

View File

@ -574,36 +574,6 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
end
context 'Quick actions' do
context 'as work item' do
let(:opts) do
{
title: "My work item",
work_item_type: work_item_type,
description: "/shrug"
}
end
context 'when work item type is not the default Issue' do
let(:work_item_type) { create(:work_item_type, namespace: project.namespace) }
it 'saves the work item without applying the quick action' do
expect(result).to be_success
expect(issue).to be_persisted
expect(issue.description).to eq("/shrug")
end
end
context 'when work item type is the default Issue' do
let(:work_item_type) { WorkItems::Type.default_by_type(:issue) }
it 'saves the work item and applies the quick action' do
expect(result).to be_success
expect(issue).to be_persisted
expect(issue.description).to eq(" ¯\\_(ツ)_")
end
end
end
context 'with assignee, milestone, and contact in params and command' do
let_it_be(:contact) { create(:contact, group: group) }
@ -696,6 +666,23 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
expect(issue.labels).to eq([label])
end
end
context 'when using promote_to_incident' do
let(:opts) { { title: 'Title', description: '/promote_to_incident' } }
before do
project.add_developer(user)
end
it 'creates an issue with the correct issue type' do
expect { result }.to change(Issue, :count).by(1)
created_issue = Issue.last
expect(created_issue.issue_type).to eq('incident')
expect(created_issue.work_item_type).to eq(WorkItems::Type.default_by_type('incident'))
end
end
end
context 'resolving discussions' do
@ -864,5 +851,49 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
subject.execute
end
end
describe 'setting issue type' do
using RSpec::Parameterized::TableSyntax
let_it_be(:guest) { user.tap { |u| project.add_guest(u) } }
let_it_be(:reporter) { assignee.tap { |u| project.add_reporter(u) } }
context 'with a corresponding WorkItems::Type' do
let_it_be(:type_issue_id) { WorkItems::Type.default_issue_type.id }
let_it_be(:type_incident_id) { WorkItems::Type.default_by_type(:incident).id }
where(:issue_type, :current_user, :work_item_type_id, :resulting_issue_type) do
nil | ref(:guest) | ref(:type_issue_id) | 'issue'
'issue' | ref(:guest) | ref(:type_issue_id) | 'issue'
'incident' | ref(:guest) | ref(:type_issue_id) | 'issue'
'incident' | ref(:reporter) | ref(:type_incident_id) | 'incident'
# update once support for test_case is enabled
'test_case' | ref(:guest) | ref(:type_issue_id) | 'issue'
# update once support for requirement is enabled
'requirement' | ref(:guest) | ref(:type_issue_id) | 'issue'
'invalid' | ref(:guest) | ref(:type_issue_id) | 'issue'
# ensure that we don't set a value which has a permission check but is an invalid issue type
'project' | ref(:guest) | ref(:type_issue_id) | 'issue'
end
with_them do
let(:user) { current_user }
let(:params) { { title: 'title', issue_type: issue_type } }
let(:issue) do
described_class.new(
container: project,
current_user: user,
params: params,
spam_params: spam_params
).execute[:issue]
end
it 'creates an issue' do
expect(issue.issue_type).to eq(resulting_issue_type)
expect(issue.work_item_type_id).to eq(work_item_type_id)
end
end
end
end
end
end

View File

@ -1493,31 +1493,5 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning
let(:existing_issue) { create(:issue, project: project) }
let(:issuable) { described_class.new(container: project, current_user: user, params: params).execute(existing_issue) }
end
context 'with quick actions' do
context 'as work item' do
let(:opts) { { description: "/shrug" } }
context 'when work item type is not the default Issue' do
let(:issue) { create(:work_item, :task, description: "") }
it 'does not apply the quick action' do
expect do
update_issue(opts)
end.to change(issue, :description).to("/shrug")
end
end
context 'when work item type is the default Issue' do
let(:issue) { create(:work_item, :issue, description: "") }
it 'does not apply the quick action' do
expect do
update_issue(opts)
end.to change(issue, :description).to(" ¯\\_(ツ)_")
end
end
end
end
end
end

View File

@ -182,7 +182,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do
context 'on an incident' do
before do
issue.update!(issue_type: :incident)
issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident))
end
it 'leaves the note empty' do
@ -224,7 +224,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do
context 'on an incident' do
before do
issue.update!(issue_type: :incident)
issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident))
end
it 'leaves the note empty' do

View File

@ -81,6 +81,37 @@ RSpec.describe WorkItems::CreateService, feature_category: :team_planning do
end
end
context 'when applying quick actions' do
let(:work_item) { service_result[:work_item] }
let(:opts) do
{
title: 'My work item',
work_item_type: work_item_type,
description: '/shrug'
}
end
context 'when work item type is not the default Issue' do
let(:work_item_type) { create(:work_item_type, :task, namespace: group) }
it 'saves the work item without applying the quick action' do
expect(service_result).to be_success
expect(work_item).to be_persisted
expect(work_item.description).to eq('/shrug')
end
end
context 'when work item type is the default Issue' do
let(:work_item_type) { WorkItems::Type.default_by_type(:issue) }
it 'saves the work item and applies the quick action' do
expect(service_result).to be_success
expect(work_item).to be_persisted
expect(work_item.description).to eq(' ¯\_(ツ)_/¯')
end
end
end
context 'when params are valid' do
it 'created instance is a WorkItem' do
expect(Issuable::CommonSystemNotesService).to receive_message_chain(:new, :execute)

View File

@ -44,6 +44,33 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
end
end
context 'when applying quick actions' do
let(:opts) { { description: "/shrug" } }
context 'when work item type is not the default Issue' do
before do
task_type = WorkItems::Type.default_by_type(:task)
work_item.update_columns(issue_type: task_type.base_type, work_item_type_id: task_type.id)
end
it 'does not apply the quick action' do
expect do
update_work_item
end.to change(work_item, :description).to('/shrug')
end
end
context 'when work item type is the default Issue' do
let(:issue) { create(:work_item, :issue, description: '') }
it 'applies the quick action' do
expect do
update_work_item
end.to change(work_item, :description).to(' ¯\_(ツ)_/¯')
end
end
end
context 'when title is changed' do
let(:opts) { { title: 'changed' } }

View File

@ -914,9 +914,9 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
end
context 'filtering by item type' do
let_it_be(:incident_item) { create(factory, issue_type: :incident, project: project1) }
let_it_be(:objective) { create(factory, issue_type: :objective, project: project1) }
let_it_be(:key_result) { create(factory, issue_type: :key_result, project: project1) }
let_it_be(:incident_item) { create(factory, :incident, project: project1) }
let_it_be(:objective) { create(factory, :objective, project: project1) }
let_it_be(:key_result) { create(factory, :key_result, project: project1) }
context 'no type given' do
let(:params) { { issue_types: [] } }

View File

@ -36,7 +36,7 @@ RSpec.describe IncidentManagement::CloseIncidentWorker, feature_category: :incid
context 'when issue type is not incident' do
before do
issue.update!(issue_type: :issue)
issue.update!(issue_type: :issue, work_item_type: WorkItems::Type.default_by_type(:issue))
end
it_behaves_like 'does not call the close issue service'

326
yarn.lock
View File

@ -1085,10 +1085,19 @@
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
"@gitlab/cluster-client@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@gitlab/cluster-client/-/cluster-client-1.2.0.tgz#3b56da46748403354b5af73678b17db3851cbe53"
integrity sha512-2emHgfOF9CdibzwXJ2yZVf2d+ez8b67O47qa+pwlG+NnYatjfIcj9Pkzs4kBcO/9+j2lVH/EegPDyEkZZt8Irg==
dependencies:
axios "^0.24.0"
core-js "^3.29.1"
"@gitlab/eslint-plugin@18.3.2":
version "18.3.2"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-18.3.2.tgz#dc4d5b487e26a1473106c1a3e34ae3ea219d4dd1"
integrity sha512-Lz0RnEW5isZ/jkeHcr2k6NqaHISwgKeWN/vkWUU5J4Ax7oYPR0CgA2KO/dEnOvIPmGfbnUKowsekBmmy5SUQHA==
dependencies:
"@babel/core" "^7.17.0"
"@babel/eslint-parser" "^7.17.0"
@ -1827,182 +1836,181 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
"@tiptap/core@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.220.tgz#ced4b8f13ad6361f957275510bd0c005de29d18c"
integrity sha512-F2Q666xJqijBU5o+GqekqseNgIEMTs6BhsLDaf9DwThhljGLS8RXKnSvQxrxLNrYEPpw39n/G3Qt8YAOk5qR6w==
"@tiptap/core@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.3.tgz#dfd55124b3e7b0482e5ccb8be46eb9c3189167e2"
integrity sha512-jLyVIWAdjjlNzrsRhSE2lVL/7N8228/1R1QtaVU85UlMIwHFAcdzhD8FeiKkqxpTnGpaDVaTy7VNEtEgaYdCyA==
"@tiptap/extension-blockquote@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz#acce6a7d2fda829296e1e0b6386f618ea8ae328e"
integrity sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==
"@tiptap/extension-blockquote@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.3.tgz#3ee7aff66a2526501154ca69f3e91e153c58313c"
integrity sha512-rkUcFv2iL6f86DBBHoa4XdKNG2StvkJ7tfY9GoMpT46k3nxOaMTqak9/qZOo79TWxMLYtXzoxtKIkmWsbbcj4A==
"@tiptap/extension-bold@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz#f10468317fd5c63ebab68be907e33fb138a60ef9"
integrity sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==
"@tiptap/extension-bold@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.3.tgz#2a28816195562a39c33f50e626796d14a800784f"
integrity sha512-OGT62fMRovSSayjehumygFWTg2Qn0IDbqyMpigg/RUAsnoOI2yBZFVrdM2gk1StyoSay7gTn2MLw97IUfr7FXg==
"@tiptap/extension-bubble-menu@2.0.0-beta.220", "@tiptap/extension-bubble-menu@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.220.tgz#3fea0c846f73a237f562fdce05671ef1fa025943"
integrity sha512-wthyec7s0vZlTSEAAZEgoFfx/1Arwg1zxDUrrE+YAost/Yn+w4xQksz/ts5Bx90iOk2qsJ+jzzttLRV17Ku7lA==
dependencies:
lodash "^4.17.21"
tippy.js "^6.3.7"
"@tiptap/extension-bullet-list@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz#ffc04992bbee53bc858aab6c082f17419a2236b7"
integrity sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==
"@tiptap/extension-code-block-lowlight@2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.220.tgz#9496a1989b5385872456f6910a43ac822b4896c1"
integrity sha512-xMwbl5O50yaIGYQF3yrBM7Ft0JYejkuEo161jRElYG+PYvUCvbf2wB5oLNtRknj00WOM01kPXH2xMTuk8fTOPg==
"@tiptap/extension-code-block@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz#8396b72f634d77d23b9ea01c9a253e8a7f471471"
integrity sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==
"@tiptap/extension-code@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.220.tgz#3543afeda2b0b240682a36eeb401b00a3da56ab6"
integrity sha512-JKKDZoceagqVXeC1XF/gOkKhLtsbYJYV+MRDorLnQVz4tXcg/SMs5Ez7OM9MxSSior8fIbUFMNsj1/UNlG+tFw==
"@tiptap/extension-document@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.220.tgz#15b4db7a92659eff7efc6d4d877dcf72e3fd61b6"
integrity sha512-2sja4ZvOb4iynHrzinnclCSFgLyo6fJc1fBV5fIYaOgZOYcvz9KK8fgKiq+wIpG58sJEmQ5kcwwBlkXv+NTK+g==
"@tiptap/extension-dropcursor@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz#b635fa6cdf9be1027579c7ab6c00e5a811b3b30b"
integrity sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==
"@tiptap/extension-floating-menu@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.220.tgz#35eb154227533ada738c922be2f8cf18426fe4bf"
integrity sha512-+WfcBEedm82ntaVIEQAGz0Om96Rpav7a+4f7e8N4PrLKm6nZ3gBaEkZVQ6vjJ6S/1htiWCv1XosYIwRboPBG0w==
"@tiptap/extension-bubble-menu@2.0.3", "@tiptap/extension-bubble-menu@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.3.tgz#44b3c4e35fd478c42467d8fb7dbc9532614e5b18"
integrity sha512-lPt1ELrYCuoQrQEUukqjp9xt38EwgPUwaKHI3wwt2Rbv+C6q1gmRsK1yeO/KqCNmFxNqF2p9ZF9srOnug/RZDQ==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-gapcursor@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz#07c96f7adc354d19b6209ea1e080188fb8d63de5"
integrity sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==
"@tiptap/extension-bullet-list@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.3.tgz#43c4c0c161d5c065f3f87e4bf54d13bd6c55b4c3"
integrity sha512-RtaLiRvZbMTOje+FW5bn+mYogiIgNxOm065wmyLPypnTbLSeHeYkoqVSqzZeqUn+7GLnwgn1shirUe6csVE/BA==
"@tiptap/extension-hard-break@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz#8ff432615d9c9090c3d59c2a745c88e4f39ab1a3"
integrity sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==
"@tiptap/extension-code-block-lowlight@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.3.tgz#42cad47b048d4657cb0d554a890abd1bc7072451"
integrity sha512-thFXcFdFyHF0/dr9sqBedjj0Vt14k3m52YVc4l65+d65wRuHp4f8suu8T2ZGRJwqLCE3NIrvwQTSHhzjIqJVxQ==
"@tiptap/extension-heading@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.220.tgz#b4889de7b3f152ff88a119d6cb6a22537eff73a2"
integrity sha512-7mrHRj++UaZ26C2Gjwb0WKWAzpiKb8TOYkVC2uMaCwaNhLDXpFEwZ7RtJRSTNBHkIGnMO46BH8Z0qlkFMmk9Jw==
"@tiptap/extension-code-block@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.3.tgz#4ce08b4f3c5af166d3cc00e91ba5b989f01fee63"
integrity sha512-F4xMy18EwgpyY9f5Te7UuF7UwxRLptOtCq1p2c2DfxBvHDWhAjQqVqcW/sq/I/WuED7FwCnPLyyAasPiVPkLPw==
"@tiptap/extension-highlight@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.0-beta.220.tgz#1bf2954524b99bb393dad46b5613b84aa660713f"
integrity sha512-+h4seFq99b0dCmShVlSc44PBQUiW4xBXze61V6ZNILLkfzo27wrj0W+I3WrdSXX9uz3wwE/BR+3T8m1Ro8lHng==
"@tiptap/extension-code@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.3.tgz#74d88073faedd1fc52d6ed3de4eed8fde80ff4bf"
integrity sha512-LsVCKVxgBtkstAr1FjxN8T3OjlC76a2X8ouoZpELMp+aXbjqyanCKzt+sjjUhE4H0yLFd4v+5v6UFoCv4EILiw==
"@tiptap/extension-history@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz#6370b28872b29288d655cd14211efb8dc76daba0"
integrity sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==
"@tiptap/extension-document@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.3.tgz#b58af5b4f71c0acea953a7ebe8b1d24341bfaf68"
integrity sha512-PsYeNQQBYIU9ayz1R11Kv/kKNPFNIV8tApJ9pxelXjzcAhkjncNUazPN/dyho60mzo+WpsmS3ceTj/gK3bCtWA==
"@tiptap/extension-horizontal-rule@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz#4b8eaf081b38359235312308ebd59950705c7b10"
integrity sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==
"@tiptap/extension-dropcursor@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.3.tgz#205d02c70b200810572d0b7e264bbdb718343ad0"
integrity sha512-McthMrfusn6PjcaynJLheZJcXto8TaIW5iVitYh8qQrDXr31MALC/5GvWuiswmQ8bAXiWPwlLDYE/OJfwtggaw==
"@tiptap/extension-image@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.220.tgz#c197b0dbd2f5d7a08f91e63cb8ca98c1972159a4"
integrity sha512-xyzlY/cupj/7AVqybQDaPaJ3SwKqe12xMWQlWxhhksuNpbQ6RGHrJz0DBSe61kIkaTZmIUBw055IFEMOPFF53g==
"@tiptap/extension-floating-menu@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.3.tgz#8d9943246aa3247442c1993f235617094fe705b5"
integrity sha512-zN1vRGRvyK3pO2aHRmQSOTpl4UJraXYwKYM009n6WviYKUNm0LPGo+VD4OAtdzUhPXyccnlsTv2p6LIqFty6Bg==
dependencies:
tippy.js "^6.3.7"
"@tiptap/extension-italic@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz#94e442689f69e694a2a983eabcae0ccc803262b9"
integrity sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==
"@tiptap/extension-gapcursor@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.3.tgz#e098b78c4a169e1630dc6531d68b7f365de59c2f"
integrity sha512-6I9EzzsYOyyqDvDvxIK6Rv3EXB+fHKFj8ntHO8IXmeNJ6pkhOinuXVsW6Yo7TcDYoTj4D5I2MNFAW2rIkgassw==
"@tiptap/extension-link@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.220.tgz#c9954613cd1e0a0f1527853b732ef50dff734eac"
integrity sha512-vjEA8cE37ZZVVgPHSpttw3kbJoClb+ya/BVukDtJ1h6C7mIR1rqzNxTgpbnXJuA8xww0JOjpa5dpzEgcs294fA==
"@tiptap/extension-hard-break@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.3.tgz#aa7805d825e5244bdccc508da18c781e231b2859"
integrity sha512-RCln6ARn16jvKTjhkcAD5KzYXYS0xRMc0/LrHeV8TKdCd4Yd0YYHe0PU4F9gAgAfPQn7Dgt4uTVJLN11ICl8sQ==
"@tiptap/extension-heading@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.3.tgz#5e9e779f33f366afcf729d9f68ef49721f825e11"
integrity sha512-f0IEv5ms6aCzL80WeZ1qLCXTkRVwbpRr1qAETjg3gG4eoJN18+lZNOJYpyZy3P92C5KwF2T3Av00eFyVLIbb8Q==
"@tiptap/extension-highlight@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.0.3.tgz#4a52de6666dfe4a80b018aa43805d2d220e90219"
integrity sha512-NrtibY8cZkIjZMQuHRrKd4php+plOvAoSo8g3uVFu275I/Ixt5HqJ53R4voCXs8W8BOBRs2HS2QX8Cjh79XhtA==
"@tiptap/extension-history@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.3.tgz#8936c15aa46f2ddeada1c3d9abe2888d58d08c30"
integrity sha512-00KHIcJ8kivn2ARI6NQYphv2LfllVCXViHGm0EhzDW6NQxCrriJKE3tKDcTFCu7LlC5doMpq9Z6KXdljc4oVeQ==
"@tiptap/extension-horizontal-rule@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.3.tgz#5c67db2c0bf3bc14a8aab80df584bee5aa23fbeb"
integrity sha512-SZRUSh07b/M0kJHNKnfBwBMWrZBEm/E2LrK1NbluwT3DBhE+gvwiEdBxgB32zKHNxaDEXUJwUIPNC3JSbKvPUA==
"@tiptap/extension-image@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.3.tgz#048484b2e059d4bed78f97f08651bd57b41855a9"
integrity sha512-hS9ZJwz0md07EHsC+o4NuuJkhCZsZn7TuRz/2CvRSj2fWFIz+40CyNAHf/2J0qNugG9ommXaemetsADeEZP9ag==
"@tiptap/extension-italic@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.3.tgz#2d9d5d8ccf3c38266f745029c2ec0646c075c1fc"
integrity sha512-cfS5sW0gu7qf4ihwnLtW/QMTBrBEXaT0sJl3RwkhjIBg/65ywJKE5Nz9ewnQHmDeT18hvMJJ1VIb4j4ze9jj9A==
"@tiptap/extension-link@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.3.tgz#4714a4c23d04032e75b5b8364a9c532f7a385aba"
integrity sha512-H72tXQ5rkVCkAhFaf08fbEU7EBUCK0uocsqOF+4th9sOlrhfgyJtc8Jv5EXPDpxNgG5jixSqWBo0zKXQm9s9eg==
dependencies:
linkifyjs "^4.1.0"
"@tiptap/extension-list-item@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz#c2fcff1fb9148d303d78b0336032a6353a86ff6c"
integrity sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==
"@tiptap/extension-list-item@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.3.tgz#2bca673b1ed83fdc00cb208f4d5c57d4d44ddb22"
integrity sha512-p7cUsk0LpM1PfdAuFE8wYBNJ3gvA0UhNGR08Lo++rt9UaCeFLSN1SXRxg97c0oa5+Ski7SrCjIJ5Ynhz0viTjQ==
"@tiptap/extension-ordered-list@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz#1fac8e8c2f8c0187e23ede59764fd031d5d1a83a"
integrity sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==
"@tiptap/extension-ordered-list@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.3.tgz#d1e5d6fc240545dbba7f7e6666bebd658fc3b4ad"
integrity sha512-ZB3MpZh/GEy1zKgw7XDQF4FIwycZWNof1k9WbDZOI063Ch4qHZowhVttH2mTCELuyvTMM/o9a8CS7qMqQB48bw==
"@tiptap/extension-paragraph@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz#d552dfdeeab9856e9eb8f0a7cf850f37d7cced69"
integrity sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==
"@tiptap/extension-paragraph@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.3.tgz#88d332158c70622d36849256f90e43ca4d226dfe"
integrity sha512-a+tKtmj4bU3GVCH1NE8VHWnhVexxX5boTVxsHIr4yGG3UoKo1c5AO7YMaeX2W5xB5iIA+BQqOPCDPEAx34dd2A==
"@tiptap/extension-strike@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz#2beb02d2d8807056ff3ea4ea74d9f6abba42bf78"
integrity sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==
"@tiptap/extension-strike@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.3.tgz#4ec0001db5f51f86d06da22364114f20f073d4b3"
integrity sha512-RO4/EYe2iPD6ifDHORT8fF6O9tfdtnzxLGwZIKZXnEgtweH+MgoqevEzXYdS+54Wraq4TUQGNcsYhe49pv7Rlw==
"@tiptap/extension-subscript@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.0-beta.220.tgz#3b9ebd181804f411f7755b8a73d1863dc72c5b8a"
integrity sha512-+C6nyAU4aaeCMvtBI1CJrMseE+YYqLUmmUVOK4ka3ZjmYkn1n+Tduf0ZGQHYmSSMDHPqQ8KsN+AQwaeSWKM/dA==
"@tiptap/extension-subscript@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-subscript/-/extension-subscript-2.0.3.tgz#26be9609b52dcdc1ff0f0f00e9e3bc01dd464f78"
integrity sha512-XFAEUaKxWRmTq7ePEF4aj7knelJPr2fTz0y/iSXydtS094LKwBHBzxatIZY3phrgfpDc+f51ycwarsgz27UJfg==
"@tiptap/extension-superscript@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.0-beta.220.tgz#93289e25bce0fb13608d7d41e74a13a3faf4d3c6"
integrity sha512-h7Qh8Jqb5r84hS0GhhQdNPFk+6AZhvbOKv/4dP6g9S5mRc287WlfhTrbpMdHI/p0r5vKkpLmAXpNCn6IImd3jQ==
"@tiptap/extension-superscript@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-superscript/-/extension-superscript-2.0.3.tgz#7d57b2517a2f2e1ffb603edba5f05c6631bfe3a7"
integrity sha512-5EBjUvkw2SXL1e8C1i0UF26/GBNHxEbiNQKw7Shy88omVa4HTY+D8KWC/j29ZW/IomUbGPlbpXp1z+1TETzmyw==
"@tiptap/extension-table-cell@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.0-beta.220.tgz#b3ae21a72a2012cb5e2656d91c90b7424672e6f8"
integrity sha512-JvX9CTaDBBbI1Qra7pwhsv0vD6Y3A+X6PL7EYVrqIHZlmWq7Lz2ELxjx8RkWyp2LzowVNgZwUu2i3yHakaX5oA==
"@tiptap/extension-table-cell@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-cell/-/extension-table-cell-2.0.3.tgz#44369bafdad3bb5b4f96296a8e93701673079b3a"
integrity sha512-d0vpwQfRIOhqKJdoiOJybwWhjnug3QA4Mkgccp378moDRyOer3hPKavG1Ljgz087qHrN4WfdUlMGEvasYsWE7w==
"@tiptap/extension-table-header@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.0-beta.220.tgz#3f6050847bc978dbcb117a9fa7dd16e13dd3b633"
integrity sha512-oOCBxrOuHCy4feuZKcBU9WWxi2SqBwfn/rmzSU6loKK8rR1+0olyAYu8IREb6DMmemTxl0ITp74hBxKeZyzjrA==
"@tiptap/extension-table-header@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-header/-/extension-table-header-2.0.3.tgz#bb35953da353f757202efab6e0c0d5d390a70f51"
integrity sha512-SnGl1U6usRRS6LyAjSdhaCYLF6NWbGhjVFSmiPrjb0pOzsiVeDOiUNCyUAIYaDNnjAF2pfK6+H+uHzYPqTi+/w==
"@tiptap/extension-table-row@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.220.tgz#5339c2be44cbbf871768a809a1f3694bef2031e0"
integrity sha512-DbYfrzLREulL+xOx74XAuhuqHUNi0t9hXDzG6RYdPiNnMhX/HhmTIV7bLNjEGxy6rOX0LhDzrBpNlA1elYUrwQ==
"@tiptap/extension-table-row@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.3.tgz#fb7fd381435b06942dfbc2ba475072c25bf0a478"
integrity sha512-tyqeXmQLNSBsYyiNsnQuJMxNbz6dYt+P5W58+h10mjbt+hERA5+alQQyP06O2DggsT3Z0LPt7QRAlNmOBe7cyQ==
"@tiptap/extension-table@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.220.tgz#42731e23e98d2c074e4e0460525718e57ebfc6a9"
integrity sha512-wdA957lSwIPtaSEAGw/KDXvhKAv28XkooHctY8FxqxEtvyMyCA8v0YXuOhGny/Uz6VZE+vdRiESMjwRU4ZQQ4g==
"@tiptap/extension-table@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.3.tgz#780b946ca8526ac8a4044bdec4c9c720ad8ba89a"
integrity sha512-8swHqm8vRM1w9WzaAhLmY24gGoTozctz4KHKBjvFY/Ka0yXabT0+hoCCdkZLnXWi15H3pbHs2HnDBaTGL9bZTw==
"@tiptap/extension-task-item@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.220.tgz#4c83d7b55587da9c8ed51cca6cd0edee13ff188d"
integrity sha512-dta4V3GkL3C+gYUUkv26gxvCD11JYE7XYp4GSED/1X/3aHOdV9HcYRtIVnHqb4YwfuX/AJyIDfjhxc2tNGevkQ==
"@tiptap/extension-task-item@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.3.tgz#b3845b51af565dec4c2c30c73ddbbc2b9e0bd297"
integrity sha512-13u1Q769WiSNcjFieYAMuJyWXNaY9yOdw6WFg9tQg4EZ5h6+2DaxB0qmu6I3pH+wwSn2UkCkXIirAo/k7wnzbw==
"@tiptap/extension-task-list@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.220.tgz#ba349d84dfb9fd5dff90bfea8fb234cd90383d78"
integrity sha512-Hix7/Er4T4xKz4uLTxniJaDtcctmooaxoHiHv4yDUOXZYiK5BZypr8cbCcUaoD3qpfGe8O5JBzY2sbwk0PkNwA==
"@tiptap/extension-task-list@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.3.tgz#7e32dd518d7bd5359faab43fb48b37e2d83f5937"
integrity sha512-NdW0RtMF2L96qy+j946mTB5Av6Qn5L3vGVWFmJA6/JPXr9Uj/grItCmqUQKHfPBSFow7UqBY82ODblP+GQFgew==
"@tiptap/extension-text@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.220.tgz#3f51d4aac11c16d79cf8ca22502898b67f5bc2f5"
integrity sha512-3tnffc2YMjNyv7Lbad6fx9wYDE/Buz8vhx76M2AOSrjYbzmTJf7mLkgdlPM0VTy7FGZD5CGgHJAgYNt5HIqPkQ==
"@tiptap/extension-text@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.3.tgz#12b6400a31ac6d35cbaf1822600f4c425457902f"
integrity sha512-LvzChcTCcPSMNLUjZe/A9SHXWGDHtvk73fR7CBqAeNU0MxhBPEBI03GFQ6RzW3xX0CmDmjpZoDxFMB+hDEtW1A==
"@tiptap/pm@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.0-beta.220.tgz#04e4c98e4d042ea8d67148ec6676f7078c6bac5a"
integrity sha512-O9mGcmwUpEr630HY9RylIyZJKnpXi3xWINWNiAEfRJ1br5j5pHRoVRJQ1HzU+6+Z+i/8qp3zRHGLTBqihaZETA==
"@tiptap/pm@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.3.tgz#e8bb47df765fc1b7acd52f2800c52d7ff945c5ec"
integrity sha512-I9dsInD89Agdm1QjFRO9dmJtU1ldVSILNPW0pEhv9wYqYVvl4HUj/JMtYNqu2jWrCHNXQcaX/WkdSdvGJtmg5g==
dependencies:
prosemirror-changeset "^2.2.0"
prosemirror-collab "^1.3.0"
@ -2023,18 +2031,18 @@
prosemirror-transform "^1.7.0"
prosemirror-view "^1.28.2"
"@tiptap/suggestion@^2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.220.tgz#2dc05f65e89006ffaad9f2b6a3468311a305e5ee"
integrity sha512-lYb2HOAKJLjEBbTx5VXA32wRryQiMwaKkNfr3v6UhlwoNgD6NkCYID08UJbpMV7iM+iFQp9408D/vVWFwvOuKg==
"@tiptap/suggestion@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.3.tgz#3f25e20f50de6748f2b65a88e264d9b5887ca16a"
integrity sha512-1y3palQStGZq13UtHjouZ50k4sotM+N56cIlFeygIv3gqdai2zGPaPQtqV9FOVVQizXpUbQMTlPSDC5Ej4SPnQ==
"@tiptap/vue-2@2.0.0-beta.220":
version "2.0.0-beta.220"
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.220.tgz#a3d9d84fc3cb6f1130bcd7e23fab9a4c56034312"
integrity sha512-GGK2M/pBVZSh2E0y1JXWVW7vllKvc2b/AwqPFZmbOGLKmxbH/xeaIeqOvt2w8b8RiA3G7UOq2lUGyjhn0/PnoQ==
"@tiptap/vue-2@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.3.tgz#076778985d1e5ccbefb414b05c4c9804bb377258"
integrity sha512-So2cl/W11Xt1MQqK47uNrddf08ruI2ScGHaBG2WZnYDtqJfwlAChRXi67fOeo/Y1vWy/69ekv5kLeQYWw9YJAg==
dependencies:
"@tiptap/extension-bubble-menu" "^2.0.0-beta.220"
"@tiptap/extension-floating-menu" "^2.0.0-beta.220"
"@tiptap/extension-bubble-menu" "^2.0.3"
"@tiptap/extension-floating-menu" "^2.0.3"
"@tootallnate/once@2":
version "2.0.0"
@ -3662,9 +3670,9 @@ camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001370:
version "1.0.30001388"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001388.tgz#88e01f4591cbd81f9f665f3f078c66b509fbe55d"
integrity sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==
version "1.0.30001478"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz#0ef8a1cf8b16be47a0f9fc4ecfc952232724b32a"
integrity sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==
canvas-confetti@^1.4.0:
version "1.4.0"