Skip to content

Commit

Permalink
✨ [WIP] Implement the Task Manager drawer
Browse files Browse the repository at this point in the history
Resolves: konveyor#1938

Add a queued tasks count badge plus and item drawer.

  - Use the standard `NotificationDrawer` attached to
    the `Page` layout to render the task manager item
    drawer.

  - Add a `TaskManagerContext` to control the count indicator
    and visibility of the drawer.  This is a top level context
    so the task manager is available on all pages.

  - Add `TaskQueue` and query for the notification badge

__Still Needs__:
  - Task rows in the notification drawer
  - Infinite scroll on the task list (or at least a load more link/icon,
    maybe a visual indicator that more can be fetched on scroll or click)
  - Individual task actions

Related changes:
  - Update the `HeaderApp` to handle visibility of masthead
    toolbar items at the `ToolbarGroup` level.

  - Rename `SSOMenu` to `SsoToolbarItem`.

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 committed Jun 17, 2024
1 parent f66bee0 commit 111d3a9
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 136 deletions.
9 changes: 6 additions & 3 deletions client/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import { BrowserRouter } from "react-router-dom";
import { AppRoutes } from "./Routes";
import { DefaultLayout } from "./layout";
import { NotificationsProvider } from "./components/NotificationsContext";
import { TaskManagerProvider } from "./components/task-manager/TaskManagerContext";

import "./app.css";

