Skip to content

Commit

Permalink
feat(UI-1239): sessions statuses count in dashboard projects table
Browse files Browse the repository at this point in the history
  • Loading branch information
RonenMars committed Jan 23, 2025
1 parent 279fab2 commit 0330fe7
Show file tree
Hide file tree
Showing 21 changed files with 182 additions and 155 deletions.
8 changes: 2 additions & 6 deletions src/components/molecules/idCopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LoggerService } from "@services/logger.service";
import { namespaces } from "@src/constants";
import { ButtonVariant } from "@src/enums/components";
import { useToastStore } from "@src/store";
import { getShortId } from "@src/utilities";

import { Button } from "@components/atoms";
import { CopyButton } from "@components/molecules/copyButton";
Expand Down Expand Up @@ -38,16 +39,11 @@ export const IdCopyButton = ({
);
}

const idPrefix = id.split("_")[0];
const idSuffix = id.split("_")[1];
const idSuffixEnd = idSuffix.substring(idSuffix.length - 3, idSuffix.length);
const idStr = `${idPrefix}...${idSuffixEnd}`;

return (
<div className="flex flex-row items-center">
{hideId ? null : (
<div className="mr-1 flex cursor-pointer items-center gap-2.5 text-center text-white">
{displayFullLength ? id : idStr}
{displayFullLength ? id : getShortId(id, 3)}
</div>
)}
<CopyButton className="mb-0.5 p-0" successMessage={successMessage} tabIndex={0} text={id} title={id} />
Expand Down
13 changes: 2 additions & 11 deletions src/components/organisms/dashboard/projectsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,11 @@ export const DashboardProjectsTable = () => {
}
});

const stats = {
totalDeployments,
sessionCounts: {
running: sessionStats[SessionStateType.running]?.count || 0,
stopped: sessionStats[SessionStateType.stopped]?.count || 0,
completed: sessionStats[SessionStateType.completed]?.count || 0,
error: sessionStats[SessionStateType.error]?.count || 0,
},
};
projectsStats[project.id] = {
id: project.id,
name: project.name,
totalDeployments: stats.totalDeployments,
...stats.sessionCounts,
totalDeployments,
...sessionStats,
status: projectStatus,
lastDeployed,
};
Expand Down
10 changes: 5 additions & 5 deletions src/components/organisms/deployments/sessions/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { DeleteSessionModal } from "@components/organisms/deployments/sessions/deleteModal";
export { SessionsTableFilter } from "@components/organisms/deployments/sessions/filter";
export { SessionsTableState } from "@components/organisms/deployments/sessions/state";
export { SessionsTable } from "@components/organisms/deployments/sessions/table";
export { SessionsTableList } from "@components/organisms/deployments/sessions/tableList";
export { SessionsTableRow } from "@components/organisms/deployments/sessions/tableRow";
export { SessionsTableFilter } from "@components/organisms/deployments/sessions/table/filters/filterBySessionState";
export { SessionsTableState } from "@components/organisms/deployments/sessions/table/state";
export { SessionsTable } from "@components/organisms/deployments/sessions/table/table";
export { SessionsTableList } from "@components/organisms/deployments/sessions/table/tableList";
export { SessionsTableRow } from "@components/organisms/deployments/sessions/table/tableRow";
export { SessionViewer } from "@components/organisms/deployments/sessions/viewer";
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";

import { useTranslation } from "react-i18next";

import { getShortId } from "@src/utilities";

