Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-06 03:08:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-06 03:08:00 +0300
commit1e2aa980a7214f025d22e1d8936147391b670a89 (patch)
tree80c087d22788031d8fe0d2021306ba4c0636960d
parent339b91536372cb04b4528725aae408145628ca69 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/ci/pipeline_details/header/constants.js9
-rw-r--r--app/assets/javascripts/ci/pipeline_details/header/graphql/fragments/pipeline_header.fragment.graphql4
-rw-r--r--app/assets/javascripts/ci/pipeline_details/header/graphql/queries/get_pipeline_header_data.query.graphql17
-rw-r--r--app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue103
-rw-r--r--app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js44
-rw-r--r--app/assets/javascripts/deprecated_notes.js2
-rw-r--r--app/assets/javascripts/environments/helpers/k8s_integration_helper.js23
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/constants.js6
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/client.js17
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js26
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql14
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js47
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js12
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/deployments_page.vue84
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/constants.js2
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/routes.js16
-rw-r--r--app/assets/javascripts/members/components/table/max_role.vue6
-rw-r--r--app/assets/javascripts/repository/components/commit_info.vue45
-rw-r--r--app/helpers/projects/pipeline_helper.rb17
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/views/groups/_home_panel.html.haml2
-rw-r--r--app/views/groups/settings/_general.html.haml2
-rw-r--r--app/views/shared/notes/_edit_form.html.haml1
-rw-r--r--doc/administration/dedicated/index.md5
-rw-r--r--doc/user/application_security/container_scanning/index.md7
-rw-r--r--doc/user/group/import/index.md3
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/project/index.md6
-rw-r--r--doc/user/project/working_with_projects.md2
-rw-r--r--locale/gitlab.pot9
-rw-r--r--package.json2
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb2
-rw-r--r--spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js56
-rw-r--r--spec/frontend/environments/helpers/k8s_integration_helper_spec.js225
-rw-r--r--spec/frontend/fixtures/pipeline_header.rb7
-rw-r--r--spec/frontend/kubernetes_dashboard/graphql/mock_data.js107
-rw-r--r--spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js93
-rw-r--r--spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js (renamed from spec/frontend/kubernetes_dashboard/helpers/k8s_integraion_helper_spec.js)35
-rw-r--r--spec/frontend/kubernetes_dashboard/pages/deployments_page_spec.js106
-rw-r--r--spec/helpers/projects/pipeline_helper_spec.rb16
-rw-r--r--spec/models/namespace_spec.rb2
-rw-r--r--yarn.lock8
43 files changed, 974 insertions, 222 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 9807f6e9cbf..15058411922 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-61813ee439cc7a3e0858b4955f4511315eaab650
+9171724863b73d3e98c1b2021a6095a58ac128e1
diff --git a/app/assets/javascripts/ci/pipeline_details/header/constants.js b/app/assets/javascripts/ci/pipeline_details/header/constants.js
new file mode 100644
index 00000000000..a4aed7b8f46
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_details/header/constants.js
@@ -0,0 +1,9 @@
+export const DELETE_MODAL_ID = 'pipeline-delete-modal';
+
+export const POLL_INTERVAL = 10000;
+
+export const SCHEDULE_SOURCE = 'schedule';
+export const AUTO_DEVOPS_SOURCE = 'AUTO_DEVOPS_SOURCE';
+export const DETACHED_EVENT_TYPE = 'DETACHED';
+export const MERGED_RESULT_EVENT_TYPE = 'MERGED_RESULT';
+export const MERGE_TRAIN_EVENT_TYPE = 'MERGE_TRAIN';
diff --git a/app/assets/javascripts/ci/pipeline_details/header/graphql/fragments/pipeline_header.fragment.graphql b/app/assets/javascripts/ci/pipeline_details/header/graphql/fragments/pipeline_header.fragment.graphql
new file mode 100644
index 00000000000..80fc8b92a47
--- /dev/null
+++ b/app/assets/javascripts/ci/pipeline_details/header/graphql/fragments/pipeline_header.fragment.graphql
@@ -0,0 +1,4 @@
+fragment PipelineHeaderData on Pipeline {
+ id
+ iid
+}
diff --git a/app/assets/javascripts/ci/pipeline_details/header/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/ci/pipeline_details/header/graphql/queries/get_pipeline_header_data.query.graphql
index 00c4c1ae3b4..4ef79aaa03c 100644
--- a/app/assets/javascripts/ci/pipeline_details/header/graphql/queries/get_pipeline_header_data.query.graphql
+++ b/app/assets/javascripts/ci/pipeline_details/header/graphql/queries/get_pipeline_header_data.query.graphql
@@ -1,9 +1,9 @@
+#import "ee_else_ce/ci/pipeline_details/header/graphql/fragments/pipeline_header.fragment.graphql"
+
query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {
project(fullPath: $fullPath) {
id
pipeline(iid: $iid) {
- id
- iid
status
retryable
cancelable
@@ -42,6 +42,19 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {
finishedAt
queuedDuration
duration
+ name
+ totalJobs
+ refText
+ triggeredByPath
+ stuck
+ child
+ complete
+ latest
+ mergeRequestEventType
+ configSource
+ failureReason
+ source
+ ...PipelineHeaderData
}
}
}
diff --git a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue
index 4572e25face..20936fe6e54 100644
--- a/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue
+++ b/app/assets/javascripts/ci/pipeline_details/header/pipeline_details_header.vue
@@ -26,9 +26,15 @@ import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutatio
import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
import { getQueryHeaders } from '../graph/utils';
import getPipelineQuery from './graphql/queries/get_pipeline_header_data.query.graphql';
-
-const DELETE_MODAL_ID = 'pipeline-delete-modal';
-const POLL_INTERVAL = 10000;
+import {
+ DELETE_MODAL_ID,
+ POLL_INTERVAL,
+ DETACHED_EVENT_TYPE,
+ AUTO_DEVOPS_SOURCE,
+ SCHEDULE_SOURCE,
+ MERGE_TRAIN_EVENT_TYPE,
+ MERGED_RESULT_EVENT_TYPE,
+} from './constants';
export default {
name: 'PipelineDetailsHeader',
@@ -129,40 +135,14 @@ export default {
},
},
props: {
- name: {
- type: String,
- required: false,
- default: '',
- },
- totalJobs: {
- type: String,
- required: false,
- default: '',
- },
- computeMinutes: {
- type: Number,
- required: false,
- default: 0,
- },
yamlErrors: {
type: String,
required: false,
default: '',
},
- failureReason: {
- type: String,
- required: false,
- default: '',
- },
- refText: {
- type: String,
- required: false,
- default: '',
- },
- badges: {
- type: Object,
- required: false,
- default: () => {},
+ trigger: {
+ type: Boolean,
+ required: true,
},
},
apollo: {
@@ -270,7 +250,7 @@ export default {
},
totalJobsText() {
return sprintf(__('%{jobs} Jobs'), {
- jobs: this.totalJobs,
+ jobs: this.pipeline?.totalJobs || 0,
});
},
triggeredText() {
@@ -314,9 +294,60 @@ export default {
return cancelable && userPermissions.cancelPipeline;
},
+ computeMinutes() {
+ return this.pipeline?.computeMinutes;
+ },
showComputeMinutes() {
return this.isFinished && this.computeMinutes;
},
+ pipelineName() {
+ return this.pipeline?.name;
+ },
+ refText() {
+ return this.pipeline?.refText;
+ },
+ triggeredByPath() {
+ return this.pipeline?.triggeredByPath;
+ },
+ mergeRequestEventType() {
+ return this.pipeline.mergeRequestEventType;
+ },
+ isMergeTrainPipeline() {
+ return this.mergeRequestEventType === MERGE_TRAIN_EVENT_TYPE;
+ },
+ isMergedResultsPipeline() {
+ return this.mergeRequestEventType === MERGED_RESULT_EVENT_TYPE;
+ },
+ isDetachedPipeline() {
+ return this.mergeRequestEventType === DETACHED_EVENT_TYPE;
+ },
+ isAutoDevopsPipeline() {
+ return this.pipeline.configSource === AUTO_DEVOPS_SOURCE;
+ },
+ isScheduledPipeline() {
+ return this.pipeline.source === SCHEDULE_SOURCE;
+ },
+ isInvalidPipeline() {
+ return Boolean(this.yamlErrors);
+ },
+ failureReason() {
+ return this.pipeline.failureReason;
+ },
+ badges() {
+ return {
+ schedule: this.isScheduledPipeline,
+ trigger: this.trigger,
+ invalid: this.isInvalidPipeline,
+ child: this.pipeline.child,
+ latest: this.pipeline.latest,
+ mergeTrainPipeline: this.isMergeTrainPipeline,
+ mergedResultsPipeline: this.isMergedResultsPipeline,
+ detached: this.isDetachedPipeline,
+ failed: Boolean(this.failureReason),
+ autoDevops: this.isAutoDevopsPipeline,
+ stuck: this.pipeline.stuck,
+ };
+ },
},
methods: {
reportFailure(errorType, errorMessages = []) {
@@ -406,7 +437,9 @@ export default {
<gl-loading-icon v-if="loading" class="gl-text-left" size="lg" />
<div v-else class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
<div>
- <h3 v-if="name" class="gl-mt-0 gl-mb-3" data-testid="pipeline-name">{{ name }}</h3>
+ <h3 v-if="pipelineName" class="gl-mt-0 gl-mb-3" data-testid="pipeline-name">
+ {{ pipelineName }}
+ </h3>
<h3 v-else class="gl-mt-0 gl-mb-3" data-testid="pipeline-commit-title">
{{ commitTitle }}
</h3>
@@ -483,7 +516,7 @@ export default {
>
<gl-sprintf :message="$options.i18n.childBadgeText">
<template #link="{ content }">
- <gl-link :href="paths.triggeredByPath" target="_blank">
+ <gl-link :href="triggeredByPath" target="_blank">
{{ content }}
</gl-link>
</template>
diff --git a/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js b/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js
index e21ccd835e0..0430bc83dd7 100644
--- a/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js
+++ b/app/assets/javascripts/ci/pipeline_details/pipeline_details_header.js
@@ -12,29 +12,7 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph
return;
}
- const {
- fullPath,
- pipelineIid,
- pipelinesPath,
- name,
- totalJobs,
- computeMinutes,
- yamlErrors,
- failureReason,
- triggeredByPath,
- schedule,
- trigger,
- child,
- latest,
- mergeTrainPipeline,
- mergedResultsPipeline,
- invalid,
- failed,
- autoDevops,
- detached,
- stuck,
- refText,
- } = el.dataset;
+ const { fullPath, pipelineIid, pipelinesPath, yamlErrors, trigger } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
@@ -46,32 +24,14 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph
fullProject: fullPath,
graphqlResourceEtag,
pipelinesPath,
- triggeredByPath,
},
pipelineIid,
},
render(createElement) {
return createElement(PipelineDetailsHeader, {
props: {
- name,
- totalJobs,
- computeMinutes: Number(computeMinutes),
yamlErrors,
- failureReason,
- refText,
- badges: {
- schedule: parseBoolean(schedule),
- trigger: parseBoolean(trigger),
- child: parseBoolean(child),
- latest: parseBoolean(latest),
- mergeTrainPipeline: parseBoolean(mergeTrainPipeline),
- mergedResultsPipeline: parseBoolean(mergedResultsPipeline),
- invalid: parseBoolean(invalid),
- failed: parseBoolean(failed),
- autoDevops: parseBoolean(autoDevops),
- detached: parseBoolean(detached),
- stuck: parseBoolean(stuck),
- },
+ trigger: parseBoolean(trigger),
},
});
},
diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js
index 9b5b4cef1b9..26500c37acf 100644
--- a/app/assets/javascripts/deprecated_notes.js
+++ b/app/assets/javascripts/deprecated_notes.js
@@ -844,7 +844,7 @@ export default class Notes {
const selector = this.getEditFormSelector($target);
const $editForm = $(selector);
- $editForm.insertBefore('.diffs');
+ $editForm.insertBefore('.js-snippets-note-edit-form-holder');
$editForm.find('.js-comment-save-button').enable();
$editForm.find('.js-finish-edit-warning').hide();
}
diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
index a67c8b83eb6..ed14aa3169e 100644
--- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
+++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
@@ -1,4 +1,6 @@
-import { CLUSTER_AGENT_ERROR_MESSAGES, STATUS_TRUE, STATUS_FALSE } from '../constants';
+import { calculateDeploymentStatus } from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
+import { PHASE_READY, PHASE_FAILED } from '~/kubernetes_dashboard/constants';
+import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants';
export function generateServicePortsString(ports) {
if (!ports?.length) return '';
@@ -17,13 +19,18 @@ export function getDeploymentsStatuses(items) {
const pending = [];
items.forEach((item) => {
- const [available, progressing] = item.status?.conditions ?? [];
- if (available?.status === STATUS_TRUE) {
- ready.push(item);
- } else if (available?.status === STATUS_FALSE && progressing?.status !== STATUS_TRUE) {
- failed.push(item);
- } else {
- pending.push(item);
+ const status = calculateDeploymentStatus(item);
+
+ switch (status) {
+ case PHASE_READY:
+ ready.push(item);
+ break;
+ case PHASE_FAILED:
+ failed.push(item);
+ break;
+ default:
+ pending.push(item);
+ break;
}
});
diff --git a/app/assets/javascripts/kubernetes_dashboard/constants.js b/app/assets/javascripts/kubernetes_dashboard/constants.js
index cbb89dff1cf..bbbc8a33675 100644
--- a/app/assets/javascripts/kubernetes_dashboard/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/constants.js
@@ -4,12 +4,14 @@ export const PHASE_RUNNING = 'Running';
export const PHASE_PENDING = 'Pending';
export const PHASE_SUCCEEDED = 'Succeeded';
export const PHASE_FAILED = 'Failed';
+export const PHASE_READY = 'Ready';
export const STATUS_LABELS = {
[PHASE_RUNNING]: s__('KubernetesDashboard|Running'),
[PHASE_PENDING]: s__('KubernetesDashboard|Pending'),
[PHASE_SUCCEEDED]: s__('KubernetesDashboard|Succeeded'),
[PHASE_FAILED]: s__('KubernetesDashboard|Failed'),
+ [PHASE_READY]: s__('KubernetesDashboard|Ready'),
};
export const WORKLOAD_STATUS_BADGE_VARIANTS = {
@@ -17,6 +19,7 @@ export const WORKLOAD_STATUS_BADGE_VARIANTS = {
[PHASE_PENDING]: 'warning',
[PHASE_SUCCEEDED]: 'success',
[PHASE_FAILED]: 'danger',
+ [PHASE_READY]: 'success',
};
export const PAGE_SIZE = 20;
@@ -41,3 +44,6 @@ export const DEFAULT_WORKLOAD_TABLE_FIELDS = [
label: s__('KubernetesDashboard|Age'),
},
];
+
+export const STATUS_TRUE = 'True';
+export const STATUS_FALSE = 'False';
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
index f6248627efa..a058fefd0e2 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
@@ -2,6 +2,7 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import typeDefs from '~/environments/graphql/typedefs.graphql';
import k8sPodsQuery from './queries/k8s_dashboard_pods.query.graphql';
+import k8sDeploymentsQuery from './queries/k8s_dashboard_deployments.query.graphql';
import { resolvers } from './resolvers';
export const apolloProvider = () => {
@@ -26,6 +27,22 @@ export const apolloProvider = () => {
},
});
+ cache.writeQuery({
+ query: k8sDeploymentsQuery,
+ data: {
+ metadata: {
+ name: null,
+ namespace: null,
+ creationTimestamp: null,
+ labels: null,
+ annotations: null,
+ },
+ status: {
+ conditions: null,
+ },
+ },
+ });
+
return new VueApollo({
defaultClient,
});
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
index a6af2fcb96f..f034036287a 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
@@ -13,7 +13,7 @@ export const buildWatchPath = ({ resource, api = 'api/v1', namespace = '' }) =>
return namespace ? `/${api}/namespaces/${namespace}/${resource}` : `/${api}/${resource}`;
};
-const mapWorkloadItem = (item) => {
+export const mapWorkloadItem = (item) => {
if (item.metadata) {
const metadata = {
...item.metadata,
@@ -25,13 +25,19 @@ const mapWorkloadItem = (item) => {
return { status: item.status };
};
-export const watchPods = ({ client, query, configuration, namespace }) => {
- const path = buildWatchPath({ resource: 'pods', namespace });
+export const watchWorkloadItems = ({
+ client,
+ query,
+ configuration,
+ namespace,
+ watchPath,
+ queryField,
+}) => {
const config = new Configuration(configuration);
const watcherApi = new WatchApi(config);
watcherApi
- .subscribeToStream(path, { watch: true })
+ .subscribeToStream(watchPath, { watch: true })
.then((watcher) => {
let result = [];
@@ -41,7 +47,7 @@ export const watchPods = ({ client, query, configuration, namespace }) => {
client.writeQuery({
query,
variables: { configuration, namespace },
- data: { k8sPods: result },
+ data: { [queryField]: result },
});
});
})
@@ -67,7 +73,15 @@ export const getK8sPods = ({
return podsApi
.then((res) => {
if (enableWatch) {
- watchPods({ client, query, configuration, namespace });
+ const watchPath = buildWatchPath({ resource: 'pods', namespace });
+ watchWorkloadItems({
+ client,
+ query,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sPods',
+ });
}
const data = res?.items || [];
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql
new file mode 100644
index 00000000000..21172bbbeb0
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql
@@ -0,0 +1,14 @@
+query getK8sDashboardDeployments($configuration: LocalConfiguration) {
+ k8sDeployments(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ status {
+ conditions
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
index 5f046464730..6a064d881e7 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
@@ -1,5 +1,14 @@
-import { getK8sPods } from '../helpers/resolver_helpers';
+import { Configuration, AppsV1Api } from '@gitlab/cluster-client';
+
+import {
+ getK8sPods,
+ handleClusterError,
+ mapWorkloadItem,
+ buildWatchPath,
+ watchWorkloadItems,
+} from '../helpers/resolver_helpers';
import k8sDashboardPodsQuery from '../queries/k8s_dashboard_pods.query.graphql';
+import k8sDashboardDeploymentsQuery from '../queries/k8s_dashboard_deployments.query.graphql';
export default {
k8sPods(_, { configuration }, { client }) {
@@ -7,4 +16,40 @@ export default {
const enableWatch = true;
return getK8sPods({ client, query, configuration, enableWatch });
},
+
+ k8sDeployments(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const appsV1api = new AppsV1Api(config);
+ const deploymentsApi = namespace
+ ? appsV1api.listAppsV1NamespacedDeployment({ namespace })
+ : appsV1api.listAppsV1DeploymentForAllNamespaces();
+ return deploymentsApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'deployments',
+ api: 'apis/apps/v1',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sDashboardDeploymentsQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sDeployments',
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapWorkloadItem);
+ })
+ .catch(async (err) => {
+ try {
+ await handleClusterError(err);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ });
+ },
};
diff --git a/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js b/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
index 0c6c0907000..d55a57f0ab7 100644
--- a/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
+++ b/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
@@ -1,4 +1,5 @@
import { differenceInSeconds } from '~/lib/utils/datetime_utility';
+import { STATUS_TRUE, STATUS_FALSE, PHASE_PENDING, PHASE_READY, PHASE_FAILED } from '../constants';
export function getAge(creationTimestamp) {
if (!creationTimestamp) return '';
@@ -23,3 +24,14 @@ export function getAge(creationTimestamp) {
return ageString;
}
+
+export function calculateDeploymentStatus(item) {
+ const [available, progressing] = item.status?.conditions ?? [];
+ if (available?.status === STATUS_TRUE) {
+ return PHASE_READY;
+ }
+ if (available?.status === STATUS_FALSE && progressing?.status !== STATUS_TRUE) {
+ return PHASE_FAILED;
+ }
+ return PHASE_PENDING;
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/deployments_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/deployments_page.vue
new file mode 100644
index 00000000000..2e08d9f0c03
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/deployments_page.vue
@@ -0,0 +1,84 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, calculateDeploymentStatus } from '../helpers/k8s_integration_helper';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sDeploymentsQuery from '../graphql/queries/k8s_dashboard_deployments.query.graphql';
+import { PHASE_FAILED, PHASE_READY, PHASE_PENDING, STATUS_LABELS } from '../constants';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sDeployments: {
+ query: k8sDeploymentsQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.k8sDeployments?.map((deployment) => {
+ return {
+ name: deployment.metadata?.name,
+ namespace: deployment.metadata?.namespace,
+ status: calculateDeploymentStatus(deployment),
+ age: getAge(deployment.metadata?.creationTimestamp),
+ labels: deployment.metadata?.labels,
+ annotations: deployment.metadata?.annotations,
+ kind: s__('KubernetesDashboard|Deployment'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sDeployments: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ deploymentsStats() {
+ return [
+ {
+ value: this.countDeploymentsByStatus(PHASE_READY),
+ title: STATUS_LABELS[PHASE_READY],
+ },
+ {
+ value: this.countDeploymentsByStatus(PHASE_FAILED),
+ title: STATUS_LABELS[PHASE_FAILED],
+ },
+ {
+ value: this.countDeploymentsByStatus(PHASE_PENDING),
+ title: STATUS_LABELS[PHASE_PENDING],
+ },
+ ];
+ },
+ loading() {
+ return this.$apollo.queries.k8sDeployments.loading;
+ },
+ },
+ methods: {
+ countDeploymentsByStatus(phase) {
+ const filteredDeployments = this.k8sDeployments.filter((item) => item.status === phase) || [];
+
+ return filteredDeployments.length;
+ },
+ },
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="deploymentsStats"
+ :items="k8sDeployments"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/constants.js b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
index 5862ea7c995..d2db2e616fd 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
@@ -1,3 +1,5 @@
export const PODS_ROUTE_NAME = 'pods';
+export const DEPLOYMENTS_ROUTE_NAME = 'deployments';
export const PODS_ROUTE_PATH = '/pods';
+export const DEPLOYMENTS_ROUTE_PATH = '/deployments';
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/routes.js b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
index 33ae36716ff..639f41df926 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/routes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
@@ -1,6 +1,12 @@
import { s__ } from '~/locale';
import PodsPage from '../pages/pods_page.vue';
-import { PODS_ROUTE_NAME, PODS_ROUTE_PATH } from './constants';
+import DeploymentsPage from '../pages/deployments_page.vue';
+import {
+ PODS_ROUTE_NAME,
+ PODS_ROUTE_PATH,
+ DEPLOYMENTS_ROUTE_NAME,
+ DEPLOYMENTS_ROUTE_PATH,
+} from './constants';
export default [
{
@@ -11,4 +17,12 @@ export default [
title: s__('KubernetesDashboard|Pods'),
},
},
+ {
+ name: DEPLOYMENTS_ROUTE_NAME,
+ path: DEPLOYMENTS_ROUTE_PATH,
+ component: DeploymentsPage,
+ meta: {
+ title: s__('KubernetesDashboard|Deployments'),
+ },
+ },
];
diff --git a/app/assets/javascripts/members/components/table/max_role.vue b/app/assets/javascripts/members/components/table/max_role.vue
index d0b04752ef2..504e68f4f09 100644
--- a/app/assets/javascripts/members/components/table/max_role.vue
+++ b/app/assets/javascripts/members/components/table/max_role.vue
@@ -6,6 +6,7 @@ import { mapActions } from 'vuex';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { guestOverageConfirmAction } from 'ee_else_ce/members/guest_overage_confirm_action';
import { roleDropdownItems, initialSelectedRole } from 'ee_else_ce/members/utils';
+import { ACCESS_LEVEL_LABELS } from '~/access_level/constants';
import { s__ } from '~/locale';
export default {
@@ -65,9 +66,8 @@ export default {
try {
const confirmed = await guestOverageConfirmAction({
- currentRoleValue: this.member.accessLevel.integerValue,
- newRoleValue: newRole.accessLevel,
- newRoleName: newRole.text,
+ oldAccessLevel: this.member.accessLevel.integerValue,
+ newRoleName: ACCESS_LEVEL_LABELS[newRole.accessLevel],
newMemberRoleId: newRole.memberRoleId,
group: this.group,
memberId: this.member.id,
diff --git a/app/assets/javascripts/repository/components/commit_info.vue b/app/assets/javascripts/repository/components/commit_info.vue
index 7bf6ab2ed27..319ce2cea84 100644
--- a/app/assets/javascripts/repository/components/commit_info.vue
+++ b/app/assets/javascripts/repository/components/commit_info.vue
@@ -77,25 +77,32 @@ export default {
:size="32"
/>
<div class="commit-detail flex-list gl-display-flex gl-flex-grow-1 gl-min-w-0">
- <div class="commit-content gl-w-full" data-testid="commit-content">
- <gl-link
- v-safe-html:[$options.safeHtmlConfig]="commit.titleHtml"
- :href="commit.webPath"
- :class="{ 'gl-font-style-italic': !commit.message }"
- class="commit-row-message item-title gl-line-clamp-1"
- />
- <gl-button
- v-if="commit.descriptionHtml"
- v-gl-tooltip
- :class="{ open: showDescription }"
- :title="$options.i18n.toggleCommitDescription"
- :aria-label="$options.i18n.toggleCommitDescription"
- :selected="showDescription"
- class="text-expander gl-vertical-align-bottom!"
- icon="ellipsis_h"
- @click="toggleShowDescription"
- />
- <div class="committer gl-pb-2">
+ <div
+ class="commit-content gl-w-full gl-display-inline-flex gl-flex-wrap gl-align-items-baseline"
+ data-testid="commit-content"
+ >
+ <div
+ class="gl-flex-basis-full gl-display-inline-flex gl-align-items-center gl-column-gap-3"
+ >
+ <gl-link
+ v-safe-html:[$options.safeHtmlConfig]="commit.titleHtml"
+ :href="commit.webPath"
+ :class="{ 'gl-font-style-italic': !commit.message }"
+ class="commit-row-message item-title gl-line-clamp-1 gl-word-break-all!"
+ />
+ <gl-button
+ v-if="commit.descriptionHtml"
+ v-gl-tooltip
+ :class="{ open: showDescription }"
+ :title="$options.i18n.toggleCommitDescription"
+ :aria-label="$options.i18n.toggleCommitDescription"
+ :selected="showDescription"
+ class="text-expander gl-ml-0!"
+ icon="ellipsis_h"
+ @click="toggleShowDescription"
+ />
+ </div>
+ <div class="committer gl-flex-basis-full">
<gl-link
v-if="commit.author"
:href="commit.author.webPath"
diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb
index fc33e239451..b37d5f3327e 100644
--- a/app/helpers/projects/pipeline_helper.rb
+++ b/app/helpers/projects/pipeline_helper.rb
@@ -31,23 +31,8 @@ module Projects
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
pipeline_iid: pipeline.iid,
pipelines_path: project_pipelines_path(project),
- name: pipeline.name,
- total_jobs: pipeline.total_size,
yaml_errors: pipeline.yaml_errors,
- failure_reason: pipeline.failure_reason,
- triggered_by_path: pipeline.child? ? pipeline_path(pipeline.triggered_by_pipeline) : '',
- schedule: pipeline.schedule?.to_s,
- trigger: pipeline.trigger?.to_s,
- child: pipeline.child?.to_s,
- latest: pipeline.latest?.to_s,
- merge_train_pipeline: pipeline.merge_train_pipeline?.to_s,
- merged_results_pipeline: (pipeline.merged_result_pipeline? && !pipeline.merge_train_pipeline?).to_s,
- invalid: pipeline.has_yaml_errors?.to_s,
- failed: pipeline.failure_reason?.to_s,
- auto_devops: pipeline.auto_devops_source?.to_s,
- detached: pipeline.detached_merge_request_pipeline?.to_s,
- stuck: pipeline.stuck?.to_s,
- ref_text: pipeline.ref_text
+ trigger: pipeline.trigger?.to_s
}
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index cd54ac1b24a..acdb96fb4e6 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -95,7 +95,7 @@ class Namespace < ApplicationRecord
length: { maximum: 255 }
validates :name, uniqueness: { scope: [:type, :parent_id] }, if: -> { parent_id.present? }
- validates :description, length: { maximum: 255 }
+ validates :description, length: { maximum: 500 }
validates :path,
presence: true,
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index e528ff2f870..5d1ebd60413 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -34,7 +34,7 @@
- if @group.description.present?
.group-home-desc.mt-1
- .home-panel-description
+ .home-panel-description.text-break
.home-panel-description-markdown.read-more-container{ itemprop: 'description' }
= markdown_field(@group, :description)
= render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'js-read-more-trigger gl-lg-display-none' }) do
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 22ed6ea4403..115ed0f0fa4 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -17,7 +17,7 @@
.row.gl-mt-3
.form-group.col-md-9
= f.label :description, s_('Groups|Group description (optional)'), class: 'label-bold'
- = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+ = f.text_area :description, class: 'form-control', rows: 3
.row.gl-mt-3
.form-group.col-md-5
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index d4dec49c367..76737ff99c5 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -1,3 +1,4 @@
+.js-snippets-note-edit-form-holder
.snippets.note-edit-form
= form_tag '#', method: :put, class: 'edit-note common-note-form js-quick-submit' do
= hidden_field_tag :target_id, '', class: 'js-form-target-id'
diff --git a/doc/administration/dedicated/index.md b/doc/administration/dedicated/index.md
index 01ce8458aff..2baf4a6e9cb 100644
--- a/doc/administration/dedicated/index.md
+++ b/doc/administration/dedicated/index.md
@@ -222,8 +222,9 @@ window.
To have a change considered for an upcoming weekly maintenance window, all required information
must be submitted in full two business days before the start of the window.
-If there is insufficient time to complete a configuration change during the weekly maintenance
-window, it will postponed to the following week.
+A configuration change might not be applied during an upcoming weekly maintenance window, even if
+it meets the minimum lead time. If GitLab needs to perform high-priority maintenance tasks that
+run beyond the maintenance window, configuration changes will be postponed to the following week.
Changes cannot be applied outside of a weekly maintenance window unless it qualifies for
[emergency support](https://about.gitlab.com/support/#how-to-engage-emergency-support).
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 1d7799a35cc..143d7ff3dad 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -669,7 +669,10 @@ mirror trivy java db:
- oras push $CI_REGISTRY_IMAGE:1 --config /dev/null:application/vnd.aquasec.trivy.config.v1+json javadb.tar.gz:application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip
```
-If the above container registry is `gitlab.example.com/trivy-java-db-mirror`, then the container scanning job should be configured in the following way:
+The vulnerability database is not a regular Docker image, so it is not possible to pull it by using `docker pull`.
+The image shows an error if you navigate to it in the GitLab UI.
+
+If the above container registry is `gitlab.example.com/trivy-java-db-mirror`, then the container scanning job should be configured in the following way. Do not add the tag `:1` at the end, it is added by `trivy`:
```yaml
include:
@@ -677,7 +680,7 @@ include:
container_scanning:
variables:
- CS_TRIVY_JAVA_DB: gitlab.example.com/trivy-java-db-mirror:1
+ CS_TRIVY_JAVA_DB: gitlab.example.com/trivy-java-db-mirror
```
## Running the standalone container scanning tool
diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md
index 4c565aa0c32..694b84c45a6 100644
--- a/doc/user/group/import/index.md
+++ b/doc/user/group/import/index.md
@@ -113,6 +113,8 @@ If you are migrating large projects and encounter problems with timeouts or dura
### Limits
+> Eight hour time limit on migrations [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/429867) in GitLab 16.7.
+
Hardcoded limits apply on migration by direct transfer.
| Limit | Description |
@@ -121,7 +123,6 @@ Hardcoded limits apply on migration by direct transfer.
| 210 seconds | Maximum number of seconds to wait for decompressing an archive file. |
| 50 MB | Maximum length an NDJSON row can have. |
| 5 minutes | Maximum number of seconds until an empty export status on source instance is raised. |
-| 8 hours | Time until migration times out. |
[Configurable limits](../../../administration/settings/account_and_limit_settings.md) are also available.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index bb808065d33..82a254c090a 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -114,7 +114,7 @@ To edit group details:
1. Select **Settings > General**.
1. In the **Group name** text box, enter your group name. See the [limitations on group names](../../user/reserved_names.md).
1. Optional. In the **Group description (optional)** text box, enter your group description.
- The description is limited to 250 characters.
+ The description is limited to 500 characters.
1. Select **Save changes**.
## Remove a group
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 8601e7f7eef..e770940503c 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -52,7 +52,7 @@ To create a project from a built-in template:
- In the **Project slug** field, enter the path to your project. The GitLab instance uses the
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- - In the **Project description (optional)** field, enter the description of your project's dashboard. The description is limited to 250 characters.
+ - In the **Project description (optional)** field, enter the description of your project's dashboard. The description is limited to 500 characters.
- To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
@@ -82,7 +82,7 @@ Custom project templates are available at:
- In the **Project slug** field, enter the path to your project. The GitLab instance uses the
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- - The description of your project's dashboard in the **Project description (optional)** field. The description is limited to 250 characters.
+ - The description of your project's dashboard in the **Project description (optional)** field. The description is limited to 500 characters.
- To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
@@ -107,7 +107,7 @@ To create a project from the HIPAA Audit Protocol template:
- In the **Project slug** field, enter the path to your project. The GitLab instance uses the
slug as the URL path to the project. To change the slug, first enter the project name,
then change the slug.
- - In the **Project description (optional)** field, enter the description of your project's dashboard. The description is limited to 250 characters.
+ - In the **Project description (optional)** field, enter the description of your project's dashboard. The description is limited to 500 characters.
- To modify the project's [viewing and access rights](../public_access.md) for users,
change the **Visibility Level**.
1. Select **Create project**.
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index a7109184945..e007c3fadeb 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -61,7 +61,7 @@ Prerequisites:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > General**.
1. In the **Project name** text box, enter your project name. See the [limitations on project names](../../user/reserved_names.md).
-1. In the **Project description** text box, enter your project description. The description is limited to 250 characters.
+1. In the **Project description** text box, enter your project description. The description is limited to 500 characters.
1. Under **Project avatar**, to change your project avatar, select **Choose file**.
## Star a project
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ba797fc81aa..12f1f35e5ea 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -27823,6 +27823,12 @@ msgstr ""
msgid "KubernetesDashboard|Dashboard"
msgstr ""
+msgid "KubernetesDashboard|Deployment"
+msgstr ""
+
+msgid "KubernetesDashboard|Deployments"
+msgstr ""
+
msgid "KubernetesDashboard|Failed"
msgstr ""
@@ -27847,6 +27853,9 @@ msgstr ""
msgid "KubernetesDashboard|Pods"
msgstr ""
+msgid "KubernetesDashboard|Ready"
+msgstr ""
+
msgid "KubernetesDashboard|Running"
msgstr ""
diff --git a/package.json b/package.json
index 7f96ffcbaa2..834847e1f96 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"@gitlab/cluster-client": "^2.1.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
- "@gitlab/svgs": "3.71.0",
+ "@gitlab/svgs": "3.72.0",
"@gitlab/ui": "^71.1.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20231129035648",
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index 847f7aeae7c..5b1fdd6388a 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
sign_in user
end
- it "sets the session varible for provider 2FA" do
+ it "sets the session variable for provider 2FA" do
post :saml, params: { SAMLResponse: mock_saml_response }
expect(session[:provider_2FA]).to eq(true)
diff --git a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js
index e907bee030c..ac118e07fa3 100644
--- a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js
+++ b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js
@@ -69,7 +69,6 @@ describe('Pipeline details header', () => {
const findPipelineName = () => wrapper.findByTestId('pipeline-name');
const findCommitTitle = () => wrapper.findByTestId('pipeline-commit-title');
const findTotalJobs = () => wrapper.findByTestId('total-jobs');
- const findComputeMinutes = () => wrapper.findByTestId('compute-minutes');
const findCommitLink = () => wrapper.findByTestId('commit-link');
const findPipelineRunningText = () => wrapper.findByTestId('pipeline-running-text').text();
const findPipelineRefText = () => wrapper.findByTestId('pipeline-ref-text').text();
@@ -86,31 +85,12 @@ describe('Pipeline details header', () => {
paths: {
pipelinesPath: '/namespace/my-project/-/pipelines',
fullProject: '/namespace/my-project',
- triggeredByPath: '',
},
};
const defaultProps = {
- name: 'Ruby 3.0 master branch pipeline',
- totalJobs: '50',
- computeMinutes: 0.65,
- yamlErrors: 'errors',
- failureReason: 'pipeline failed',
- badges: {
- schedule: true,
- trigger: false,
- child: false,
- latest: true,
- mergeTrainPipeline: false,
- mergedResultsPipeline: false,
- invalid: false,
- failed: false,
- autoDevops: false,
- detached: false,
- stuck: false,
- },
- refText:
- 'Related merge request <a class="mr-iid" href="/root/ci-project/-/merge_requests/1">!1</a> to merge <a class="ref-name" href="/root/ci-project/-/commits/test">test</a>',
+ yamlErrors: '',
+ trigger: false,
};
const createMockApolloProvider = (handlers) => {
@@ -163,11 +143,11 @@ describe('Pipeline details header', () => {
});
it('displays pipeline name', () => {
- expect(findPipelineName().text()).toBe(defaultProps.name);
+ expect(findPipelineName().text()).toBe('Build pipeline');
});
it('displays total jobs', () => {
- expect(findTotalJobs().text()).toBe('50 Jobs');
+ expect(findTotalJobs().text()).toBe('3 Jobs');
});
it('has link to commit', () => {
@@ -182,13 +162,13 @@ describe('Pipeline details header', () => {
it('displays correct badges', () => {
expect(findAllBadges()).toHaveLength(2);
- expect(wrapper.findByText('latest').exists()).toBe(true);
+ expect(wrapper.findByText('merged results').exists()).toBe(true);
expect(wrapper.findByText('Scheduled').exists()).toBe(true);
expect(wrapper.findByText('trigger token').exists()).toBe(false);
});
it('displays ref text', () => {
- expect(findPipelineRefText()).toBe('Related merge request !1 to merge test');
+ expect(findPipelineRefText()).toBe('Related merge request !1 to merge master into feature');
});
it('displays pipeline user link with required user popover attributes', () => {
@@ -213,7 +193,7 @@ describe('Pipeline details header', () => {
beforeEach(async () => {
createComponent(defaultHandlers, {
...defaultProps,
- badges: { ...defaultProps.badges, trigger: true },
+ trigger: true,
});
await waitForPromises();
@@ -226,7 +206,7 @@ describe('Pipeline details header', () => {
describe('without pipeline name', () => {
it('displays commit title', async () => {
- createComponent(defaultHandlers, { ...defaultProps, name: '' });
+ createComponent([[getPipelineDetailsQuery, runningHandler]]);
await waitForPromises();
@@ -238,22 +218,6 @@ describe('Pipeline details header', () => {
});
describe('finished pipeline', () => {
- it('displays compute minutes when not zero', async () => {
- createComponent();
-
- await waitForPromises();
-
- expect(findComputeMinutes().text()).toBe('0.65');
- });
-
- it('does not display compute minutes when zero', async () => {
- createComponent(defaultHandlers, { ...defaultProps, computeMinutes: 0.0 });
-
- await waitForPromises();
-
- expect(findComputeMinutes().exists()).toBe(false);
- });
-
it('does not display created time ago', async () => {
createComponent();
@@ -288,10 +252,6 @@ describe('Pipeline details header', () => {
await waitForPromises();
});
- it('does not display compute minutes', () => {
- expect(findComputeMinutes().exists()).toBe(false);
- });
-
it('does not display finished time ago', () => {
expect(findFinishedTimeAgo().exists()).toBe(false);
});
diff --git a/spec/frontend/environments/helpers/k8s_integration_helper_spec.js b/spec/frontend/environments/helpers/k8s_integration_helper_spec.js
new file mode 100644
index 00000000000..97100557ef3
--- /dev/null
+++ b/spec/frontend/environments/helpers/k8s_integration_helper_spec.js
@@ -0,0 +1,225 @@
+import {
+ generateServicePortsString,
+ getDeploymentsStatuses,
+ getDaemonSetStatuses,
+ getStatefulSetStatuses,
+ getReplicaSetStatuses,
+ getJobsStatuses,
+ getCronJobsStatuses,
+ humanizeClusterErrors,
+} from '~/environments/helpers/k8s_integration_helper';
+
+import { CLUSTER_AGENT_ERROR_MESSAGES } from '~/environments/constants';
+
+describe('k8s_integration_helper', () => {
+ describe('generateServicePortsString', () => {
+ const port = '8080';
+ const protocol = 'TCP';
+ const nodePort = '31732';
+
+ it('returns empty string if no ports provided', () => {
+ expect(generateServicePortsString([])).toBe('');
+ });
+
+ it('returns port and protocol when provided', () => {
+ expect(generateServicePortsString([{ port, protocol }])).toBe(`${port}/${protocol}`);
+ });
+
+ it('returns port, protocol and nodePort when provided', () => {
+ expect(generateServicePortsString([{ port, protocol, nodePort }])).toBe(
+ `${port}:${nodePort}/${protocol}`,
+ );
+ });
+
+ it('returns joined strings of ports if multiple are provided', () => {
+ expect(
+ generateServicePortsString([
+ { port, protocol },
+ { port, protocol, nodePort },
+ ]),
+ ).toBe(`${port}/${protocol}, ${port}:${nodePort}/${protocol}`);
+ });
+ });
+
+ describe('getDeploymentsStatuses', () => {
+ const pending = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'True' },
+ ],
+ },
+ };
+ const ready = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'True' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ },
+ };
+ const failed = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only pending items'} | ${[pending]} | ${{ pending: [pending] }}
+ ${'there are pending and ready items'} | ${[pending, ready]} | ${{ pending: [pending], ready: [ready] }}
+ ${'there are all kind of items'} | ${[failed, ready, ready, pending]} | ${{ pending: [pending], failed: [failed], ready: [ready, ready] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getDeploymentsStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('getDaemonSetStatuses', () => {
+ const ready = {
+ status: {
+ numberMisscheduled: 0,
+ numberReady: 1,
+ desiredNumberScheduled: 1,
+ },
+ };
+ const failed = {
+ status: {
+ numberReady: 0,
+ desiredNumberScheduled: 1,
+ },
+ };
+ const anotherFailed = {
+ status: {
+ numberReady: 0,
+ desiredNumberScheduled: 0,
+ numberMisscheduled: 1,
+ },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only failed items'} | ${[failed, anotherFailed]} | ${{ failed: [failed, anotherFailed] }}
+ ${'there are only ready items'} | ${[ready]} | ${{ ready: [ready] }}
+ ${'there are all kind of items'} | ${[failed, ready, anotherFailed]} | ${{ failed: [failed, anotherFailed], ready: [ready] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getDaemonSetStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('getStatefulSetStatuses', () => {
+ const ready = {
+ status: {
+ readyReplicas: 1,
+ },
+ spec: { replicas: 1 },
+ };
+ const failed = {
+ status: {
+ readyReplicas: 1,
+ },
+ spec: { replicas: 3 },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only failed items'} | ${[failed, failed]} | ${{ failed: [failed, failed] }}
+ ${'there are only ready items'} | ${[ready]} | ${{ ready: [ready] }}
+ ${'there are all kind of items'} | ${[failed, failed, ready]} | ${{ failed: [failed, failed], ready: [ready] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getStatefulSetStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('getReplicaSetStatuses', () => {
+ const ready = {
+ status: {
+ readyReplicas: 1,
+ },
+ spec: { replicas: 1 },
+ };
+ const failed = {
+ status: {
+ readyReplicas: 1,
+ },
+ spec: { replicas: 3 },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only failed items'} | ${[failed, failed]} | ${{ failed: [failed, failed] }}
+ ${'there are only ready items'} | ${[ready]} | ${{ ready: [ready] }}
+ ${'there are all kind of items'} | ${[failed, failed, ready]} | ${{ failed: [failed, failed], ready: [ready] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getReplicaSetStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('getJobsStatuses', () => {
+ const completed = {
+ status: {
+ succeeded: 1,
+ },
+ spec: { completions: 1 },
+ };
+ const failed = {
+ status: {
+ failed: 1,
+ },
+ spec: { completions: 2 },
+ };
+
+ const anotherFailed = {
+ status: {
+ succeeded: 1,
+ },
+ spec: { completions: 2 },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only failed items'} | ${[failed, anotherFailed]} | ${{ failed: [failed, anotherFailed] }}
+ ${'there are only completed items'} | ${[completed]} | ${{ completed: [completed] }}
+ ${'there are all kind of items'} | ${[failed, completed, anotherFailed]} | ${{ failed: [failed, anotherFailed], completed: [completed] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getJobsStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('getCronJobsStatuses', () => {
+ const suspended = {
+ spec: { suspend: true },
+ };
+ const ready = {
+ status: {
+ active: 2,
+ lastScheduleTime: new Date(),
+ },
+ };
+ const failed = {
+ status: {
+ active: 2,
+ },
+ };
+
+ it.each`
+ condition | items | expected
+ ${'there are only suspended items'} | ${[suspended]} | ${{ suspended: [suspended] }}
+ ${'there are suspended and ready items'} | ${[suspended, ready]} | ${{ suspended: [suspended], ready: [ready] }}
+ ${'there are all kind of items'} | ${[failed, ready, ready, suspended]} | ${{ suspended: [suspended], failed: [failed], ready: [ready, ready] }}
+ `('returns correct object of statuses when $condition', ({ items, expected }) => {
+ expect(getCronJobsStatuses(items)).toEqual(expected);
+ });
+ });
+
+ describe('humanizeClusterErrors', () => {
+ it.each(['unauthorized', 'forbidden', 'not found', 'other'])(
+ 'returns correct object of statuses when error reason is %s',
+ (reason) => {
+ expect(humanizeClusterErrors(reason)).toEqual(CLUSTER_AGENT_ERROR_MESSAGES[reason]);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/fixtures/pipeline_header.rb b/spec/frontend/fixtures/pipeline_header.rb
index 8f5b5b67fae..77d626100ad 100644
--- a/spec/frontend/fixtures/pipeline_header.rb
+++ b/spec/frontend/fixtures/pipeline_header.rb
@@ -18,18 +18,23 @@ RSpec.describe "GraphQL Pipeline Header", '(JavaScript fixtures)', type: :reques
let_it_be(:pipeline) do
create(
:ci_pipeline,
+ :merged_result_pipeline,
project: project,
sha: commit.id,
ref: 'master',
user: user,
+ name: 'Build pipeline',
status: :success,
duration: 7210,
created_at: 2.hours.ago,
started_at: 1.hour.ago,
- finished_at: Time.current
+ finished_at: Time.current,
+ source: :schedule
)
end
+ let_it_be(:builds) { create_list(:ci_build, 3, :success, pipeline: pipeline, ref: 'master') }
+
it "graphql/pipelines/pipeline_header_success.json" do
query = get_graphql_query_as_string(query_path)
diff --git a/spec/frontend/kubernetes_dashboard/graphql/mock_data.js b/spec/frontend/kubernetes_dashboard/graphql/mock_data.js
index 3b51778dbb7..705d2682cd1 100644
--- a/spec/frontend/kubernetes_dashboard/graphql/mock_data.js
+++ b/spec/frontend/kubernetes_dashboard/graphql/mock_data.js
@@ -116,3 +116,110 @@ export const mockPodsTableItems = [
kind: 'Pod',
},
];
+
+const pendingDeployment = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'True' },
+ ],
+ },
+ metadata: {
+ name: 'deployment-1',
+ namespace: 'new-namespace',
+ creationTimestamp: '2023-11-21T11:50:59Z',
+ labels: {},
+ annotations: {},
+ },
+};
+const readyDeployment = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'True' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ },
+ metadata: {
+ name: 'deployment-2',
+ namespace: 'default',
+ creationTimestamp: '2023-07-31T11:50:17Z',
+ labels: {},
+ annotations: {},
+ },
+};
+const failedDeployment = {
+ status: {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ },
+ metadata: {
+ name: 'deployment-3',
+ namespace: 'default',
+ creationTimestamp: '2023-11-21T11:50:59Z',
+ labels: {},
+ annotations: {},
+ },
+};
+
+export const k8sDeploymentsMock = [
+ pendingDeployment,
+ readyDeployment,
+ readyDeployment,
+ failedDeployment,
+];
+
+export const mockDeploymentsStats = [
+ {
+ title: 'Ready',
+ value: 2,
+ },
+ {
+ title: 'Failed',
+ value: 1,
+ },
+ {
+ title: 'Pending',
+ value: 1,
+ },
+];
+
+export const mockDeploymentsTableItems = [
+ {
+ name: 'deployment-1',
+ namespace: 'new-namespace',
+ status: 'Pending',
+ age: '1d',
+ labels: {},
+ annotations: {},
+ kind: 'Deployment',
+ },
+ {
+ name: 'deployment-2',
+ namespace: 'default',
+ status: 'Ready',
+ age: '114d',
+ labels: {},
+ annotations: {},
+ kind: 'Deployment',
+ },
+ {
+ name: 'deployment-2',
+ namespace: 'default',
+ status: 'Ready',
+ age: '114d',
+ labels: {},
+ annotations: {},
+ kind: 'Deployment',
+ },
+ {
+ name: 'deployment-3',
+ namespace: 'default',
+ status: 'Failed',
+ age: '1d',
+ labels: {},
+ annotations: {},
+ kind: 'Deployment',
+ },
+];
diff --git a/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js b/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js
index d1eef5fbc8c..d4f838161ba 100644
--- a/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js
+++ b/spec/frontend/kubernetes_dashboard/graphql/resolvers/kubernetes_spec.js
@@ -1,7 +1,8 @@
-import { CoreV1Api, WatchApi } from '@gitlab/cluster-client';
+import { CoreV1Api, WatchApi, AppsV1Api } from '@gitlab/cluster-client';
import { resolvers } from '~/kubernetes_dashboard/graphql/resolvers';
import k8sDashboardPodsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_pods.query.graphql';
-import { k8sPodsMock } from '../mock_data';
+import k8sDashboardDeploymentsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql';
+import { k8sPodsMock, k8sDeploymentsMock } from '../mock_data';
describe('~/frontend/environments/graphql/resolvers', () => {
let mockResolvers;
@@ -98,4 +99,92 @@ describe('~/frontend/environments/graphql/resolvers', () => {
).rejects.toThrow('API error');
});
});
+
+ describe('k8sDeployments', () => {
+ const client = { writeQuery: jest.fn() };
+
+ const mockWatcher = WatchApi.prototype;
+ const mockDeploymentsListWatcherFn = jest.fn().mockImplementation(() => {
+ return Promise.resolve(mockWatcher);
+ });
+
+ const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
+ if (eventName === 'data') {
+ callback([]);
+ }
+ });
+
+ const mockDeploymentsListFn = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ items: k8sDeploymentsMock,
+ });
+ });
+
+ const mockAllDeploymentsListFn = jest.fn().mockImplementation(mockDeploymentsListFn);
+
+ describe('when the deployments data is present', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces')
+ .mockImplementation(mockAllDeploymentsListFn);
+ jest
+ .spyOn(mockWatcher, 'subscribeToStream')
+ .mockImplementation(mockDeploymentsListWatcherFn);
+ jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn);
+ });
+
+ it('should request all deployments from the cluster_client library and watch the events', async () => {
+ const deployments = await mockResolvers.Query.k8sDeployments(
+ null,
+ {
+ configuration,
+ },
+ { client },
+ );
+
+ expect(mockAllDeploymentsListFn).toHaveBeenCalled();
+ expect(mockDeploymentsListWatcherFn).toHaveBeenCalled();
+
+ expect(deployments).toEqual(k8sDeploymentsMock);
+ });
+
+ it('should update cache with the new data when received from the library', async () => {
+ await mockResolvers.Query.k8sDeployments(
+ null,
+ { configuration, namespace: '' },
+ { client },
+ );
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: k8sDashboardDeploymentsQuery,
+ variables: { configuration, namespace: '' },
+ data: { k8sDeployments: [] },
+ });
+ });
+ });
+
+ it('should not watch deployments from the cluster_client library when the deployments data is not present', async () => {
+ jest.spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces').mockImplementation(
+ jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ items: [],
+ });
+ }),
+ );
+
+ await mockResolvers.Query.k8sDeployments(null, { configuration }, { client });
+
+ expect(mockDeploymentsListWatcherFn).not.toHaveBeenCalled();
+ });
+
+ it('should throw an error if the API call fails', async () => {
+ jest
+ .spyOn(AppsV1Api.prototype, 'listAppsV1DeploymentForAllNamespaces')
+ .mockRejectedValue(new Error('API error'));
+
+ await expect(
+ mockResolvers.Query.k8sDeployments(null, { configuration }, { client }),
+ ).rejects.toThrow('API error');
+ });
+ });
});
diff --git a/spec/frontend/kubernetes_dashboard/helpers/k8s_integraion_helper_spec.js b/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js
index 0a61604fb6c..c52527f0d39 100644
--- a/spec/frontend/kubernetes_dashboard/helpers/k8s_integraion_helper_spec.js
+++ b/spec/frontend/kubernetes_dashboard/helpers/k8s_integration_helper_spec.js
@@ -1,4 +1,7 @@
-import { getAge } from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
+import {
+ getAge,
+ calculateDeploymentStatus,
+} from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
import { useFakeDate } from 'helpers/fake_date';
describe('k8s_integration_helper', () => {
@@ -19,4 +22,34 @@ describe('k8s_integration_helper', () => {
expect(getAge(timestamp)).toBe(expected);
});
});
+
+ describe('calculateDeploymentStatus', () => {
+ const pending = {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'True' },
+ ],
+ };
+ const ready = {
+ conditions: [
+ { type: 'Available', status: 'True' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ };
+ const failed = {
+ conditions: [
+ { type: 'Available', status: 'False' },
+ { type: 'Progressing', status: 'False' },
+ ],
+ };
+
+ it.each`
+ condition | status | expected
+ ${'Available is false and Progressing is true'} | ${pending} | ${'Pending'}
+ ${'Available is true and Progressing is false'} | ${ready} | ${'Ready'}
+ ${'Available is false and Progressing is false'} | ${failed} | ${'Failed'}
+ `('returns status as $expected when $condition', ({ status, expected }) => {
+ expect(calculateDeploymentStatus({ status })).toBe(expected);
+ });
+ });
});
diff --git a/spec/frontend/kubernetes_dashboard/pages/deployments_page_spec.js b/spec/frontend/kubernetes_dashboard/pages/deployments_page_spec.js
new file mode 100644
index 00000000000..371116f0495
--- /dev/null
+++ b/spec/frontend/kubernetes_dashboard/pages/deployments_page_spec.js
@@ -0,0 +1,106 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import DeploymentsPage from '~/kubernetes_dashboard/pages/deployments_page.vue';
+import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue';
+import { useFakeDate } from 'helpers/fake_date';
+import {
+ k8sDeploymentsMock,
+ mockDeploymentsStats,
+ mockDeploymentsTableItems,
+} from '../graphql/mock_data';
+
+Vue.use(VueApollo);
+
+describe('Kubernetes dashboard deployments page', () => {
+ let wrapper;
+
+ const configuration = {
+ basePath: 'kas/tunnel/url',
+ baseOptions: {
+ headers: { 'GitLab-Agent-Id': '1' },
+ },
+ };
+
+ const findWorkloadLayout = () => wrapper.findComponent(WorkloadLayout);
+
+ const createApolloProvider = () => {
+ const mockResolvers = {
+ Query: {
+ k8sDeployments: jest.fn().mockReturnValue(k8sDeploymentsMock),
+ },
+ };
+
+ return createMockApollo([], mockResolvers);
+ };
+
+ const createWrapper = (apolloProvider = createApolloProvider()) => {
+ wrapper = shallowMount(DeploymentsPage, {
+ provide: { configuration },
+ apolloProvider,
+ });
+ };
+
+ describe('mounted', () => {
+ it('renders WorkloadLayout component', () => {
+ createWrapper();
+
+ expect(findWorkloadLayout().exists()).toBe(true);
+ });
+
+ it('sets loading prop for the WorkloadLayout', () => {
+ createWrapper();
+
+ expect(findWorkloadLayout().props('loading')).toBe(true);
+ });
+
+ it('removes loading prop from the WorkloadLayout when the list of pods loaded', async () => {
+ createWrapper();
+ await waitForPromises();
+
+ expect(findWorkloadLayout().props('loading')).toBe(false);
+ });
+ });
+
+ describe('when gets pods data', () => {
+ useFakeDate(2023, 10, 23, 10, 10);
+
+ it('sets correct stats object for the WorkloadLayout', async () => {
+ createWrapper();
+ await waitForPromises();
+
+ expect(findWorkloadLayout().props('stats')).toEqual(mockDeploymentsStats);
+ });
+
+ it('sets correct table items object for the WorkloadLayout', async () => {
+ createWrapper();
+ await waitForPromises();
+
+ expect(findWorkloadLayout().props('items')).toMatchObject(mockDeploymentsTableItems);
+ });
+ });
+
+ 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: {
+ k8sDeployments: jest.fn().mockRejectedValueOnce(error),
+ },
+ };
+
+ return createMockApollo([], mockResolvers);
+ };
+
+ beforeEach(async () => {
+ createWrapper(createErroredApolloProvider());
+ await waitForPromises();
+ });
+
+ it('sets errorMessage prop for the WorkloadLayout', () => {
+ expect(findWorkloadLayout().props('errorMessage')).toBe(error.message);
+ });
+ });
+});
diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb
index 7e117fe0cce..7b8b349c3e5 100644
--- a/spec/helpers/projects/pipeline_helper_spec.rb
+++ b/spec/helpers/projects/pipeline_helper_spec.rb
@@ -48,22 +48,8 @@ RSpec.describe Projects::PipelineHelper do
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
pipeline_iid: pipeline.iid,
pipelines_path: project_pipelines_path(project),
- name: pipeline.name,
- total_jobs: pipeline.total_size,
yaml_errors: pipeline.yaml_errors,
- failure_reason: pipeline.failure_reason,
- triggered_by_path: '',
- schedule: pipeline.schedule?.to_s,
- trigger: pipeline.trigger?.to_s,
- child: pipeline.child?.to_s,
- latest: pipeline.latest?.to_s,
- merge_train_pipeline: pipeline.merge_train_pipeline?.to_s,
- invalid: pipeline.has_yaml_errors?.to_s,
- failed: pipeline.failure_reason?.to_s,
- auto_devops: pipeline.auto_devops_source?.to_s,
- detached: pipeline.detached_merge_request_pipeline?.to_s,
- stuck: pipeline.stuck?.to_s,
- ref_text: pipeline.ref_text
+ trigger: pipeline.trigger?.to_s
})
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 3054bcacd9c..a226d1cefe4 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
describe 'validations' do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(255) }
- it { is_expected.to validate_length_of(:description).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(500) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_length_of(:path).is_at_most(255) }
it { is_expected.to validate_presence_of(:owner) }
diff --git a/yarn.lock b/yarn.lock
index b72a2afd272..eefa4895f3b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1269,10 +1269,10 @@
stylelint-declaration-strict-value "1.9.2"
stylelint-scss "5.1.0"
-"@gitlab/svgs@3.71.0":
- version "3.71.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.71.0.tgz#ff4a3cf22cd12b3c861ef2065583cc49923cf5f8"
- integrity sha512-aYjC9uef5Q3CDg4Zu9fh0mce4jO2LANaEgRLutoAYRXG4ymWwRmgP8SZmZyQY0B4hcZjBfUsyVykIhVnlNcRLw==
+"@gitlab/svgs@3.72.0":
+ version "3.72.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.72.0.tgz#5daaa7366913b52ea89439305067e030f967c8a5"
+ integrity sha512-VbSdwXxu9Y6NAXNFTROjZa83e2b8QeDAO7byqjJ0z+2Y3gGGXdw+HclAzz0Ns8B0+DMV5mV7dtmTlv/1xAXXYQ==
"@gitlab/ui@^71.1.1":
version "71.1.1"