Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5b62f8e3ee
commit
6df3cf6b4a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
query getK8sPods($configuration: Object, $namespace: String) {
|
||||
k8sPods(configuration: $configuration, namespace: $namespace) @client {
|
||||
status {
|
||||
phase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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?');
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
71
package.json
71
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [] } }
|
||||
|
|
|
|||
|
|
@ -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
326
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue