summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-14 21:13:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-14 21:13:24 +0000
commit6df3cf6b4acbfe83f7a19e40bbd1471af5d6ce95 (patch)
treead732f3108fb02f1e8f95f13cc87edaedbb728b1
parent5b62f8e3ee531f63ce3c49cae03e2a618ba51615 (diff)
downloadgitlab-ce-6df3cf6b4acbfe83f7a19e40bbd1471af5d6ce95.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/AI Project Proposal.md110
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_overview.vue39
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_pods.vue111
-rw-r--r--app/assets/javascripts/environments/components/new_environment_item.vue4
-rw-r--r--app/assets/javascripts/environments/graphql/client.js9
-rw-r--r--app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql7
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js14
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql14
-rw-r--r--app/assets/javascripts/environments/index.js3
-rw-r--r--app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue27
-rw-r--r--app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js5
-rw-r--r--app/assets/javascripts/pages/admin/jobs/components/constants.js9
-rw-r--r--app/assets/javascripts/pages/admin/jobs/components/table/admin_jobs_table_app.vue73
-rw-r--r--app/assets/javascripts/pages/admin/jobs/components/table/graphql/cache_config.js62
-rw-r--r--app/assets/javascripts/pages/admin/jobs/components/table/graphql/queries/get_all_jobs.query.graphql81
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js11
-rw-r--r--app/controllers/projects/ml/candidates_controller.rb3
-rw-r--r--app/helpers/projects/ml/experiments_helper.rb1
-rw-r--r--app/models/issue.rb30
-rw-r--r--app/services/issues/base_service.rb5
-rw-r--r--app/services/issues/build_service.rb20
-rw-r--r--app/services/issues/create_service.rb33
-rw-r--r--app/services/issues/update_service.rb8
-rw-r--r--app/services/work_items/create_service.rb10
-rw-r--r--app/services/work_items/update_service.rb10
-rw-r--r--app/views/projects/environments/index.html.haml3
-rw-r--r--app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml2
-rw-r--r--config/feature_flags/development/summarize_diff_quick_action.yml8
-rw-r--r--doc/api/dependencies.md2
-rw-r--r--jest.config.base.js1
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb3
-rw-r--r--lib/gitlab/usage_data_counters/known_events/quickactions.yml2
-rw-r--r--locale/gitlab.pot36
-rw-r--r--package.json71
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock6
-rw-r--r--spec/factories/issues.rb10
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb20
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb21
-rw-r--r--spec/features/merge_request/user_reverts_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb8
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb6
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb4
-rw-r--r--spec/frontend/environments/graphql/mock_data.js10
-rw-r--r--spec/frontend/environments/graphql/resolvers_spec.js57
-rw-r--r--spec/frontend/environments/kubernetes_overview_spec.js56
-rw-r--r--spec/frontend/environments/kubernetes_pods_spec.js114
-rw-r--r--spec/frontend/environments/mock_data.js3
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js10
-rw-r--r--spec/frontend/fixtures/jobs.rb64
-rw-r--r--spec/frontend/jobs/mock_data.js12
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap113
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js25
-rw-r--r--spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js61
-rw-r--r--spec/frontend/pages/admin/jobs/components/table/graphql/cache_config_spec.js106
-rw-r--r--spec/graphql/types/issue_type_spec.rb5
-rw-r--r--spec/helpers/integrations_helper_spec.rb3
-rw-r--r--spec/helpers/projects/ml/experiments_helper_spec.rb3
-rw-r--r--spec/models/issue_spec.rb26
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb7
-rw-r--r--spec/requests/projects/ml/candidates_controller_spec.rb3
-rw-r--r--spec/serializers/issue_sidebar_basic_entity_spec.rb5
-rw-r--r--spec/services/boards/issues/list_service_spec.rb10
-rw-r--r--spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml31
-rw-r--r--spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml29
-rw-r--r--spec/services/issues/build_service_spec.rb32
-rw-r--r--spec/services/issues/create_service_spec.rb91
-rw-r--r--spec/services/issues/update_service_spec.rb26
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb4
-rw-r--r--spec/services/work_items/create_service_spec.rb31
-rw-r--r--spec/services/work_items/update_service_spec.rb27
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb6
-rw-r--r--spec/workers/incident_management/close_incident_worker_spec.rb2
-rw-r--r--yarn.lock362
76 files changed, 1645 insertions, 601 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index 3c9742f59e6..8586d0af0fc 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -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
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index ab6737b8278..4e794d972dc 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.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
diff --git a/.gitlab/issue_templates/AI Project Proposal.md b/.gitlab/issue_templates/AI Project Proposal.md
index 48c5c0ac7ba..1b3202b4028 100644
--- a/.gitlab/issue_templates/AI Project Proposal.md
+++ b/.gitlab/issue_templates/AI Project Proposal.md
@@ -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 \ No newline at end of file
+[Make change to this template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/AI%20Project%20Proposal.md)
diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index cfb18cc4f82..736eaa7062d 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/kubernetes_pods.vue b/app/assets/javascripts/environments/components/kubernetes_pods.vue
new file mode 100644
index 00000000000..e43bc838708
--- /dev/null
+++ b/app/assets/javascripts/environments/components/kubernetes_pods.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
index f46bd0e3780..b5ef7d00cc3 100644
--- a/app/assets/javascripts/environments/components/new_environment_item.vue
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -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">
diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js
index 26514b59995..0482741979b 100644
--- a/app/assets/javascripts/environments/graphql/client.js
+++ b/app/assets/javascripts/environments/graphql/client.js
@@ -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,
});
diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
new file mode 100644
index 00000000000..818bca24d51
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
@@ -0,0 +1,7 @@
+query getK8sPods($configuration: Object, $namespace: String) {
+ k8sPods(configuration: $configuration, namespace: $namespace) @client {
+ status {
+ phase
+ }
+ }
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index e21670870b8..39e05825cf0 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -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 }) {
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index b4d1f7326f6..7c102fd04d8 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -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 {
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index d9a523fd806..3f746bc5383 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -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) {
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
index 3c765de92a2..23b58543f11 100644
--- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
@@ -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>
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js
index caad145873e..5f7714aa0c0 100644
--- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/translations.js
@@ -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?');
diff --git a/app/assets/javascripts/pages/admin/jobs/components/constants.js b/app/assets/javascripts/pages/admin/jobs/components/constants.js
index cfde1fc0a2b..84be895e194 100644
--- a/app/assets/javascripts/pages/admin/jobs/components/constants.js
+++ b/app/assets/javascripts/pages/admin/jobs/components/constants.js
@@ -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),
+];
diff --git a/app/assets/javascripts/pages/admin/jobs/components/table/admin_jobs_table_app.vue b/app/assets/javascripts/pages/admin/jobs/components/table/admin_jobs_table_app.vue
index c5a0509b625..6d5b4f24119 100644
--- a/app/assets/javascripts/pages/admin/jobs/components/table/admin_jobs_table_app.vue
+++ b/app/assets/javascripts/pages/admin/jobs/components/table/admin_jobs_table_app.vue
@@ -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>
diff --git a/app/assets/javascripts/pages/admin/jobs/components/table/graphql/cache_config.js b/app/assets/javascripts/pages/admin/jobs/components/table/graphql/cache_config.js
new file mode 100644
index 00000000000..fd7ee2a6f8c
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/components/table/graphql/cache_config.js
@@ -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,
+ };
+ },
+ },
+ },
+};
diff --git a/app/assets/javascripts/pages/admin/jobs/components/table/graphql/queries/get_all_jobs.query.graphql b/app/assets/javascripts/pages/admin/jobs/components/table/graphql/queries/get_all_jobs.query.graphql
new file mode 100644
index 00000000000..374009efa15
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/components/table/graphql/queries/get_all_jobs.query.graphql
@@ -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
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
index 2297efd805e..9c2a255a1a3 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/index.js
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -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,
diff --git a/app/controllers/projects/ml/candidates_controller.rb b/app/controllers/projects/ml/candidates_controller.rb
index ed465860004..e534000f494 100644
--- a/app/controllers/projects/ml/candidates_controller.rb
+++ b/app/controllers/projects/ml/candidates_controller.rb
@@ -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
diff --git a/app/helpers/projects/ml/experiments_helper.rb b/app/helpers/projects/ml/experiments_helper.rb
index 927337da6bb..1f044ebed3b 100644
--- a/app/helpers/projects/ml/experiments_helper.rb
+++ b/app/helpers/projects/ml/experiments_helper.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d7b72fa9dad..77dcaf9336c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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?
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 42807b4d7cf..05090efe260 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -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
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 1b87c42be69..cb90aca5800 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -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
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e652fec02e5..06977dec04b 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -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
+
+ 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
- super
+ # 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
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index b64826d5357..877f0a73c82 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -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?
diff --git a/app/services/work_items/create_service.rb b/app/services/work_items/create_service.rb
index eff2132039f..ae355dc6d96 100644
--- a/app/services/work_items/create_service.rb
+++ b/app/services/work_items/create_service.rb
@@ -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
diff --git a/app/services/work_items/update_service.rb b/app/services/work_items/update_service.rb
index d4acadbc851..defdeebfed8 100644
--- a/app/services/work_items/update_service.rb
+++ b/app/services/work_items/update_service.rb
@@ -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,
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index e4b8750b96c..7ddaf868a35 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -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 } }
diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
index 92b0a5a0b90..b8ee62055f0 100644
--- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
+++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
@@ -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')
diff --git a/config/feature_flags/development/summarize_diff_quick_action.yml b/config/feature_flags/development/summarize_diff_quick_action.yml
new file mode 100644
index 00000000000..ebbfaba2dba
--- /dev/null
+++ b/config/feature_flags/development/summarize_diff_quick_action.yml
@@ -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
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
index 47db8645be1..5ae36926868 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -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"
diff --git a/jest.config.base.js b/jest.config.base.js
index e8ba15f0a09..3cbf2fdd61b 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -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',
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index ae8bc102f57..10e8c702826 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -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') }
diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml
index 7006173cc59..ee5fa29c0c3 100644
--- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml
+++ b/lib/gitlab/usage_data_counters/known_events/quickactions.yml
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b5802faba75..1e10165d02e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/package.json b/package.json
index 5fd478da2c9..6418a12910e 100644
--- a/package.json
+++ b/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",
diff --git a/qa/Gemfile b/qa/Gemfile
index ef66b1327eb..16d9cf9e74a 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -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'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 1cd898903b8..10e3de2b999 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -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
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 54a3fc57a5d..67824a10288 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -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
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 8ff0c294b24..e3989a8a192 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -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
diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index cdc00017ab3..2a71dd5eac7 100644
--- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -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
diff --git a/spec/features/merge_request/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb
index e09a4569caf..da48a31abbd 100644
--- a/spec/features/merge_request/user_reverts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb
@@ -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
diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index b83580565e4..476be5ab599 100644
--- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -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
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index acf2893b513..eb293fbbd20 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -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
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index ae3158e4270..1c754943acb 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -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
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index b5435990042..8d91ffe5ffc 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -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];
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index 2c223d3a1a7..c66844f5f24 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -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);
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
index 8673c657760..1912fd4a82b 100644
--- a/spec/frontend/environments/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -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);
+ });
});
});
diff --git a/spec/frontend/environments/kubernetes_pods_spec.js b/spec/frontend/environments/kubernetes_pods_spec.js
new file mode 100644
index 00000000000..137309d7853
--- /dev/null
+++ b/spec/frontend/environments/kubernetes_pods_spec.js
@@ -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]]);
+ });
+ });
+});
diff --git a/spec/frontend/environments/mock_data.js b/spec/frontend/environments/mock_data.js
index a6d67c26304..bd2c6b7c892 100644
--- a/spec/frontend/environments/mock_data.js
+++ b/spec/frontend/environments/mock_data.js
@@ -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,
};
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index 89a9ca725ba..b4f5263a151 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -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,
});
});
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 3583beb83c2..a825e1af268 100644
--- a/spec/frontend/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -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}")
- end
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{path}/#{jobs_query}")
+ end
- it "#{fixtures_path}#{get_jobs_query}.json" do
- post_graphql(query, current_user: user, variables: {
- fullPath: full_path
- })
+ fixtures_path = 'graphql/jobs/'
- expect_graphql_errors_to_be_empty
- end
+ it "#{fixtures_path}#{jobs_query}.json" do
+ post_graphql(query, current_user: user, variables: variables)
- it "#{fixtures_path}#{get_jobs_query}.as_guest.json" do
- guest = create(:user)
- project.add_guest(guest)
+ expect_graphql_errors_to_be_empty
+ end
- post_graphql(query, current_user: guest, variables: {
- fullPath: full_path
- })
+ it "#{fixtures_path}#{jobs_query}.as_guest.json" do
+ guest = create(:user)
+ project.add_guest(guest)
- expect_graphql_errors_to_be_empty
- end
+ post_graphql(query, current_user: guest, variables: variables)
- 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
- 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 }))
- 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
- expect_graphql_errors_to_be_empty
+ 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_behaves_like 'graphql queries', 'jobs/components/table/graphql/queries', 'get_jobs.query.graphql' do
+ let(:variables) { { fullPath: 'frontend-fixtures/builds-project' } }
+ 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
diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js
index 483b4ca711f..fb1ded7b4ef 100644
--- a/spec/frontend/jobs/mock_data.js
+++ b/spec/frontend/jobs/mock_data.js
@@ -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: {
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap b/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap
index dc21db39259..6fd3dce1941 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap
@@ -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>
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
index 36455339041..7d03ab3b509 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
@@ -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);
- it('shows incubation warning', () => {
- wrapper = createWrapper();
+ const findAlert = () => wrapper.findComponent(IncubationAlert);
+ const findDeleteButton = () => wrapper.findComponent(DeleteButton);
+ it('shows incubation warning', () => {
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();
});
});
diff --git a/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js b/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js
new file mode 100644
index 00000000000..e7ac2576b6f
--- /dev/null
+++ b/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js
@@ -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);
+ });
+ });
+});
diff --git a/spec/frontend/pages/admin/jobs/components/table/graphql/cache_config_spec.js b/spec/frontend/pages/admin/jobs/components/table/graphql/cache_config_spec.js
new file mode 100644
index 00000000000..59e9eda6343
--- /dev/null
+++ b/spec/frontend/pages/admin/jobs/components/table/graphql/cache_config_spec.js
@@ -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);
+ });
+ });
+});
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index 7c6cf137a1e..87f99878a4d 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -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 }
diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb
index 9822f9fac05..8be847e1c6c 100644
--- a/spec/helpers/integrations_helper_spec.rb
+++ b/spec/helpers/integrations_helper_spec.rb
@@ -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
diff --git a/spec/helpers/projects/ml/experiments_helper_spec.rb b/spec/helpers/projects/ml/experiments_helper_spec.rb
index 004c89e5ca7..9b3c23e1f87 100644
--- a/spec/helpers/projects/ml/experiments_helper_spec.rb
+++ b/spec/helpers/projects/ml/experiments_helper_spec.rb
@@ -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)
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index dec62815366..d238133b59e 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index 4cc6bbe01b6..9e5e365ab9d 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -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
diff --git a/spec/requests/projects/ml/candidates_controller_spec.rb b/spec/requests/projects/ml/candidates_controller_spec.rb
index 3b36ec9fdad..78c8e99e3f3 100644
--- a/spec/requests/projects/ml/candidates_controller_spec.rb
+++ b/spec/requests/projects/ml/candidates_controller_spec.rb
@@ -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
diff --git a/spec/serializers/issue_sidebar_basic_entity_spec.rb b/spec/serializers/issue_sidebar_basic_entity_spec.rb
index 64a271e359a..f24e379ec67 100644
--- a/spec/serializers/issue_sidebar_basic_entity_spec.rb
+++ b/spec/serializers/issue_sidebar_basic_entity_spec.rb
@@ -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
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 5e10d1d216c..4b31a041342 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -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
diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml b/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml
new file mode 100644
index 00000000000..12c51828628
--- /dev/null
+++ b/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml
@@ -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
diff --git a/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml b/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml
new file mode 100644
index 00000000000..57b3aa9ae80
--- /dev/null
+++ b/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml
@@ -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
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index fecfc3f3d64..bca6a3cd4f9 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -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
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index df47780bc89..88dcc870ee7 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -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
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index a5a18562ca5..167bff2a492 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -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
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index b474285e67e..78a17aed707 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -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
diff --git a/spec/services/work_items/create_service_spec.rb b/spec/services/work_items/create_service_spec.rb
index 0e9e49bc877..46e598c3f11 100644
--- a/spec/services/work_items/create_service_spec.rb
+++ b/spec/services/work_items/create_service_spec.rb
@@ -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)
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index 5647f8c085c..2cf52ee853a 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -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' } }
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index 78863fe1169..c68d53db01e 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -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: [] } }
diff --git a/spec/workers/incident_management/close_incident_worker_spec.rb b/spec/workers/incident_management/close_incident_worker_spec.rb
index bf967a42ceb..3c2e69a4675 100644
--- a/spec/workers/incident_management/close_incident_worker_spec.rb
+++ b/spec/workers/incident_management/close_incident_worker_spec.rb
@@ -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'
diff --git a/yarn.lock b/yarn.lock
index d1bc955c14e..1143743e7f6 100644
--- a/yarn.lock
+++ b/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==
+"@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:
- 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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@^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-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-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-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/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/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.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.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.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.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.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.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.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.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.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.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.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.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.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"