Skip to content

Commit

Permalink
Merge pull request #766 from open-rmf/port/hammer
Browse files Browse the repository at this point in the history
Port/hammer
  • Loading branch information
aaronchongth authored Sep 11, 2023
2 parents 05dcf73 + 26d635a commit c2a158f
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .github/actions/bootstrap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ runs:
steps:
- uses: pnpm/[email protected]
with:
version: '8.6.12'
version: 'latest'
- uses: actions/setup-node@v2
with:
node-version: '16'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ class PostScheduledTaskRequest(BaseModel):


def datetime_to_date_format(date: datetime) -> str:
return date.date().strftime("%m/%d/%Y").lstrip("0")
# input 09/08/2023
formatted_date = date.date().strftime("%m/%d/%Y").lstrip("0")
parts = formatted_date.split("/")
parts[1] = parts[1].lstrip("0")
# output 9/8/2023
return "/".join(parts)


async def schedule_task(task: ttm.ScheduledTask, task_repo: TaskRepository):
Expand Down
3 changes: 2 additions & 1 deletion packages/dashboard-e2e/wdio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ function browserstackOptions(opts) {
return {
projectName: 'rmf-web',
buildName: `dashboard-e2e:${process.env.BROWSERSTACK_BUILD || 'local'}`,
resolution: '1920x1080',
// Resolution higher than 1080p to prevent triggering low resolution alert.
resolution: '2560x1600',
localIdentifier,
...opts,
};
Expand Down
4 changes: 1 addition & 3 deletions packages/dashboard/src/components/alert-store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ enum AlertCategory {
export const AlertStore = React.memo(() => {
const rmf = React.useContext(RmfAppContext);
const [taskAlerts, setTaskAlerts] = React.useState<Record<string, Alert>>({});
const refreshAlertCount = React.useRef(0);

const categorizeAndPushAlerts = (alert: Alert) => {
// We check if an existing alert has been acknowledged, remove it before
Expand Down Expand Up @@ -51,8 +50,7 @@ export const AlertStore = React.memo(() => {
}
const sub = rmf.alertObsStore.subscribe(async (alert) => {
categorizeAndPushAlerts(alert);
refreshAlertCount.current += 1;
AppEvents.refreshAlertCount.next(refreshAlertCount.current);
AppEvents.refreshAlert.next();
});
return () => sub.unsubscribe();
}, [rmf]);
Expand Down
46 changes: 45 additions & 1 deletion packages/dashboard/src/components/app-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import React from 'react';
import { rmfDark, rmfDarkLeaflet, rmfLight } from 'react-components';
import { rmfDark, rmfDarkLeaflet, rmfLight, AlertDialog } from 'react-components';
import { loadSettings, saveSettings, Settings, ThemeMode } from '../settings';
import { AppController, AppControllerContext, SettingsContext } from './app-contexts';
import AppBar from './appbar';
Expand All @@ -34,6 +34,7 @@ const defaultTheme = createTheme({
export function AppBase({ children }: React.PropsWithChildren<{}>): JSX.Element | null {
const [settings, setSettings] = React.useState(() => loadSettings());
const [showAlert, setShowAlert] = React.useState(false);
const [lowResolutionAlert, setLowResolutionAlert] = React.useState(false);
const [alertSeverity, setAlertSeverity] = React.useState<AlertProps['severity']>('error');
const [alertMessage, setAlertMessage] = React.useState('');
const [alertDuration, setAlertDuration] = React.useState(DefaultAlertDuration);
Expand Down Expand Up @@ -69,13 +70,56 @@ export function AppBase({ children }: React.PropsWithChildren<{}>): JSX.Element
[updateSettings],
);

React.useEffect(() => {
const checkSize = () => {
if (window.innerHeight < 1080 || window.innerWidth < 1080) {
setLowResolutionAlert(true);
}
};
checkSize();
window.addEventListener('resize', checkSize);
}, []);

const dismissDisplayAlert = () => {
setLowResolutionAlert(false);
};

const lowResolutionDisplayAlert = () => {
return (
<AlertDialog
key="display-alert"
onDismiss={dismissDisplayAlert}
title="Low display resolution detected"
alertContents={[
{
title: 'Current resolution',
value: `${window.innerWidth} x ${window.innerHeight}`,
},
{
title: 'Minimum recommended resolution',
value: '1920 x 1080',
},
{
title: 'Message',
value:
'To ensure maximum compatibility, please reduce the zoom ' +
'level in the browser (Ctrl+Minus) or display settings, ' +
'or change to a higher resolution display device.',
},
]}
backgroundColor={theme.palette.background.default}
/>
);
};

return (
<ThemeProvider theme={theme}>
<CssBaseline />
{settings.themeMode === ThemeMode.RmfDark && <GlobalStyles styles={rmfDarkLeaflet} />}
<SettingsContext.Provider value={settings}>
<AppControllerContext.Provider value={appController}>
<AlertStore />
{lowResolutionAlert && lowResolutionDisplayAlert()}
<Grid
container
direction="column"
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard/src/components/app-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const AppEvents = {
ingestorSelect: new Subject<Ingestor | null>(),
robotSelect: new Subject<[fleetName: string, robotName: string] | null>(),
taskSelect: new Subject<TaskState | null>(),
refreshTaskAppCount: new Subject<number>(),
refreshAlertCount: new Subject<number>(),
refreshTaskApp: new Subject<void>(),
refreshAlert: new Subject<void>(),
alertListOpenedAlert: new Subject<Alert | null>(),
disabledLayers: new ReplaySubject<Record<string, boolean>>(),
zoom: new BehaviorSubject<number | null>(null),
Expand Down
39 changes: 21 additions & 18 deletions packages/dashboard/src/components/appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
}, [rmf]);

React.useEffect(() => {
const sub = AppEvents.refreshTaskAppCount.subscribe((currentValue) => {
setRefreshTaskAppCount(currentValue);
const sub = AppEvents.refreshTaskApp.subscribe({
next: () => setRefreshTaskAppCount((oldValue) => ++oldValue),
});
return () => sub.unsubscribe();
}, []);
Expand Down Expand Up @@ -254,18 +254,21 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
}),
);
subs.push(
AppEvents.refreshAlertCount.subscribe((_) => {
(async () => {
const resp = await rmf.alertsApi.getAlertsAlertsGet();
const alerts = resp.data as Alert[];
setUnacknowledgedAlertsNum(
alerts.filter(
(alert) => !(alert.acknowledged_by && alert.unix_millis_acknowledged_time),
).length,
);
})();
AppEvents.refreshAlert.subscribe({
next: () => {
(async () => {
const resp = await rmf.alertsApi.getAlertsAlertsGet();
const alerts = resp.data as Alert[];
setUnacknowledgedAlertsNum(
alerts.filter(
(alert) => !(alert.acknowledged_by && alert.unix_millis_acknowledged_time),
).length,
);
})();
},
}),
);

// Get the initial number of unacknowledged alerts
(async () => {
const resp = await rmf.alertsApi.getAlertsAlertsGet();
Expand Down Expand Up @@ -298,9 +301,9 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
scheduleRequests.map((req) => rmf.tasksApi.postScheduledTaskScheduledTasksPost(req)),
);
}
AppEvents.refreshTaskAppCount.next(refreshTaskAppCount + 1);
AppEvents.refreshTaskApp.next();
},
[rmf, refreshTaskAppCount],
[rmf],
);

const uploadFileInputRef = React.useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -357,9 +360,9 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
throw new Error('tasks api not available');
}
await rmf.tasksApi.postFavoriteTaskFavoriteTasksPost(taskFavoriteRequest);
AppEvents.refreshTaskAppCount.next(refreshTaskAppCount + 1);
AppEvents.refreshTaskApp.next();
},
[rmf, refreshTaskAppCount],
[rmf],
);

const deleteFavoriteTask = React.useCallback<Required<CreateTaskFormProps>['deleteFavoriteTask']>(
Expand All @@ -372,9 +375,9 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
}

await rmf.tasksApi.deleteFavoriteTaskFavoriteTasksFavoriteTaskIdDelete(favoriteTask.id);
AppEvents.refreshTaskAppCount.next(refreshTaskAppCount + 1);
AppEvents.refreshTaskApp.next();
},
[rmf, refreshTaskAppCount],
[rmf],
);
//#endregion 'Favorite Task'

