fix(apps): group helm apps together [r8s-102] (#24)

pull/12346/head
Ali 2024-10-14 10:28:56 +13:00 committed by GitHub
parent 1110f745e1
commit 57e10dc911
8 changed files with 95 additions and 17 deletions

View File

@ -31,9 +31,7 @@ import {
KubernetesPodNodeAffinityPayload,
KubernetesPreferredSchedulingTermPayload,
} from 'Kubernetes/pod/payloads/affinities';
export const PodKubernetesInstanceLabel = 'app.kubernetes.io/instance';
export const PodManagedByLabel = 'app.kubernetes.io/managed-by';
import { PodKubernetesInstanceLabel, PodManagedByLabel } from '@/react/kubernetes/applications/constants';
class KubernetesApplicationHelper {
/* #region UTILITY FUNCTIONS */

View File

@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { BoxIcon } from 'lucide-react';
import { groupBy, partition } from 'lodash';
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
@ -10,6 +11,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
import { useAuthorizations } from '@/react/hooks/useUser';
import { isSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace';
import { KubernetesApplicationTypes } from '@/kubernetes/models/application/models/appConstants';
import { TableSettingsMenu } from '@@/datatables';
import { useRepeater } from '@@/datatables/useRepeater';
@ -20,8 +22,9 @@ import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
import { NamespaceFilter } from '../ApplicationsStacksDatatable/NamespaceFilter';
import { Namespace } from '../ApplicationsStacksDatatable/types';
import { useApplications } from '../../application.queries';
import { PodKubernetesInstanceLabel, PodManagedByLabel } from '../../constants';
import { Application, ConfigKind } from './types';
import { Application, ApplicationRowData, ConfigKind } from './types';
import { useColumns } from './useColumns';
import { getPublishedUrls } from './PublishedPorts';
import { SubRow } from './SubRow';
@ -70,7 +73,7 @@ export function ApplicationsDatatable({
namespace,
withDependencies: true,
});
const applications = applicationsQuery.data ?? [];
const applications = useApplicationsRowData(applicationsQuery.data);
const filteredApplications = showSystem
? applications
: applications.filter(
@ -156,7 +159,74 @@ export function ApplicationsDatatable({
);
}
function isExpandable(item: Application) {
function useApplicationsRowData(
applications?: Application[]
): ApplicationRowData[] {
return useMemo(() => separateHelmApps(applications ?? []), [applications]);
}
function separateHelmApps(applications: Application[]): ApplicationRowData[] {
const [helmApps, nonHelmApps] = partition(
applications,
(app) =>
app.Metadata?.labels &&
app.Metadata.labels[PodKubernetesInstanceLabel] &&
app.Metadata.labels[PodManagedByLabel] === 'Helm'
);
const groupedHelmApps: Record<string, Application[]> = groupBy(
helmApps,
(app) => app.Metadata?.labels[PodKubernetesInstanceLabel] ?? ''
);
// build the helm apps row data from the grouped helm apps
const helmAppsRowData = Object.entries(groupedHelmApps).reduce<
ApplicationRowData[]
>((helmApps, [appName, apps]) => {
const helmApp = buildHelmAppRowData(appName, apps);
return [...helmApps, helmApp];
}, []);
return [...helmAppsRowData, ...nonHelmApps];
}
function buildHelmAppRowData(
appName: string,
apps: Application[]
): ApplicationRowData {
const id = `${apps[0].ResourcePool}-${appName
.toLowerCase()
.replaceAll(' ', '-')}`;
const { earliestCreationDate, runningPods, totalPods } = apps.reduce(
(acc, app) => ({
earliestCreationDate:
new Date(app.CreationDate) < new Date(acc.earliestCreationDate)
? app.CreationDate
: acc.earliestCreationDate,
runningPods: acc.runningPods + app.RunningPodsCount,
totalPods: acc.totalPods + app.TotalPodsCount,
}),
{
earliestCreationDate: apps[0].CreationDate,
runningPods: 0,
totalPods: 0,
}
);
const helmApp: ApplicationRowData = {
...apps[0],
Name: appName,
Id: id,
KubernetesApplications: apps,
ApplicationType: KubernetesApplicationTypes.Helm,
Status: runningPods < totalPods ? 'Not ready' : 'Ready',
CreationDate: earliestCreationDate,
RunningPodsCount: runningPods,
TotalPodsCount: totalPods,
};
return helmApp;
}
function isExpandable(item: ApplicationRowData) {
return (
!!item.KubernetesApplications ||
!!getPublishedUrls(item).length ||

View File

@ -5,25 +5,26 @@ import { useCurrentUser } from '@/react/hooks/useUser';
import { ConfigurationDetails } from './ConfigurationDetails';
import { InnerTable } from './InnerTable';
import { PublishedPorts } from './PublishedPorts';
import { Application } from './types';
import { ApplicationRowData } from './types';
export function SubRow({
item,
hideStacks,
areSecretsRestricted,
}: {
item: Application;
item: ApplicationRowData;
hideStacks: boolean;
areSecretsRestricted: boolean;
}) {
const {
user: { Username: username },
} = useCurrentUser();
const colSpan = hideStacks ? 8 : 9;
return (
<tr className={clsx({ 'secondary-body': !item.KubernetesApplications })}>
<td />
<td colSpan={8} className="datatable-padding-vertical">
<td colSpan={colSpan} className="datatable-padding-vertical">
{item.KubernetesApplications ? (
<InnerTable
dataset={item.KubernetesApplications}

View File

@ -1,5 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Application } from './types';
import { ApplicationRowData } from './types';
export const helper = createColumnHelper<Application>();
export const helper = createColumnHelper<ApplicationRowData>();

View File

@ -7,14 +7,16 @@ import { SystemBadge } from '@@/Badge/SystemBadge';
import { ExternalBadge } from '@@/Badge/ExternalBadge';
import { helper } from './columns.helper';
import { Application } from './types';
import { ApplicationRowData } from './types';
export const name = helper.accessor('Name', {
header: 'Name',
cell: Cell,
});
function Cell({ row: { original: item } }: CellContext<Application, string>) {
function Cell({
row: { original: item },
}: CellContext<ApplicationRowData, string>) {
const isSystem = useIsSystemNamespace(item.ResourcePool);
return (

View File

@ -8,7 +8,7 @@ import {
import styles from './columns.status.module.css';
import { helper } from './columns.helper';
import { Application } from './types';
import { ApplicationRowData } from './types';
export const status = helper.accessor('Status', {
header: 'Status',
@ -16,7 +16,9 @@ export const status = helper.accessor('Status', {
enableSorting: false,
});
function Cell({ row: { original: item } }: CellContext<Application, string>) {
function Cell({
row: { original: item },
}: CellContext<ApplicationRowData, string>) {
if (
item.ApplicationType === KubernetesApplicationTypes.Pod &&
item.Pods &&

View File

@ -1,5 +1,9 @@
import { AppType, DeploymentType } from '../../types';
export interface ApplicationRowData extends Application {
KubernetesApplications?: Array<Application>;
}
export interface Application {
Id: string;
Name: string;
@ -11,7 +15,6 @@ export interface Application {
StackName?: string;
ResourcePool: string;
ApplicationType: AppType;
KubernetesApplications?: Array<Application>;
Metadata?: {
labels: Record<string, string>;
};

View File

@ -8,6 +8,8 @@ export const appNoteAnnotation = 'io.portainer.kubernetes.application.note';
export const appDeployMethodLabel = 'io.portainer.kubernetes.application.kind';
export const defaultDeploymentUniqueLabel = 'pod-template-hash';
export const appNameLabel = 'io.portainer.kubernetes.application.name';
export const PodKubernetesInstanceLabel = 'app.kubernetes.io/instance';
export const PodManagedByLabel = 'app.kubernetes.io/managed-by';
export const appRevisionAnnotation = 'deployment.kubernetes.io/revision';