Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions src/components/ControlPlane/FluxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { FluxRequest } from '../../lib/api/types/flux/listGitRepo';
import { FluxKustomization, KustomizationsResponse } from '../../lib/api/types/flux/listKustomization';
import { useTranslation } from 'react-i18next';
import { timeAgo } from '../../utils/i18n/timeAgo.ts';
import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';

import { YamlViewButton } from '../Yaml/YamlViewButton.tsx';
import { useMemo } from 'react';
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
import { ResourceStatusCellWithButton } from '../Shared/ResourceStatusCellWithButton.tsx';

export default function FluxList() {
const { data: gitReposData, error: repoErr, isLoading: repoIsLoading } = useApiResource(FluxRequest); //404 if component not enabled
Expand All @@ -36,6 +37,7 @@ export default function FluxList() {
isReady: boolean;
statusUpdateTime?: string;
item: unknown;
readyMessage: string;
};

const gitReposColumns: AnalyticalTableColumnDefinition[] = useMemo(
Expand All @@ -56,23 +58,26 @@ export default function FluxList() {
{
Header: t('FluxList.tableStatusHeader'),
accessor: 'status',
width: 85,
width: 125,
hAlign: 'Center',
Filter: ({ column }) => <StatusFilter column={column} />,
Cell: (cellData: CellData<FluxRow['isReady']>) =>
Cell: (cellData: CellData<FluxRow>) =>
cellData.cell.row.original?.isReady != null ? (
<ResourceStatusCell
<ResourceStatusCellWithButton
positiveText={'Ready'}
negativeText={'Error'}
value={cellData.cell.row.original?.isReady}
transitionTime={
cellData.cell.row.original?.statusUpdateTime ? cellData.cell.row.original?.statusUpdateTime : ''
}
message={cellData.cell.row.original?.readyMessage}
/>
) : null,
},
{
Header: t('yaml.YAML'),
hAlign: 'Center',
width: 85,
width: 75,
accessor: 'yaml',
disableFilters: true,
Cell: (cellData: CellData<KustomizationsResponse['items']>) => (
Expand All @@ -97,24 +102,27 @@ export default function FluxList() {
{
Header: t('FluxList.tableStatusHeader'),
accessor: 'status',
width: 85,
width: 125,
hAlign: 'Center',
Filter: ({ column }) => <StatusFilter column={column} />,
Cell: (cellData: CellData<FluxRow['isReady']>) =>
cellData.cell.row.original?.isReady != null ? (
<ResourceStatusCell
<ResourceStatusCellWithButton
positiveText={'Ready'}
negativeText={'Error'}
value={cellData.cell.row.original?.isReady}
transitionTime={
cellData.cell.row.original?.statusUpdateTime ? cellData.cell.row.original?.statusUpdateTime : ''
}
message={cellData.cell.row.original?.readyMessage}
/>
) : null,
},

{
Header: t('yaml.YAML'),
hAlign: 'Center',
width: 85,
width: 75,
accessor: 'yaml',
disableFilters: true,
Cell: (cellData: CellData<FluxRow>) => <YamlViewButton resourceObject={cellData.cell.row.original?.item} />,
Expand All @@ -134,24 +142,28 @@ export default function FluxList() {

const gitReposRows: FluxRow[] =
gitReposData?.items?.map((item) => {
const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready');
return {
name: item.metadata.name,
isReady: item?.status?.conditions?.find((x) => x.type === 'Ready')?.status === 'True',
statusUpdateTime: item.status?.conditions?.find((x) => x.type === 'Ready')?.lastTransitionTime,
isReady: readyObject?.status === 'True',
statusUpdateTime: readyObject?.lastTransitionTime,
revision: shortenCommitHash(item.status.artifact?.revision ?? '-'),
created: timeAgo.format(new Date(item.metadata.creationTimestamp)),
item: item,
readyMessage: readyObject?.message ?? readyObject?.reason ?? '',
};
}) ?? [];

const kustomizationsRows: FluxRow[] =
kustmizationData?.items?.map((item) => {
const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready');
return {
name: item.metadata.name,
isReady: item.status?.conditions?.find((x) => x.type === 'Ready')?.status === 'True',
statusUpdateTime: item.status?.conditions?.find((x) => x.type === 'Ready')?.lastTransitionTime,
isReady: readyObject?.status === 'True',
statusUpdateTime: readyObject?.lastTransitionTime,
created: timeAgo.format(new Date(item.metadata.creationTimestamp)),
item: item,
readyMessage: readyObject?.message ?? readyObject?.reason ?? '',
};
}) ?? [];

Expand Down
125 changes: 64 additions & 61 deletions src/components/ControlPlane/MCPHealthPopoverButton.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import { AnalyticalTable, Icon, Popover, FlexBox, FlexBoxJustifyContent, Button } from '@ui5/webcomponents-react';
import {
AnalyticalTable,
Icon,
Popover,
FlexBox,
FlexBoxJustifyContent,
Button,
PopoverDomRef,
} from '@ui5/webcomponents-react';
import { AnalyticalTableColumnDefinition } from '@ui5/webcomponents-react/wrappers';
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
import '@ui5/webcomponents-icons/dist/copy';
import { JSX, useRef, useState } from 'react';
import { ControlPlaneStatusType, ReadyStatus } from '../../lib/api/types/crate/controlPlanes';
import { JSX, useRef, useState, MouseEvent, ReactNode } from 'react';
import {
ControlPlaneStatusType,
ReadyStatus,
ControlPlaneStatusCondition,
} from '../../lib/api/types/crate/controlPlanes';
import ReactTimeAgo from 'react-time-ago';
import { AnimatedHoverTextButton } from '../Helper/AnimatedHoverTextButton.tsx';
import { useTranslation } from 'react-i18next';
import { useLink } from '../../lib/shared/useLink.ts';
import TooltipCell from '../Shared/TooltipCell.tsx';
export default function MCPHealthPopoverButton({
mcpStatus,
projectName,
workspaceName,
mcpName,
}: {

interface CellData<T> {
cell: {
value: ReactNode;
};
row: {
original: T;
[key: string]: unknown;
};
[key: string]: unknown;
}

type MCPHealthPopoverButtonProps = {
mcpStatus: ControlPlaneStatusType | undefined;
projectName: string;
workspaceName: string;
mcpName: string;
}) {
const popoverRef = useRef(null);
};

const MCPHealthPopoverButton = ({ mcpStatus, projectName, workspaceName, mcpName }: MCPHealthPopoverButtonProps) => {
const popoverRef = useRef<PopoverDomRef>(null);
const [open, setOpen] = useState(false);
const { githubIssuesSupportTicket } = useLink();

const { t } = useTranslation();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleOpenerClick = (e: any) => {
const handleOpenerClick = (e: MouseEvent<HTMLButtonElement>) => {
if (popoverRef.current) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ref = popoverRef.current as any;
ref.opener = e.target;
(popoverRef.current as unknown as { opener: EventTarget | null }).opener = e.target;
setOpen((prev) => !prev);
}
};

const getTicketTitle = () => {
const getTicketTitle = (): string => {
switch (mcpStatus?.status) {
case ReadyStatus.Ready:
return t('MCPHealthPopoverButton.supportTicketTitleReady');
Expand All @@ -49,13 +66,13 @@ export default function MCPHealthPopoverButton({
}
};

const constructGithubIssuesLink = () => {
const constructGithubIssuesLink = (): string => {
const clusterDetails = `${projectName}/${workspaceName}/${mcpName}`;

const statusDetails = mcpStatus?.conditions
? `${t('MCPHealthPopoverButton.statusDetailsLabel')}: ${mcpStatus.status}\n\n${t('MCPHealthPopoverButton.detailsLabel')}\n` +
mcpStatus?.conditions
.map((condition) => {
mcpStatus.conditions
.map((condition: ControlPlaneStatusCondition) => {
let text = `- ${condition.type}: ${condition.status}\n`;
if (condition.reason) text += ` - ${t('MCPHealthPopoverButton.reasonHeader')}: ${condition.reason}\n`;
if (condition.message) text += ` - ${t('MCPHealthPopoverButton.messageHeader')}: ${condition.message}\n`;
Expand All @@ -79,8 +96,7 @@ export default function MCPHealthPopoverButton({
Header: t('MCPHealthPopoverButton.statusHeader'),
accessor: 'status',
width: 50,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => {
Cell: (instance: CellData<ControlPlaneStatusCondition>) => {
const isReady = instance.cell.value === 'True';
return (
<Icon
Expand All @@ -94,38 +110,33 @@ export default function MCPHealthPopoverButton({
Header: t('MCPHealthPopoverButton.typeHeader'),
accessor: 'type',
width: 150,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => {
Cell: (instance: CellData<ControlPlaneStatusCondition>) => {
return <TooltipCell>{instance.cell.value}</TooltipCell>;
},
},
{
Header: t('MCPHealthPopoverButton.messageHeader'),
accessor: 'message',
width: 350,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => {
Cell: (instance: CellData<ControlPlaneStatusCondition>) => {
return <TooltipCell>{instance.cell.value}</TooltipCell>;
},
},
{
Header: t('MCPHealthPopoverButton.reasonHeader'),
accessor: 'reason',
width: 100,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => {
Cell: (instance: CellData<ControlPlaneStatusCondition>) => {
return <TooltipCell>{instance.cell.value}</TooltipCell>;
},
},
{
Header: t('MCPHealthPopoverButton.transitionHeader'),
accessor: 'lastTransitionTime',
width: 110,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Cell: (instance: any) => {
width: 125,
Cell: (instance: CellData<ControlPlaneStatusCondition>) => {
const rawDate = instance.cell.value;
const date = new Date(rawDate);

const date = new Date(rawDate as string);
return (
<TooltipCell>
<ReactTimeAgo date={date} />
Expand All @@ -143,58 +154,50 @@ export default function MCPHealthPopoverButton({
onClick={handleOpenerClick}
/>
<Popover ref={popoverRef} open={open} placement={PopoverPlacement.Bottom}>
{
<StatusTable
status={mcpStatus}
tableColumns={statusTableColumns}
githubIssuesLink={constructGithubIssuesLink()}
/>
}
<StatusTable
status={mcpStatus}
tableColumns={statusTableColumns}
githubIssuesLink={constructGithubIssuesLink()}
/>
</Popover>
</div>
);
}
};

function StatusTable({
status,
tableColumns,
githubIssuesLink,
}: {
export default MCPHealthPopoverButton;

type StatusTableProps = {
status: ControlPlaneStatusType | undefined;
tableColumns: AnalyticalTableColumnDefinition[];
githubIssuesLink: string;
}) {
};

const StatusTable = ({ status, tableColumns, githubIssuesLink }: StatusTableProps) => {
const { t } = useTranslation();

const sortedConditions = status?.conditions ? [...status.conditions].sort((a, b) => (a.type < b.type ? -1 : 1)) : [];

return (
<div style={{ width: 770 }}>
<AnalyticalTable
scaleWidthMode="Default"
columns={tableColumns}
data={
status?.conditions?.sort((a, b) => {
return a.type < b.type ? -1 : 1;
}) ?? []
}
/>
<AnalyticalTable scaleWidthMode="Default" columns={tableColumns} data={sortedConditions} />
<FlexBox justifyContent={FlexBoxJustifyContent.End} style={{ marginTop: '0.5rem' }}>
<a href={githubIssuesLink} target="_blank" rel="noreferrer">
<Button>{t('MCPHealthPopoverButton.createSupportTicketButton')}</Button>
</a>
</FlexBox>
</div>
);
}
};

function getIconForOverallStatus(status: ReadyStatus | undefined): JSX.Element {
const getIconForOverallStatus = (status: ReadyStatus | undefined): JSX.Element => {
switch (status) {
case ReadyStatus.Ready:
return <Icon style={{ color: 'green' }} name="sap-icon://sys-enter" />;
case ReadyStatus.NotReady:
return <Icon style={{ color: 'red' }} name="sap-icon://pending" />;
case ReadyStatus.InDeletion:
return <Icon style={{ color: 'orange' }} name="sap-icon://delete" />;
case undefined:
default:
return <></>;
}
}
};
Loading
Loading