export const FilterSessionsByEntityPopoverItem = ({
entityId,
totalSessions,
translationKey,
}: {
entityId: string;
totalSessions: number;
translationKey: string;
}) => {
const { t } = useTranslation("deployments", { keyPrefix: "sessions" });

return (
<div className="flex w-full flex-row justify-between">
<div>
{t(translationKey, {
deploymentId: getShortId(entityId, 7),
})}
</div>
<div className="ml-2">({totalSessions})</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React from "react";

import { useTranslation } from "react-i18next";

Expand All @@ -11,7 +11,7 @@ import { DropdownButton } from "@components/molecules";

import { FilterIcon } from "@assets/image/icons";

export const SessionsTableFilter = ({ onChange, sessionStats, selectedState }: SessionTableFilterProps) => {
export const SessionsTableFilter = ({ onChange, filtersData, selectedState }: SessionTableFilterProps) => {
const { t } = useTranslation("deployments", { keyPrefix: "sessions.table.statuses" });

const validatedState: SessionStateType | undefined =
Expand All @@ -29,73 +29,50 @@ export const SessionsTableFilter = ({ onChange, sessionStats, selectedState }: S
const filterClass = (state?: SessionStateType) =>
cn("h-8 whitespace-nowrap border-0 pr-4 text-white hover:bg-transparent", state && getSessionStateColor(state));

const initialSessionCounts = {
[SessionStateType.completed]: 0,
[SessionStateType.created]: 0,
[SessionStateType.error]: 0,
[SessionStateType.running]: 0,
[SessionStateType.stopped]: 0,
};

const sessionCounts = useMemo(
() =>
sessionStats.reduce(
(acc, stat) => {
acc[stat.state!] = stat.count;
acc.total += stat.count;

return acc;
},
{ total: 0, ...initialSessionCounts }
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[sessionStats]
);

return (
<div className="flex items-center">
<DropdownButton
contentMenu={
<div className="flex flex-col gap-y-2">
<Button className={buttonClass()} onClick={() => onChange()}>
{t("all")} ({sessionCounts.total})
{t("all")} ({filtersData.totalSessionsCount})
</Button>

<Button
className={buttonClass(SessionStateType.running)}
onClick={() => onChange(SessionStateType.running)}
>
{t("running")} ({sessionCounts.running})
{t("running")} ({filtersData.sessionStats.running})
</Button>

<Button
className={buttonClass(SessionStateType.stopped)}
onClick={() => onChange(SessionStateType.stopped)}
>
{t("stopped")} ({sessionCounts.stopped})
{t("stopped")} ({filtersData.sessionStats.stopped})
</Button>

<Button
className={buttonClass(SessionStateType.error)}
onClick={() => onChange(SessionStateType.error)}
>
{t("error")} ({sessionCounts.error})
{t("error")} ({filtersData.sessionStats.error})
</Button>

<Button
className={buttonClass(SessionStateType.completed)}
onClick={() => onChange(SessionStateType.completed)}
>
{t("completed")} ({sessionCounts.completed})
{t("completed")} ({filtersData.sessionStats.completed})
</Button>
</div>
}
>
<Button className={filterClass(validatedState)} variant="outline">
<IconSvg className="mb-1 text-white" size="md" src={FilterIcon} />
{validatedState
? `${t(validatedState)} (${sessionCounts[validatedState]})`
: `${t("all")} (${sessionCounts.total})`}
? `${t(validatedState)} (${filtersData.sessionStats[validatedState]})`
: `${t("all")} (${filtersData.totalSessionsCount})`}
</Button>
</DropdownButton>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SessionsTableFilter } from "@components/organisms/deployments/sessions/table/filters/filterBySessionState";
export { FilterSessionsByEntityPopoverItem } from "@components/organisms/deployments/sessions/table/filters/filterByEntityPopoverItem";
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import { useResize } from "@src/hooks";
import { PopoverListItem } from "@src/interfaces/components/popover.interface";
import { Session, SessionStateKeyType } from "@src/interfaces/models";
import { useCacheStore, useModalStore, useToastStore } from "@src/store";
import { calculateDeploymentSessionsStats } from "@src/utilities";
import { DeploymentSession } from "@type/models";
import { SessionStatsFilterType } from "@src/types/components";
import { calculateDeploymentSessionsStats, getShortId, initialSessionCounts } from "@src/utilities";

import { Frame, IconSvg, Loader, ResizeButton, THead, Table, Th, Tr } from "@components/atoms";
import { RefreshButton } from "@components/molecules";
import { PopoverList, PopoverListContent, PopoverListTrigger } from "@components/molecules/popover/index";
import { SessionsTableFilter } from "@components/organisms/deployments";
import { DeleteSessionModal, SessionsTableList } from "@components/organisms/deployments/sessions";
import { FilterSessionsByEntityPopoverItem } from "@components/organisms/deployments/sessions/table/filters";

import { CatImage } from "@assets/image";
import { FilterIcon } from "@assets/image/icons";
Expand All @@ -42,11 +43,14 @@ export const SessionsTable = () => {
const [sessions, setSessions] = useState<Session[]>([]);
const [selectedSessionId, setSelectedSessionId] = useState<string>();
const [sessionsNextPageToken, setSessionsNextPageToken] = useState<string>();
const [sessionStats, setSessionStats] = useState<DeploymentSession[]>([]);
const [sessionStats, setSessionStats] = useState<SessionStatsFilterType>({
sessionStats: initialSessionCounts,
totalDeployments: 0,
totalSessionsCount: 0,
});
const [isLoading, setIsLoading] = useState(false);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const { fetchDeployments: reloadDeploymentsCache } = useCacheStore();
const [filterValue, setFilterValue] = useState<string>("");
const [popoverDeploymentItems, setPopoverDeploymentItems] = useState<Array<PopoverListItem>>([]);
const frameClass = "size-full bg-gray-1100 pb-3 pl-7 transition-all rounded-r-none";
const filteredEntityId = deploymentId || projectId!;
Expand All @@ -58,19 +62,6 @@ export const SessionsTable = () => {
return sessionState ? reverseSessionStateConverter(sessionState as SessionStateKeyType) : undefined;
}, [searchParams]);

const getShortId = (id: string, suffixLength: number) => {
if (!id?.length) return "";
const idPrefix = id.split("_")[0];
const idSuffix = id.split("_")[1];
const isValidId = idPrefix?.length > 0 && idSuffix?.length >= suffixLength;
let shortId = id;
if (isValidId) {
const idSuffixEnd = idSuffix.substring(idSuffix.length - suffixLength, idSuffix.length);
shortId = `${idPrefix}...${idSuffixEnd}`;
}
return shortId;
};

const filterSessionsByState = (stateType?: SessionStateKeyType) =>
!stateType
? navigate(getURLPathForAllSessionsInEntity(filteredEntityId))
Expand All @@ -82,31 +73,57 @@ export const SessionsTable = () => {
const fetchedDeployments = await reloadDeploymentsCache(projectId, true);

const formattedDeployments =
fetchedDeployments?.map(({ deploymentId }) => {
fetchedDeployments?.map(({ deploymentId, sessionStats }) => {
const totalSessions = sessionStats?.reduce((acc, curr) => acc + (curr.count || 0), 0) || 0;
return {
id: deploymentId,
label: `Deployment: ${getShortId(deploymentId, 7)}`,
label: () => (
<FilterSessionsByEntityPopoverItem
entityId={deploymentId}
totalSessions={totalSessions}
translationKey="table.filters.byDeploymentId"
/>
),
};
}) || [];

setPopoverDeploymentItems([{ id: projectId, label: "All sessions" }, ...formattedDeployments]);
const {
sessionStats: sessionsCountByState,
totalDeployments,
totalSessionsCount,
} = calculateDeploymentSessionsStats(fetchedDeployments || []);

const allSessionsInProject = () => (
<FilterSessionsByEntityPopoverItem
entityId={projectId}
totalSessions={totalDeployments}
translationKey="table.filters.all"
/>
);

setPopoverDeploymentItems([
{ id: projectId, label: allSessionsInProject() },
...formattedDeployments.map((deployment) => ({ ...deployment, label: deployment.label() })),
]);

if (deploymentId) {
setFilterValue(deploymentId);
const deployment = fetchedDeployments?.find((d) => d.deploymentId === deploymentId);
if (!deployment?.sessionStats) return;
if (isEqual(deployment.sessionStats, sessionStats)) return;
setSessionStats(deployment.sessionStats);
if (!deployment) return;
const deploymentStats = calculateDeploymentSessionsStats([deployment]);
if (isEqual(deploymentStats.sessionStats, sessionStats.sessionStats)) return;
setSessionStats(deploymentStats);
return fetchedDeployments;
}
setFilterValue(projectId);

const { sessionStats: allSessionStats } = calculateDeploymentSessionsStats(fetchedDeployments || []);
const aggregatedStats = Object.values(allSessionStats);
if (!aggregatedStats.length || isEqual(aggregatedStats, sessionStats)) return;
setSessionStats(aggregatedStats as DeploymentSession[]);
if (isEqual(sessionsCountByState, sessionStats)) return;
setSessionStats({
sessionStats: sessionsCountByState,
totalDeployments,
totalSessionsCount,
});
return fetchedDeployments;
}, [projectId, deploymentId, reloadDeploymentsCache, sessionStats]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId, deploymentId, reloadDeploymentsCache]);

const fetchSessions = useCallback(
async (nextPageToken?: string, forceRefresh = false) => {
Expand Down Expand Up @@ -261,11 +278,6 @@ export const SessionsTable = () => {
: `/projects/${projectId}/deployments/${entityId}/sessions`;

const filterSessionsByEntity = (filterEntityId: string) => {
setFilterValue(filterEntityId);
if (filterEntityId === projectId) {
setFilterValue(projectId);
}

if (searchParams.has("sessionState")) {
searchParams.delete("sessionState");
setSearchParams(searchParams);
Expand All @@ -287,8 +299,10 @@ export const SessionsTable = () => {
</div>
<div className="text-base">
{deploymentId
? `Deployment ID: ${getShortId(filterValue, 7)}`
: "All sessions"}
? t("table.filters.byDeploymentId", {
deploymentId: getShortId(deploymentId, 7),
})
: t("table.filters.all")}
</div>
</div>
</PopoverListTrigger>
Expand All @@ -304,9 +318,9 @@ export const SessionsTable = () => {
</div>
<div className="ml-auto flex items-center">
<SessionsTableFilter
filtersData={sessionStats}
onChange={(sessionState) => filterSessionsByState(sessionState)}
selectedState={searchParams.get("sessionState") as SessionStateType}
sessionStats={sessionStats}
/>
<RefreshButton isLoading={isLoading} onRefresh={() => refreshData()} />
</div>
Expand Down
Loading

0 comments on commit 0330fe7

Please sign in to comment.