Skip to content

Commit

Permalink
feat(topology): topology view (#891)
Browse files Browse the repository at this point in the history
* feat(topology): topology view

* fix(topology): silence match expression check notifications

* fix(topology): labels should readonly

* feat(topology): topology filters

* fix(topology): enforce feature level

* fix(topology): also enable singles mode if realm only

* fix(topology): restyle selector

* chore(topology): sync display names

* chore(topology): remove extra hook deps

* chore(topology): fix typos

* chore(topology): removing typos

* fix(topology): mount menus to parent

* feat(topology): flatten target filters

* chore(topology): remove testing funcs

* fix(topology): additional fixes for filters

* chore(topology): adjust comments

* chore(topology): refactor hooks

* chore(topology): clean up eslint comments

* chore(topology): center total column

* feat(topology): listview expression search

* feat(topology): enhance empty state view

* fix(topology): deep copy target object when checking matches

* fix(topology): selectable area in list view

* chore(topology): refactor common match expre eval

* fix(topology): fix filter typeahead

* fix(topology): selectable text area should have styled cursor
  • Loading branch information
Thuan Vo authored Mar 11, 2023
1 parent 9cfd2ef commit bfd9f86
Show file tree
Hide file tree
Showing 80 changed files with 8,940 additions and 605 deletions.
2 changes: 2 additions & 0 deletions locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"CARD_TYPE": "Card type",
"CLEAR_FILTERS": "Clear all filters",
"CRITICAL": "CRITICAL",
"CREATE": "Create",
"CREATING": "Creating",
"CRYOSTAT_TRADEMARK": "Copyright The Cryostat Authors, The Universal Permissive License (UPL), Version 1.0",
"DATE": "Date",
"DESCRIPTION": "Description",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"@patternfly/react-icons": "^4.93.6",
"@patternfly/react-styles": "^4.92.6",
"@patternfly/react-table": "^4.112.39",
"@patternfly/react-topology": "4.91.27",
"@reduxjs/toolkit": "^1.9.3",
"@types/lodash": "^4.14.191",
"@types/react-router-dom": "^5.3.3",
Expand Down
26 changes: 17 additions & 9 deletions src/app/Archives/Archives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ import { useSubscriptions } from '@app/utils/useSubscriptions';
import { Card, CardBody, EmptyState, EmptyStateIcon, Tab, Tabs, TabTitleText, Title } from '@patternfly/react-core';
import { SearchIcon } from '@patternfly/react-icons';
import * as React from 'react';
import { StaticContext } from 'react-router';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { of } from 'rxjs';
import { AllArchivedRecordingsTable } from './AllArchivedRecordingsTable';
import { AllTargetsArchivedRecordingsTable } from './AllTargetsArchivedRecordingsTable';

/*
This specific target is used as the "source" for the Uploads version of the ArchivedRecordingsTable.
The connectUrl is the 'uploads' because for actions performed on uploaded archived recordings,
Expand All @@ -60,12 +61,19 @@ export const uploadAsTarget: Target = {
alias: '',
};

export interface ArchivesProps {}
export type SupportedTab = 'all-archives' | 'all-targets' | 'uploads';

export interface ArchivesProps {
tab?: SupportedTab;
}

export const Archives: React.FC<ArchivesProps> = (_) => {
export const Archives: React.FC<RouteComponentProps<Record<string, never>, StaticContext, ArchivesProps>> = ({
location,
..._props
}) => {
const context = React.useContext(ServiceContext);
const addSubscription = useSubscriptions();
const [activeTab, setActiveTab] = React.useState(0);
const [activeTab, setActiveTab] = React.useState(location?.state?.tab || 'all-archives');
const [archiveEnabled, setArchiveEnabled] = React.useState(false);

React.useEffect(() => {
Expand All @@ -76,14 +84,14 @@ export const Archives: React.FC<ArchivesProps> = (_) => {

const cardBody = React.useMemo(() => {
return archiveEnabled ? (
<Tabs id="archives" activeKey={activeTab} onSelect={(evt, idx) => setActiveTab(Number(idx))}>
<Tab id="all-targets" eventKey={0} title={<TabTitleText>All Targets</TabTitleText>}>
<Tabs id="archives" activeKey={activeTab} onSelect={(evt, key) => setActiveTab(`${key}` as SupportedTab)}>
<Tab id="all-targets" eventKey={'all-archives'} title={<TabTitleText>All Targets</TabTitleText>}>
<AllTargetsArchivedRecordingsTable />
</Tab>
<Tab id="all-archives" eventKey={1} title={<TabTitleText>All Archives</TabTitleText>}>
<Tab id="all-archives" eventKey={'all-targets'} title={<TabTitleText>All Archives</TabTitleText>}>
<AllArchivedRecordingsTable />
</Tab>
<Tab id="uploads" eventKey={2} title={<TabTitleText>Uploads</TabTitleText>}>
<Tab id="uploads" eventKey={'uploads'} title={<TabTitleText>Uploads</TabTitleText>}>
<ArchivedRecordingsTable target={uploadTargetAsObs} isUploadsTable={true} isNestedTable={false} />
</Tab>
</Tabs>
Expand All @@ -106,4 +114,4 @@ export const Archives: React.FC<ArchivesProps> = (_) => {
);
};

export default Archives;
export default withRouter(Archives);
30 changes: 16 additions & 14 deletions src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
TemplateType,
} from '@app/Shared/Services/Api.service';
import { ServiceContext } from '@app/Shared/Services/Services';
import { NO_TARGET } from '@app/Shared/Services/Target.service';
import { NO_TARGET, Target } from '@app/Shared/Services/Target.service';
import { SelectTemplateSelectorForm } from '@app/TemplateSelector/SelectTemplateSelectorForm';
import { useSubscriptions } from '@app/utils/useSubscriptions';
import {
Expand All @@ -64,12 +64,17 @@ import {
} from '@patternfly/react-core';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { concatMap, filter, first } from 'rxjs';
import { concatMap, filter, first, Observable } from 'rxjs';

interface AutomatedAnalysisConfigFormProps {
useTitle?: boolean;
targetObs?: Observable<Target>;
}

export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormProps> = ({ useTitle = false }) => {
export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormProps> = ({
useTitle = false,
targetObs,
}) => {
const context = React.useContext(ServiceContext);
const addSubscription = useSubscriptions();
const { t } = useTranslation();
Expand Down Expand Up @@ -103,8 +108,7 @@ export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormPr
const refreshTemplates = React.useCallback(() => {
setIsLoading(true);
addSubscription(
context.target
.target()
(targetObs ? targetObs : context.target.target())
.pipe(
filter((target) => target !== NO_TARGET),
first(),
Expand All @@ -125,7 +129,7 @@ export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormPr
},
})
);
}, [addSubscription, context.target, context.api, setErrorMessage, setTemplates, setIsLoading]);
}, [addSubscription, context.target, context.api, setErrorMessage, setTemplates, setIsLoading, targetObs]);

React.useEffect(() => {
addSubscription(
Expand All @@ -139,12 +143,12 @@ export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormPr

React.useEffect(() => {
addSubscription(
context.target.target().subscribe(() => {
(targetObs ? targetObs : context.target.target()).subscribe(() => {
refreshTemplates();
setIsLoading(false);
})
);
}, [addSubscription, context.target, refreshTemplates, setIsLoading]);
}, [addSubscription, targetObs, refreshTemplates, setIsLoading, context.target]);

const getEventString = React.useCallback((templateName: string, templateType: string) => {
let str = '';
Expand Down Expand Up @@ -340,13 +344,11 @@ export const AutomatedAnalysisConfigForm: React.FC<AutomatedAnalysisConfigFormPr
if (isLoading) {
return <LoadingView />;
}
return (
return useTitle ? (
<Form>
{useTitle ? (
<FormSection title={t('AutomatedAnalysisConfigForm.FORM_TITLE')}>{formContent}</FormSection>
) : (
formContent
)}
<FormSection title={t('AutomatedAnalysisConfigForm.FORM_TITLE')}>{formContent}</FormSection>
</Form>
) : (
formContent
);
};
2 changes: 1 addition & 1 deletion src/app/Dashboard/Charts/jfr/JFRMetricsChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
*/

import { CreateRecordingProps } from '@app/CreateRecording/CreateRecording';
import { DashboardCardDescriptor, DashboardCardProps, DashboardCardSizes } from '@app/Dashboard/Dashboard';
import { LoadingView } from '@app/LoadingView/LoadingView';
import { ServiceContext } from '@app/Shared/Services/Services';
import { FeatureLevel } from '@app/Shared/Services/Settings.service';
Expand All @@ -60,7 +61,6 @@ import * as React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { interval } from 'rxjs';
import { DashboardCardDescriptor, DashboardCardProps, DashboardCardSizes } from '@app/Dashboard/Dashboard';
import { DashboardCard } from '../../DashboardCard';
import { ChartContext } from './../ChartContext';
import { ControllerState, RECORDING_NAME } from './JFRMetricsChartController';
Expand Down
2 changes: 1 addition & 1 deletion src/app/Dashboard/Charts/mbean/MBeanMetricsChartCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
* SOFTWARE.
*/

import { DashboardCardDescriptor, DashboardCardProps, DashboardCardSizes } from '@app/Dashboard/Dashboard';
import { ServiceContext } from '@app/Shared/Services/Services';
import { FeatureLevel } from '@app/Shared/Services/Settings.service';
import useDayjs from '@app/utils/useDayjs';
Expand All @@ -56,7 +57,6 @@ import _ from 'lodash';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { interval } from 'rxjs';
import { DashboardCardDescriptor, DashboardCardProps, DashboardCardSizes } from '@app/Dashboard/Dashboard';
import { DashboardCard } from '../../DashboardCard';
import { ChartContext } from './../ChartContext';
import { MBeanMetrics } from './MBeanMetricsChartController';
Expand Down
2 changes: 1 addition & 1 deletion src/app/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export const Dashboard: React.FC<DashboardProps> = (_) => {
);

return (
<TargetView pageTitle={t('Dashboard.PAGE_TITLE')} compactSelect={false}>
<TargetView pageTitle={t('Dashboard.PAGE_TITLE')}>
<ChartContext.Provider value={chartContext}>
<Grid id={'dashboard-grid'} hasGutter>
{cardConfigs
Expand Down
40 changes: 29 additions & 11 deletions src/app/Events/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,29 @@ import { TargetView } from '@app/TargetView/TargetView';
import { useSubscriptions } from '@app/utils/useSubscriptions';
import { Card, CardBody, Stack, StackItem, Tab, Tabs, Tooltip } from '@patternfly/react-core';
import * as React from 'react';
import { StaticContext } from 'react-router';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { concatMap, filter } from 'rxjs';
import { EventTemplates } from './EventTemplates';
import { EventTypes } from './EventTypes';

export interface EventsProps {}
export type SupportedEventTab = 'templates' | 'types';

export const Events: React.FC<EventsProps> = (_) => {
export type SupportedAgentTab = 'templates' | 'probes';

export interface EventsProps {
eventTab?: SupportedEventTab;
agentTab?: SupportedAgentTab;
}

export const Events: React.FC<RouteComponentProps<Record<string, never>, StaticContext, EventsProps>> = ({
location,
...props
}) => {
const context = React.useContext(ServiceContext);
const addSubscription = useSubscriptions();
const [eventActiveTab, setEventActiveTab] = React.useState(0);
const [probeActiveTab, setProbeActiveTab] = React.useState(0);
const [eventActiveTab, setEventActiveTab] = React.useState(location?.state?.eventTab || 'templates');
const [probeActiveTab, setProbeActiveTab] = React.useState(location?.state?.agentTab || 'templates');
const [agentDetected, setAgentDetected] = React.useState(false);

React.useEffect(() => {
Expand All @@ -68,9 +80,15 @@ export const Events: React.FC<EventsProps> = (_) => {
);
}, [addSubscription, context.target, context.api, setAgentDetected]);

const handleEventTabSelect = React.useCallback((evt, idx) => setEventActiveTab(idx), [setEventActiveTab]);
const handleEventTabSelect = React.useCallback(
(evt, key: string | number) => setEventActiveTab(`${key}` as SupportedEventTab),
[setEventActiveTab]
);

const handleProbeTabSelect = React.useCallback((evt, idx) => setProbeActiveTab(idx), [setProbeActiveTab]);
const handleProbeTabSelect = React.useCallback(
(evt, key: string | number) => setProbeActiveTab(`${key}` as SupportedAgentTab),
[setProbeActiveTab]
);

return (
<>
Expand All @@ -80,10 +98,10 @@ export const Events: React.FC<EventsProps> = (_) => {
<Card>
<CardBody>
<Tabs activeKey={eventActiveTab} onSelect={handleEventTabSelect}>
<Tab eventKey={0} title="Event Templates">
<Tab eventKey={'templates'} title="Event Templates">
<EventTemplates />
</Tab>
<Tab eventKey={1} title="Event Types">
<Tab eventKey={'types'} title="Event Types">
<EventTypes />
</Tab>
</Tabs>
Expand All @@ -94,11 +112,11 @@ export const Events: React.FC<EventsProps> = (_) => {
<Card>
<CardBody>
<Tabs activeKey={probeActiveTab} onSelect={handleProbeTabSelect}>
<Tab eventKey={0} title="Probe Templates">
<Tab eventKey={'templates'} title="Probe Templates">
<AgentProbeTemplates agentDetected={agentDetected} />
</Tab>
<Tab
eventKey={1}
eventKey={'probes'}
title="Live Configuration"
isAriaDisabled={!agentDetected}
tooltip={
Expand All @@ -119,4 +137,4 @@ export const Events: React.FC<EventsProps> = (_) => {
);
};

export default Events;
export default withRouter(Events);
26 changes: 19 additions & 7 deletions src/app/Recordings/Recordings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,44 @@ import { TargetView } from '@app/TargetView/TargetView';
import { useSubscriptions } from '@app/utils/useSubscriptions';
import { Card, CardBody, CardTitle, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
import * as React from 'react';
import { StaticContext } from 'react-router';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { ActiveRecordingsTable } from './ActiveRecordingsTable';
import { ArchivedRecordingsTable } from './ArchivedRecordingsTable';

export interface RecordingsProps {}
export type SupportedTab = 'active' | 'archived';

export const Recordings: React.FunctionComponent<RecordingsProps> = (_) => {
export interface RecordingsProps {
tab?: SupportedTab;
}

export const Recordings: React.FC<RouteComponentProps<Record<string, never>, StaticContext, RecordingsProps>> = ({
location,
..._props
}) => {
const context = React.useContext(ServiceContext);
const [activeTab, setActiveTab] = React.useState(0);
const [activeTab, setActiveTab] = React.useState(location?.state?.tab || 'active');
const [archiveEnabled, setArchiveEnabled] = React.useState(false);
const addSubscription = useSubscriptions();

React.useEffect(() => {
addSubscription(context.api.isArchiveEnabled().subscribe(setArchiveEnabled));
}, [context.api, addSubscription, setArchiveEnabled]);

const onTabSelect = React.useCallback((_, idx) => setActiveTab(Number(idx)), [setActiveTab]);
const onTabSelect = React.useCallback(
(_, key: string | number) => setActiveTab(`${key}` as SupportedTab),
[setActiveTab]
);

const targetAsObs = React.useMemo(() => context.target.target(), [context.target]);

const cardBody = React.useMemo(() => {
return archiveEnabled ? (
<Tabs id="recordings" activeKey={activeTab} onSelect={onTabSelect} unmountOnExit>
<Tab id="active-recordings" eventKey={0} title={<TabTitleText>Active Recordings</TabTitleText>}>
<Tab id="active-recordings" eventKey={'active'} title={<TabTitleText>Active Recordings</TabTitleText>}>
<ActiveRecordingsTable archiveEnabled={true} />
</Tab>
<Tab id="archived-recordings" eventKey={1} title={<TabTitleText>Archived Recordings</TabTitleText>}>
<Tab id="archived-recordings" eventKey={'archived'} title={<TabTitleText>Archived Recordings</TabTitleText>}>
<ArchivedRecordingsTable target={targetAsObs} isUploadsTable={false} isNestedTable={false} />
</Tab>
</Tabs>
Expand All @@ -86,4 +98,4 @@ export const Recordings: React.FunctionComponent<RecordingsProps> = (_) => {
);
};

export default Recordings;
export default withRouter(Recordings);
Loading

0 comments on commit bfd9f86

Please sign in to comment.