Expand Down
70 changes: 55 additions & 15 deletions packages/dashboard/src/components/tasks/tasks-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import { RmfAppContext } from '../rmf-app';
import { TaskSummary } from './task-summary';
import { downloadCsvFull, downloadCsvMinimal } from './utils';

const RefreshTaskQueueTableInterval = 5000;

interface TabPanelProps {
children?: React.ReactNode;
index: number;
Expand Down Expand Up @@ -197,6 +199,7 @@ export const TasksApp = React.memo(
ref: React.Ref<HTMLDivElement>,
) => {
const rmf = React.useContext(RmfAppContext);
const [autoRefresh, setAutoRefresh] = React.useState(true);
const [refreshTaskAppCount, setRefreshTaskAppCount] = React.useState(0);

const uploadFileInputRef = React.useRef<HTMLInputElement>(null);
Expand All @@ -209,7 +212,6 @@ export const TasksApp = React.memo(
);
const [currentEventId, setCurrentEventId] = React.useState<number>(-1);
const exceptDateRef = React.useRef<Date>(new Date());

const [tasksState, setTasksState] = React.useState<Tasks>({
isLoading: true,
data: [],
Expand All @@ -222,12 +224,31 @@ export const TasksApp = React.memo(
const [sortFields, setSortFields] = React.useState<SortFields>({ model: undefined });

React.useEffect(() => {
const sub = AppEvents.refreshTaskAppCount.subscribe((currentValue) => {
setRefreshTaskAppCount(currentValue);
const sub = AppEvents.refreshTaskApp.subscribe({
next: () => {
setRefreshTaskAppCount((oldValue) => ++oldValue);
},
});
return () => sub.unsubscribe();
}, []);

React.useEffect(() => {
if (!autoRefresh) {
return;
}

const refreshTaskQueueTable = async () => {
AppEvents.refreshTaskApp.next();
};
const refreshInterval = window.setInterval(
refreshTaskQueueTable,
RefreshTaskQueueTableInterval,
);
return () => {
clearInterval(refreshInterval);
};
}, [autoRefresh]);

// TODO: parameterize this variable
const GET_LIMIT = 10;
React.useEffect(() => {
Expand Down Expand Up @@ -384,9 +405,16 @@ export const TasksApp = React.memo(
[rmf],
);

const [selectedTabIndex, setSelectedTabIndex] = React.useState(0);
const handleChange = (_: React.SyntheticEvent, newSelectedTabIndex: number) => {
setSelectedTabIndex(newSelectedTabIndex);
enum TaskTablePanel {
QueueTable = 0,
Schedule = 1,
}

const [selectedPanelIndex, setSelectedPanelIndex] = React.useState(TaskTablePanel.QueueTable);

const handlePanelChange = (_: React.SyntheticEvent, newSelectedTabIndex: number) => {
setSelectedPanelIndex(newSelectedTabIndex);
setAutoRefresh(newSelectedTabIndex === TaskTablePanel.QueueTable);
};

const handleSubmitDeleteSchedule: React.MouseEventHandler = async (ev) => {
Expand All @@ -409,7 +437,7 @@ export const TasksApp = React.memo(
} else {
await rmf.tasksApi.delScheduledTasksScheduledTasksTaskIdDelete(task.id);
}
AppEvents.refreshTaskAppCount.next(refreshTaskAppCount + 1);
AppEvents.refreshTaskApp.next();

// Set the default values
setOpenDeleteScheduleDialog(false);
Expand Down Expand Up @@ -472,7 +500,7 @@ export const TasksApp = React.memo(
<Tooltip title="Refresh" color="inherit" placement="top">
<IconButton
onClick={() => {
AppEvents.refreshTaskAppCount.next(refreshTaskAppCount + 1);
AppEvents.refreshTaskApp.next();
}}
aria-label="Refresh"
>
Expand All @@ -483,11 +511,19 @@ export const TasksApp = React.memo(
}
{...otherProps}
>
<Tabs value={selectedTabIndex} onChange={handleChange} aria-label="Task App Tabs">
<Tab label="Queue" id={tabId(0)} aria-controls={tabPanelId(0)} />
<Tab label="Schedule" id={tabId(1)} aria-controls={tabPanelId(1)} />
<Tabs value={selectedPanelIndex} onChange={handlePanelChange} aria-label="Task App Tabs">
<Tab
label="Queue"
id={tabId(TaskTablePanel.QueueTable)}
aria-controls={tabPanelId(TaskTablePanel.QueueTable)}
/>
<Tab
label="Schedule"
id={tabId(TaskTablePanel.Schedule)}
aria-controls={tabPanelId(TaskTablePanel.Schedule)}
/>
</Tabs>
<TabPanel selectedTabIndex={selectedTabIndex} index={0}>
<TabPanel selectedTabIndex={selectedPanelIndex} index={TaskTablePanel.QueueTable}>
<TableContainer>
<TaskDataGridTable
tasks={tasksState}
Expand All @@ -506,7 +542,7 @@ export const TasksApp = React.memo(
/>
</TableContainer>
</TabPanel>
<TabPanel selectedTabIndex={selectedTabIndex} index={1}>
<TabPanel selectedTabIndex={selectedPanelIndex} index={TaskTablePanel.Schedule}>
<Scheduler
// react-scheduler does not support refreshing, workaround by mounting a new instance.
key={`scheduler-${refreshTaskAppCount}`}
Expand All @@ -516,9 +552,13 @@ export const TasksApp = React.memo(
weekStartOn: 1,
startHour: 0,
endHour: 23,
step: 120,
step: 60,
}}
day={{
startHour: 0,
endHour: 23,
step: 60,
}}
disableViewNavigator
draggable={false}
editable={false}
getRemoteEvents={getRemoteEvents}
Expand Down

0 comments on commit c2a158f

Please sign in to comment.