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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-22 06:10:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-22 06:10:27 +0300
commit202fdd6ddfa47e69e5ac74853dc0deb51c9be36a (patch)
tree031bc219cae30336e1f05b6fd996cf107bbf5cbb /app
parent85bc1764095bf6f8d2557837b8e3dea9a2149e55 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/environments/helpers/k8s_integration_helper.js52
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/constants.js6
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/client.js78
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js38
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql18
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql18
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js92
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js19
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue84
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue80
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/constants.js4
-rw-r--r--app/assets/javascripts/kubernetes_dashboard/router/routes.js23
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue8
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue5
-rw-r--r--app/assets/javascripts/work_items/constants.js7
-rw-r--r--app/helpers/application_settings_helper.rb4
16 files changed, 460 insertions, 76 deletions
diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
index bb5cab7c279..8b907f0b174 100644
--- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
+++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
@@ -2,8 +2,15 @@ import {
calculateDeploymentStatus,
calculateStatefulSetStatus,
calculateDaemonSetStatus,
+ calculateJobStatus,
+ calculateCronJobStatus,
} from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
-import { STATUS_READY, STATUS_FAILED } from '~/kubernetes_dashboard/constants';
+import {
+ STATUS_READY,
+ STATUS_FAILED,
+ STATUS_COMPLETED,
+ STATUS_SUSPENDED,
+} from '~/kubernetes_dashboard/constants';
import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants';
export function generateServicePortsString(ports) {
@@ -45,13 +52,14 @@ export function getDeploymentsStatuses(items) {
};
}
+const isCompleted = (status) => status === STATUS_COMPLETED;
+const isReady = (status) => status === STATUS_READY;
+const isFailed = (status) => status === STATUS_FAILED;
+const isSuspended = (status) => status === STATUS_SUSPENDED;
+
export function getDaemonSetStatuses(items) {
- const failed = items.filter((item) => {
- return calculateDaemonSetStatus(item) === STATUS_FAILED;
- });
- const ready = items.filter((item) => {
- return calculateDaemonSetStatus(item) === STATUS_READY;
- });
+ const failed = items.filter((item) => isFailed(calculateDaemonSetStatus(item)));
+ const ready = items.filter((item) => isReady(calculateDaemonSetStatus(item)));
return {
...(failed.length && { failed }),
@@ -60,12 +68,8 @@ export function getDaemonSetStatuses(items) {
}
export function getStatefulSetStatuses(items) {
- const failed = items.filter((item) => {
- return calculateStatefulSetStatus(item) === STATUS_FAILED;
- });
- const ready = items.filter((item) => {
- return calculateStatefulSetStatus(item) === STATUS_READY;
- });
+ const failed = items.filter((item) => isFailed(calculateStatefulSetStatus(item)));
+ const ready = items.filter((item) => isReady(calculateStatefulSetStatus(item)));
return {
...(failed.length && { failed }),
@@ -74,12 +78,8 @@ export function getStatefulSetStatuses(items) {
}
export function getReplicaSetStatuses(items) {
- const failed = items.filter((item) => {
- return calculateStatefulSetStatus(item) === STATUS_FAILED;
- });
- const ready = items.filter((item) => {
- return calculateStatefulSetStatus(item) === STATUS_READY;
- });
+ const failed = items.filter((item) => isFailed(calculateStatefulSetStatus(item)));
+ const ready = items.filter((item) => isReady(calculateStatefulSetStatus(item)));
return {
...(failed.length && { failed }),
@@ -88,12 +88,8 @@ export function getReplicaSetStatuses(items) {
}
export function getJobsStatuses(items) {
- const failed = items.filter((item) => {
- return item.status.failed > 0 || item.status?.succeeded !== item.spec?.completions;
- });
- const completed = items.filter((item) => {
- return item.status?.succeeded === item.spec?.completions;
- });
+ const failed = items.filter((item) => isFailed(calculateJobStatus(item)));
+ const completed = items.filter((item) => isCompleted(calculateJobStatus(item)));
return {
...(failed.length && { failed }),
@@ -107,11 +103,11 @@ export function getCronJobsStatuses(items) {
const suspended = [];
items.forEach((item) => {
- if (item.status?.active > 0 && !item.status?.lastScheduleTime) {
+ if (isFailed(calculateCronJobStatus(item))) {
failed.push(item);
- } else if (item.spec?.suspend) {
+ } else if (isSuspended(calculateCronJobStatus(item))) {
suspended.push(item);
- } else if (item.status?.lastScheduleTime) {
+ } else if (isReady(calculateCronJobStatus(item))) {
ready.push(item);
}
});
diff --git a/app/assets/javascripts/kubernetes_dashboard/constants.js b/app/assets/javascripts/kubernetes_dashboard/constants.js
index b93740aec90..cc554722bba 100644
--- a/app/assets/javascripts/kubernetes_dashboard/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/constants.js
@@ -5,6 +5,8 @@ export const STATUS_PENDING = 'Pending';
export const STATUS_SUCCEEDED = 'Succeeded';
export const STATUS_FAILED = 'Failed';
export const STATUS_READY = 'Ready';
+export const STATUS_COMPLETED = 'Completed';
+export const STATUS_SUSPENDED = 'Suspended';
export const STATUS_LABELS = {
[STATUS_RUNNING]: s__('KubernetesDashboard|Running'),
@@ -12,6 +14,8 @@ export const STATUS_LABELS = {
[STATUS_SUCCEEDED]: s__('KubernetesDashboard|Succeeded'),
[STATUS_FAILED]: s__('KubernetesDashboard|Failed'),
[STATUS_READY]: s__('KubernetesDashboard|Ready'),
+ [STATUS_COMPLETED]: s__('KubernetesDashboard|Completed'),
+ [STATUS_SUSPENDED]: s__('KubernetesDashboard|Suspended'),
};
export const WORKLOAD_STATUS_BADGE_VARIANTS = {
@@ -20,6 +24,8 @@ export const WORKLOAD_STATUS_BADGE_VARIANTS = {
[STATUS_SUCCEEDED]: 'success',
[STATUS_FAILED]: 'danger',
[STATUS_READY]: 'success',
+ [STATUS_COMPLETED]: 'success',
+ [STATUS_SUSPENDED]: 'neutral',
};
export const PAGE_SIZE = 20;
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
index 5894472d83b..4a1ab56a8e9 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/client.js
@@ -6,6 +6,8 @@ import k8sDeploymentsQuery from './queries/k8s_dashboard_deployments.query.graph
import k8sStatefulSetsQuery from './queries/k8s_dashboard_stateful_sets.query.graphql';
import k8sReplicaSetsQuery from './queries/k8s_dashboard_replica_sets.query.graphql';
import k8sDaemonSetsQuery from './queries/k8s_dashboard_daemon_sets.query.graphql';
+import k8sJobsQuery from './queries/k8s_dashboard_jobs.query.graphql';
+import k8sCronJobsQuery from './queries/k8s_dashboard_cron_jobs.query.graphql';
import { resolvers } from './resolvers';
export const apolloProvider = () => {
@@ -14,16 +16,18 @@ export const apolloProvider = () => {
});
const { cache } = defaultClient;
+ const metadata = {
+ name: null,
+ namespace: null,
+ creationTimestamp: null,
+ labels: null,
+ annotations: null,
+ };
+
cache.writeQuery({
query: k8sPodsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
phase: null,
},
@@ -33,13 +37,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sDeploymentsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
conditions: null,
},
@@ -49,13 +47,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sStatefulSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
readyReplicas: null,
},
@@ -68,13 +60,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sReplicaSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
readyReplicas: null,
},
@@ -87,13 +73,7 @@ export const apolloProvider = () => {
cache.writeQuery({
query: k8sDaemonSetsQuery,
data: {
- metadata: {
- name: null,
- namespace: null,
- creationTimestamp: null,
- labels: null,
- annotations: null,
- },
+ metadata,
status: {
numberMisscheduled: null,
numberReady: null,
@@ -102,6 +82,34 @@ export const apolloProvider = () => {
},
});
+ cache.writeQuery({
+ query: k8sJobsQuery,
+ data: {
+ metadata,
+ status: {
+ failed: null,
+ succeeded: null,
+ },
+ spec: {
+ completions: null,
+ },
+ },
+ });
+
+ cache.writeQuery({
+ query: k8sCronJobsQuery,
+ data: {
+ metadata,
+ status: {
+ active: null,
+ lastScheduleTime: null,
+ },
+ spec: {
+ suspend: 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 47c2f543357..a06883a0b24 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/helpers/resolver_helpers.js
@@ -43,6 +43,44 @@ export const mapSetItem = (item) => {
return { status, metadata, spec };
};
+export const mapJobItem = (item) => {
+ const metadata = {
+ ...item.metadata,
+ annotations: item.metadata?.annotations || {},
+ labels: item.metadata?.labels || {},
+ };
+
+ const status = {
+ failed: item.status?.failed || 0,
+ succeeded: item.status?.succeeded || 0,
+ };
+
+ return {
+ status,
+ metadata,
+ spec: item.spec,
+ };
+};
+
+export const mapCronJobItem = (item) => {
+ const metadata = {
+ ...item.metadata,
+ annotations: item.metadata?.annotations || {},
+ labels: item.metadata?.labels || {},
+ };
+
+ const status = {
+ active: item.status?.active || 0,
+ lastScheduleTime: item.status?.lastScheduleTime || null,
+ };
+
+ return {
+ status,
+ metadata,
+ spec: item.spec,
+ };
+};
+
export const watchWorkloadItems = ({
client,
query,
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql
new file mode 100644
index 00000000000..fe20cd2e70e
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_cron_jobs.query.graphql
@@ -0,0 +1,18 @@
+query getK8sDashboardCronJobs($configuration: LocalConfiguration) {
+ k8sCronJobs(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ status {
+ active
+ lastScheduleTime
+ }
+ spec {
+ suspend
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql
new file mode 100644
index 00000000000..86afb47f2f9
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/queries/k8s_dashboard_jobs.query.graphql
@@ -0,0 +1,18 @@
+query getK8sDashboardJobs($configuration: LocalConfiguration) {
+ k8sJobs(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ labels
+ annotations
+ }
+ status {
+ failed
+ succeeded
+ }
+ spec {
+ completions
+ }
+ }
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
index e59bed5581b..3450e2780cb 100644
--- a/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/graphql/resolvers/kubernetes.js
@@ -1,4 +1,4 @@
-import { Configuration, AppsV1Api } from '@gitlab/cluster-client';
+import { Configuration, AppsV1Api, BatchV1Api } from '@gitlab/cluster-client';
import {
getK8sPods,
@@ -7,12 +7,16 @@ import {
mapSetItem,
buildWatchPath,
watchWorkloadItems,
+ mapJobItem,
+ mapCronJobItem,
} from '../helpers/resolver_helpers';
import k8sDashboardPodsQuery from '../queries/k8s_dashboard_pods.query.graphql';
import k8sDashboardDeploymentsQuery from '../queries/k8s_dashboard_deployments.query.graphql';
import k8sDashboardStatefulSetsQuery from '../queries/k8s_dashboard_stateful_sets.query.graphql';
import k8sDashboardReplicaSetsQuery from '../queries/k8s_dashboard_replica_sets.query.graphql';
import k8sDaemonSetsQuery from '../queries/k8s_dashboard_daemon_sets.query.graphql';
+import k8sJobsQuery from '../queries/k8s_dashboard_jobs.query.graphql';
+import k8sCronJobsQuery from '../queries/k8s_dashboard_cron_jobs.query.graphql';
export default {
k8sPods(_, { configuration }, { client }) {
@@ -61,10 +65,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const statefulSetsApi = namespace
? appsV1api.listAppsV1NamespacedStatefulSet({ namespace })
: appsV1api.listAppsV1StatefulSetForAllNamespaces();
- return deploymentsApi
+ return statefulSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'statefulsets',
@@ -98,10 +102,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const replicaSetsApi = namespace
? appsV1api.listAppsV1NamespacedReplicaSet({ namespace })
: appsV1api.listAppsV1ReplicaSetForAllNamespaces();
- return deploymentsApi
+ return replicaSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'replicasets',
@@ -135,10 +139,10 @@ export default {
const config = new Configuration(configuration);
const appsV1api = new AppsV1Api(config);
- const deploymentsApi = namespace
+ const daemonSetsApi = namespace
? appsV1api.listAppsV1NamespacedDaemonSet({ namespace })
: appsV1api.listAppsV1DaemonSetForAllNamespaces();
- return deploymentsApi
+ return daemonSetsApi
.then((res) => {
const watchPath = buildWatchPath({
resource: 'daemonsets',
@@ -166,4 +170,78 @@ export default {
}
});
},
+
+ k8sJobs(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const batchV1api = new BatchV1Api(config);
+ const jobsApi = namespace
+ ? batchV1api.listBatchV1NamespacedJob({ namespace })
+ : batchV1api.listBatchV1JobForAllNamespaces();
+ return jobsApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'jobs',
+ api: 'apis/batch/v1',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sJobsQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sJobs',
+ mapFn: mapJobItem,
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapJobItem);
+ })
+ .catch(async (err) => {
+ try {
+ await handleClusterError(err);
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ });
+ },
+
+ k8sCronJobs(_, { configuration, namespace = '' }, { client }) {
+ const config = new Configuration(configuration);
+
+ const batchV1api = new BatchV1Api(config);
+ const cronJobsApi = namespace
+ ? batchV1api.listBatchV1NamespacedCronJob({ namespace })
+ : batchV1api.listBatchV1CronJobForAllNamespaces();
+ return cronJobsApi
+ .then((res) => {
+ const watchPath = buildWatchPath({
+ resource: 'cronjobs',
+ api: 'apis/batch/v1',
+ namespace,
+ });
+ watchWorkloadItems({
+ client,
+ query: k8sCronJobsQuery,
+ configuration,
+ namespace,
+ watchPath,
+ queryField: 'k8sCronJobs',
+ mapFn: mapCronJobItem,
+ });
+
+ const data = res?.items || [];
+
+ return data.map(mapCronJobItem);
+ })
+ .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 24f43e21506..25135e23dc8 100644
--- a/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
+++ b/app/assets/javascripts/kubernetes_dashboard/helpers/k8s_integration_helper.js
@@ -5,6 +5,8 @@ import {
STATUS_PENDING,
STATUS_READY,
STATUS_FAILED,
+ STATUS_COMPLETED,
+ STATUS_SUSPENDED,
} from '../constants';
export function getAge(creationTimestamp) {
@@ -58,3 +60,20 @@ export function calculateDaemonSetStatus(item) {
}
return STATUS_FAILED;
}
+
+export function calculateJobStatus(item) {
+ if (item.status.failed > 0 || item.status?.succeeded !== item.spec?.completions) {
+ return STATUS_FAILED;
+ }
+ return STATUS_COMPLETED;
+}
+
+export function calculateCronJobStatus(item) {
+ if (item.status?.active > 0 && !item.status?.lastScheduleTime) {
+ return STATUS_FAILED;
+ }
+ if (item.spec?.suspend) {
+ return STATUS_SUSPENDED;
+ }
+ return STATUS_READY;
+}
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue
new file mode 100644
index 00000000000..2d57bfdc9fc
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/cron_jobs_page.vue
@@ -0,0 +1,84 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, calculateCronJobStatus } from '../helpers/k8s_integration_helper';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sCronJobsQuery from '../graphql/queries/k8s_dashboard_cron_jobs.query.graphql';
+import { STATUS_FAILED, STATUS_READY, STATUS_SUSPENDED, STATUS_LABELS } from '../constants';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sCronJobs: {
+ query: k8sCronJobsQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.k8sCronJobs?.map((job) => {
+ return {
+ name: job.metadata?.name,
+ namespace: job.metadata?.namespace,
+ status: calculateCronJobStatus(job),
+ age: getAge(job.metadata?.creationTimestamp),
+ labels: job.metadata?.labels,
+ annotations: job.metadata?.annotations,
+ kind: s__('KubernetesDashboard|CronJob'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sCronJobs: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ cronJobsStats() {
+ return [
+ {
+ value: this.countJobsByStatus(STATUS_READY),
+ title: STATUS_LABELS[STATUS_READY],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_FAILED),
+ title: STATUS_LABELS[STATUS_FAILED],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_SUSPENDED),
+ title: STATUS_LABELS[STATUS_SUSPENDED],
+ },
+ ];
+ },
+ loading() {
+ return this.$apollo.queries.k8sCronJobs.loading;
+ },
+ },
+ methods: {
+ countJobsByStatus(phase) {
+ const filteredJobs = this.k8sCronJobs.filter((item) => item.status === phase) || [];
+
+ return filteredJobs.length;
+ },
+ },
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="cronJobsStats"
+ :items="k8sCronJobs"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue b/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue
new file mode 100644
index 00000000000..f9dbb53e8b4
--- /dev/null
+++ b/app/assets/javascripts/kubernetes_dashboard/pages/jobs_page.vue
@@ -0,0 +1,80 @@
+<script>
+import { s__ } from '~/locale';
+import { getAge, calculateJobStatus } from '../helpers/k8s_integration_helper';
+import WorkloadLayout from '../components/workload_layout.vue';
+import k8sJobsQuery from '../graphql/queries/k8s_dashboard_jobs.query.graphql';
+import { STATUS_FAILED, STATUS_COMPLETED, STATUS_LABELS } from '../constants';
+
+export default {
+ components: {
+ WorkloadLayout,
+ },
+ inject: ['configuration'],
+ apollo: {
+ k8sJobs: {
+ query: k8sJobsQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return (
+ data?.k8sJobs?.map((job) => {
+ return {
+ name: job.metadata?.name,
+ namespace: job.metadata?.namespace,
+ status: calculateJobStatus(job),
+ age: getAge(job.metadata?.creationTimestamp),
+ labels: job.metadata?.labels,
+ annotations: job.metadata?.annotations,
+ kind: s__('KubernetesDashboard|Job'),
+ };
+ }) || []
+ );
+ },
+ error(err) {
+ this.errorMessage = err?.message;
+ },
+ },
+ },
+ data() {
+ return {
+ k8sJobs: [],
+ errorMessage: '',
+ };
+ },
+ computed: {
+ jobsStats() {
+ return [
+ {
+ value: this.countJobsByStatus(STATUS_COMPLETED),
+ title: STATUS_LABELS[STATUS_COMPLETED],
+ },
+ {
+ value: this.countJobsByStatus(STATUS_FAILED),
+ title: STATUS_LABELS[STATUS_FAILED],
+ },
+ ];
+ },
+ loading() {
+ return this.$apollo.queries.k8sJobs.loading;
+ },
+ },
+ methods: {
+ countJobsByStatus(phase) {
+ const filteredJobs = this.k8sJobs.filter((item) => item.status === phase) || [];
+
+ return filteredJobs.length;
+ },
+ },
+};
+</script>
+<template>
+ <workload-layout
+ :loading="loading"
+ :error-message="errorMessage"
+ :stats="jobsStats"
+ :items="k8sJobs"
+ />
+</template>
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/constants.js b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
index 700f501ade4..a383ccd03e1 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/constants.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/constants.js
@@ -3,9 +3,13 @@ export const DEPLOYMENTS_ROUTE_NAME = 'deployments';
export const STATEFUL_SETS_ROUTE_NAME = 'statefulSets';
export const REPLICA_SETS_ROUTE_NAME = 'replicaSets';
export const DAEMON_SETS_ROUTE_NAME = 'daemonSets';
+export const JOBS_ROUTE_NAME = 'jobs';
+export const CRON_JOBS_ROUTE_NAME = 'cronJobs';
export const PODS_ROUTE_PATH = '/pods';
export const DEPLOYMENTS_ROUTE_PATH = '/deployments';
export const STATEFUL_SETS_ROUTE_PATH = '/statefulsets';
export const REPLICA_SETS_ROUTE_PATH = '/replicasets';
export const DAEMON_SETS_ROUTE_PATH = '/daemonsets';
+export const JOBS_ROUTE_PATH = '/jobs';
+export const CRON_JOBS_ROUTE_PATH = '/cronjobs';
diff --git a/app/assets/javascripts/kubernetes_dashboard/router/routes.js b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
index a1684a62ca4..01bb48e8dce 100644
--- a/app/assets/javascripts/kubernetes_dashboard/router/routes.js
+++ b/app/assets/javascripts/kubernetes_dashboard/router/routes.js
@@ -4,6 +4,9 @@ import DeploymentsPage from '../pages/deployments_page.vue';
import StatefulSetsPage from '../pages/stateful_sets_page.vue';
import ReplicaSetsPage from '../pages/replica_sets_page.vue';
import DaemonSetsPage from '../pages/daemon_sets_page.vue';
+import JobsPage from '../pages/jobs_page.vue';
+import CronJobsPage from '../pages/cron_jobs_page.vue';
+
import {
PODS_ROUTE_NAME,
PODS_ROUTE_PATH,
@@ -15,6 +18,10 @@ import {
REPLICA_SETS_ROUTE_PATH,
DAEMON_SETS_ROUTE_NAME,
DAEMON_SETS_ROUTE_PATH,
+ JOBS_ROUTE_NAME,
+ JOBS_ROUTE_PATH,
+ CRON_JOBS_ROUTE_NAME,
+ CRON_JOBS_ROUTE_PATH,
} from './constants';
export default [
@@ -58,4 +65,20 @@ export default [
title: s__('KubernetesDashboard|DaemonSets'),
},
},
+ {
+ name: JOBS_ROUTE_NAME,
+ path: JOBS_ROUTE_PATH,
+ component: JobsPage,
+ meta: {
+ title: s__('KubernetesDashboard|Jobs'),
+ },
+ },
+ {
+ name: CRON_JOBS_ROUTE_NAME,
+ path: CRON_JOBS_ROUTE_PATH,
+ component: CronJobsPage,
+ meta: {
+ title: s__('KubernetesDashboard|CronJobs'),
+ },
+ },
];
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 93f552bfa4c..485d85b8872 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -19,6 +19,7 @@ import {
WIDGET_TYPE_AWARD_EMOJI,
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
+ WORK_ITEM_TYPE_VALUE_EPIC,
WIDGET_TYPE_NOTES,
WIDGET_TYPE_LINKED_ITEMS,
} from '../constants';
@@ -232,6 +233,11 @@ export default {
workItemLinkedItems() {
return this.isWidgetPresent(WIDGET_TYPE_LINKED_ITEMS);
},
+ showWorkItemTree() {
+ return [WORK_ITEM_TYPE_VALUE_OBJECTIVE, WORK_ITEM_TYPE_VALUE_EPIC].includes(
+ this.workItemType,
+ );
+ },
showWorkItemLinkedItems() {
return this.hasLinkedWorkItems && this.workItemLinkedItems;
},
@@ -586,7 +592,7 @@ export default {
@emoji-updated="$emit('work-item-emoji-updated', $event)"
/>
<work-item-tree
- v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE"
+ v-if="showWorkItemTree"
:full-path="fullPath"
:work-item-type="workItemType"
:parent-work-item-type="workItem.workItemType.name"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index f24b56cac36..cc46932539d 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -11,6 +11,7 @@ import {
import { __, s__, sprintf } from '~/locale';
import WorkItemTokenInput from '../shared/work_item_token_input.vue';
import { addHierarchyChild } from '../../graphql/cache_utils';
+import groupWorkItemTypesQuery from '../../graphql/group_work_item_types.query.graphql';
import projectWorkItemTypesQuery from '../../graphql/project_work_item_types.query.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
import createWorkItemMutation from '../../graphql/create_work_item.mutation.graphql';
@@ -90,7 +91,9 @@ export default {
},
apollo: {
workItemTypes: {
- query: projectWorkItemTypesQuery,
+ query() {
+ return this.isGroup ? groupWorkItemTypesQuery : projectWorkItemTypesQuery;
+ },
variables() {
return {
fullPath: this.fullPath,
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 51f63b147ac..62fdc8a21c2 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -201,6 +201,8 @@ export const WORK_ITEMS_TYPE_MAP = {
export const WORK_ITEM_TYPE_VALUE_MAP = {
[WORK_ITEM_TYPE_VALUE_OBJECTIVE]: WORK_ITEM_TYPE_ENUM_OBJECTIVE,
[WORK_ITEM_TYPE_VALUE_KEY_RESULT]: WORK_ITEM_TYPE_ENUM_KEY_RESULT,
+ [WORK_ITEM_TYPE_VALUE_ISSUE]: WORK_ITEM_TYPE_ENUM_ISSUE,
+ [WORK_ITEM_TYPE_VALUE_EPIC]: WORK_ITEM_TYPE_ENUM_EPIC,
};
export const WORK_ITEMS_TREE_TEXT_MAP = {
@@ -214,9 +216,14 @@ export const WORK_ITEMS_TREE_TEXT_MAP = {
'WorkItem|No tasks are currently assigned. Use tasks to break down this issue into smaller parts.',
),
},
+ [WORK_ITEM_TYPE_VALUE_EPIC]: {
+ title: s__('WorkItem|Child items'),
+ empty: s__('WorkItem|No epics or issues are currently assigned.'),
+ },
};
export const WORK_ITEM_NAME_TO_ICON_MAP = {
+ Epic: 'epic',
Issue: 'issue-type-issue',
Task: 'issue-type-task',
Objective: 'issue-type-objective',
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 655fdf8b8ec..e01496ca254 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -564,10 +564,6 @@ module ApplicationSettingsHelper
can?(current_user, :read_cluster, clusterable)
end
- def omnibus_protected_paths_throttle?
- Rack::Attack.throttles.key?('protected paths')
- end
-
def valid_runner_registrars
Gitlab::CurrentSettings.valid_runner_registrars
end