const App: React.FC = () => {
return (
<BrowserRouter>
<NotificationsProvider>
<DefaultLayout>
<AppRoutes />
</DefaultLayout>
<TaskManagerProvider>
<DefaultLayout>
<AppRoutes />
</DefaultLayout>
</TaskManagerProvider>
</NotificationsProvider>
</BrowserRouter>
);
Expand Down
13 changes: 13 additions & 0 deletions client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,19 @@ export interface Taskgroup {
tasks: TaskgroupTask[];
}

export interface TaskQueue {
/** Total number of tasks scheduled */
total: number;
/** number of tasks ready to run */
ready: number;
/** number of postponed tasks */
postponed: number;
/** number of tasks with pods created awaiting node scheduler */
pending: number;
/** number of tasks with running pods */
running: number;
}

export interface Cache {
path: string;
capacity: string;
Expand Down
44 changes: 25 additions & 19 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
import axios, { AxiosPromise, RawAxiosRequestHeaders } from "axios";

import {
AnalysisAppDependency,
AnalysisAppReport,
AnalysisDependency,
BaseAnalysisRuleReport,
BaseAnalysisIssueReport,
AnalysisIssue,
AnalysisFileReport,
AnalysisIncident,
AnalysisIssue,
Application,
ApplicationAdoptionPlan,
ApplicationDependency,
ApplicationImport,
ApplicationImportSummary,
Archetype,
Assessment,
BaseAnalysisIssueReport,
BaseAnalysisRuleReport,
BusinessService,
Cache,
HubFile,
HubPaginatedResult,
HubRequestParams,
Identity,
InitialAssessment,
IReadFile,
Tracker,
JobFunction,
MigrationWave,
MimeType,
New,
Proxy,
Questionnaire,
Ref,
Review,
Setting,
SettingTypes,
Stakeholder,
StakeholderGroup,
Tag,
TagCategory,
Target,
Task,
Taskgroup,
MigrationWave,
TaskQueue,
Ticket,
New,
Ref,
Tracker,
TrackerProject,
TrackerProjectIssuetype,
UnstructuredFact,
AnalysisAppDependency,
AnalysisAppReport,
Target,
HubFile,
Questionnaire,
Archetype,
InitialAssessment,
MimeType,
} from "./models";
import { serializeRequestParamsForHub } from "@app/hooks/table-controls";

Expand Down Expand Up @@ -321,7 +322,7 @@ export const getApplicationImports = (

export function getTaskById(
id: number,
format: string,
format: "json" | "yaml",
merged: boolean = false
): Promise<Task | string> {
const headers = format === "yaml" ? { ...yamlHeaders } : { ...jsonHeaders };
Expand All @@ -333,7 +334,7 @@ export function getTaskById(
}

return axios
.get(url, {
.get<Task | string>(url, {
headers: headers,
responseType: responseType,
})
Expand All @@ -348,10 +349,15 @@ export const getTasks = () =>
export const getServerTasks = (params: HubRequestParams = {}) =>
getHubPaginatedResult<Task>(TASKS, params);

export const deleteTask = (id: number) => axios.delete<Task>(`${TASKS}/${id}`);
export const deleteTask = (id: number) => axios.delete<void>(`${TASKS}/${id}`);

export const cancelTask = (id: number) =>
axios.put<Task>(`${TASKS}/${id}/cancel`);
axios.put<void>(`${TASKS}/${id}/cancel`);

export const getTaskQueue = (addon?: string): Promise<TaskQueue> =>
axios
.get<TaskQueue>(`${TASKS}/report/queue`, { params: { addon } })
.then(({ data }) => data);

export const createTaskgroup = (obj: Taskgroup) =>
axios.post<Taskgroup>(TASKGROUPS, obj).then((response) => response.data);
Expand Down
45 changes: 45 additions & 0 deletions client/src/app/components/task-manager/TaskManagerContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useFetchTaskQueue } from "@app/queries/tasks";
import React, { useContext, useState } from "react";

interface TaskManagerContextProps {
/** Count of the currently "queued" Tasks */
queuedCount: number;

/** Is the task manager drawer currently visible? */
isExpanded: boolean;

/** Control if the task manager drawer is visible */
setIsExpanded: (value: boolean) => void;
}

const TaskManagerContext = React.createContext<TaskManagerContextProps>({
queuedCount: 0,

isExpanded: false,
setIsExpanded: () => undefined,
});

export const useTaskManagerContext = () => {
const values = useContext(TaskManagerContext);

return values;
};

export const TaskManagerProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const { taskQueue } = useFetchTaskQueue();
const [isExpanded, setIsExpanded] = useState(false);

return (
<TaskManagerContext.Provider
value={{
queuedCount: taskQueue.total,
isExpanded,
setIsExpanded,
}}
>
{children}
</TaskManagerContext.Provider>
);
};
40 changes: 40 additions & 0 deletions client/src/app/components/task-manager/TaskManagerDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { forwardRef } from "react";
import { Link } from "react-router-dom";
import {
NotificationDrawer,
NotificationDrawerBody,
NotificationDrawerHeader,
NotificationDrawerList,
} from "@patternfly/react-core";
import { useTaskManagerContext } from "./TaskManagerContext";

interface TaskManagerDrawerProps {
ref?: React.ForwardedRef<HTMLElement>;
}

export const TaskManagerDrawer: React.FC<TaskManagerDrawerProps> = forwardRef(
(_props, ref) => {
const { isExpanded, setIsExpanded, queuedCount } = useTaskManagerContext();

const closeDrawer = () => {
setIsExpanded(!isExpanded);
};

return (
<NotificationDrawer ref={ref}>
<NotificationDrawerHeader
title="Task Manager"
customText={`${queuedCount} queued`}
onClose={closeDrawer}
>
<Link to="/tasks">View All Tasks</Link>
</NotificationDrawerHeader>
<NotificationDrawerBody>
<NotificationDrawerList></NotificationDrawerList>
</NotificationDrawerBody>
</NotificationDrawer>
);
}
);

TaskManagerDrawer.displayName = "TaskManagerDrawer";
22 changes: 22 additions & 0 deletions client/src/app/components/task-manager/TaskNotificaitonBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";
import { NotificationBadge } from "@patternfly/react-core";
import { useTaskManagerContext } from "./TaskManagerContext";

export const TaskNotificationBadge: React.FC = () => {
const { isExpanded, setIsExpanded, queuedCount } = useTaskManagerContext();

const badgeClick = () => {
setIsExpanded(!isExpanded);
};

return (
<NotificationBadge
id="task-notification-badge"
aria-label="Count of queued tasks"
variant={queuedCount > 0 ? "unread" : "read"}
count={queuedCount}
onClick={badgeClick}
isExpanded={isExpanded}
/>
);
};
21 changes: 20 additions & 1 deletion client/src/app/layout/DefaultLayout/DefaultLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from "react";
import React, { useRef } from "react";
import { Page, SkipToContent } from "@patternfly/react-core";

import { HeaderApp } from "../HeaderApp";
import { SidebarApp } from "../SidebarApp";
import { Notifications } from "@app/components/Notifications";
import { PageContentWithDrawerProvider } from "@app/components/PageDrawerContext";
import { TaskManagerDrawer } from "@app/components/task-manager/TaskManagerDrawer";
import { useTaskManagerContext } from "@app/components/task-manager/TaskManagerContext";

export interface DefaultLayoutProps {}

Expand All @@ -14,13 +16,30 @@ export const DefaultLayout: React.FC<DefaultLayoutProps> = ({ children }) => {
<SkipToContent href={`#${pageId}`}>Skip to content</SkipToContent>
);

const drawerRef = useRef<HTMLElement | null>(null);
const focusDrawer = () => {
if (drawerRef.current === null) {
return;
}
const firstTabbableItem = drawerRef.current.querySelector("a, button") as
| HTMLAnchorElement
| HTMLButtonElement
| null;
firstTabbableItem?.focus();
};

const { isExpanded } = useTaskManagerContext();

return (
<Page
header={<HeaderApp />}
sidebar={<SidebarApp />}
isManagedSidebar
skipToContent={PageSkipToContent}
mainContainerId={pageId}
isNotificationDrawerExpanded={isExpanded}
notificationDrawer={<TaskManagerDrawer ref={drawerRef} />}
onNotificationDrawerExpand={() => focusDrawer()}
>
<PageContentWithDrawerProvider>
{children}
Expand Down
43 changes: 35 additions & 8 deletions client/src/app/layout/HeaderApp/HeaderApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon";
import BarsIcon from "@patternfly/react-icons/dist/js/icons/bars-icon";

import useBranding from "@app/hooks/useBranding";
import { TaskNotificationBadge } from "@app/components/task-manager/TaskNotificaitonBadge";
import { AppAboutModalState } from "../AppAboutModalState";
import { SSOMenu } from "./SSOMenu";
import { SsoToolbarItem } from "./SsoToolbarItem";
import { MobileDropdown } from "./MobileDropdown";

import "./header.css";
Expand All @@ -33,9 +34,21 @@ export const HeaderApp: React.FC = () => {
const toolbar = (
<Toolbar isFullHeight isStatic>
<ToolbarContent>
{/* toolbar items to always show */}
<ToolbarGroup
id="header-toolbar-tasks"
variant="icon-button-group"
align={{ default: "alignRight" }}
>
<ToolbarItem>
<TaskNotificationBadge />
</ToolbarItem>
</ToolbarGroup>

{/* toolbar items to show at desktop sizes */}
<ToolbarGroup
id="header-toolbar-desktop"
variant="icon-button-group"
spacer={{ default: "spacerNone", md: "spacerMd" }}
visibility={{
default: "hidden",
Expand All @@ -62,16 +75,30 @@ export const HeaderApp: React.FC = () => {
</AppAboutModalState>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem
visibility={{
lg: "hidden",
}} /** this kebab dropdown replaces the icon buttons and is hidden for desktop sizes */
>

{/* toolbar items to show at mobile sizes */}
<ToolbarGroup
id="header-toolbar-mobile"
variant="icon-button-group"
spacer={{ default: "spacerNone", md: "spacerMd" }}
visibility={{ lg: "hidden" }}
>
<ToolbarItem>
<MobileDropdown />
</ToolbarItem>
<SSOMenu />
</ToolbarGroup>

{/* Show the SSO menu at desktop sizes */}
<ToolbarGroup
id="header-toolbar-sso"
visibility={{
default: "hidden",
md: "visible",
}}
>
<SsoToolbarItem />
</ToolbarGroup>

{rightBrand ? (
<ToolbarGroup>
<ToolbarItem>
Expand Down
Loading

0 comments on commit 111d3a9

Please sign in to comment.