diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-22 06:10:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-22 06:10:27 +0300 |
commit | 202fdd6ddfa47e69e5ac74853dc0deb51c9be36a (patch) | |
tree | 031bc219cae30336e1f05b6fd996cf107bbf5cbb /app/assets | |
parent | 85bc1764095bf6f8d2557837b8e3dea9a2149e55 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
15 files changed, 460 insertions, 72 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', |