Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): implement embedded chart cards #856

Merged
merged 64 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
65fdfe4
feat(dashboard): add basic embedded chart cards
andrewazores Feb 3, 2023
2aa79ba
refactor iframe src construction
andrewazores Feb 3, 2023
4a1344c
add logical controller for all grafana chart cards
andrewazores Feb 3, 2023
8333656
release subscriptions when no controller subscribers
andrewazores Feb 3, 2023
9df6353
cleanup
andrewazores Feb 3, 2023
30ae0a4
replace px count with em
andrewazores Feb 4, 2023
bc91498
wrap content in CardBody
andrewazores Feb 4, 2023
fe4fe01
slow refresh rate
andrewazores Feb 4, 2023
1b7f63f
add refresh and popout buttons, style plainly
andrewazores Feb 4, 2023
61f0c6c
add TODO
andrewazores Feb 4, 2023
c29f50e
refactor, simplify controller timing
andrewazores Feb 4, 2023
dee7e6a
cleanup controller instantiation and resource handling. button handli…
andrewazores Feb 6, 2023
e884006
use embedded grafana builtin refresh capability
andrewazores Feb 6, 2023
2657517
cleanup, fix bug with no-recording target selection
andrewazores Feb 7, 2023
04c21a7
fixup! cleanup, fix bug with no-recording target selection
andrewazores Feb 7, 2023
adb7194
add leniency for loading no-longer-known card types from storage
andrewazores Feb 7, 2023
fb8a214
refactor cleanup
andrewazores Feb 7, 2023
02e8bb3
reset iframe on target change to force data refresh
andrewazores Feb 7, 2023
35d05c2
add TODO
andrewazores Feb 7, 2023
d3df2b6
update empty state
andrewazores Feb 7, 2023
393884f
re-add a refresh button to reset chart card
andrewazores Feb 7, 2023
48d7c11
only apply negative margin on wider cards
andrewazores Feb 7, 2023
d94ba0a
cleanup
andrewazores Feb 7, 2023
9f07067
react to recording start/stop/delete
andrewazores Feb 7, 2023
cb382ff
require recording to be running
andrewazores Feb 7, 2023
9941032
extract min refresh to settings
andrewazores Feb 7, 2023
9fdcf72
break circular dep
andrewazores Feb 7, 2023
b3827f4
add checkbox for restarting existing recordings
andrewazores Feb 7, 2023
256cec8
add basic snapshot test
andrewazores Feb 7, 2023
1b14d6f
fix settings test
andrewazores Feb 7, 2023
da14d3e
add more tests
andrewazores Feb 8, 2023
d5967d7
fix imports
andrewazores Feb 8, 2023
6ea3b2f
rebase fixup
andrewazores Feb 9, 2023
f752ef1
use consistent card height
andrewazores Feb 9, 2023
5bdb9ce
fix array key warnings
andrewazores Feb 9, 2023
092243f
add missing set state call
andrewazores Feb 9, 2023
5830893
use function form set state
andrewazores Feb 9, 2023
2d0da2c
remove redundant nullsafe nav check
andrewazores Feb 9, 2023
b64a84f
use array destructuring
andrewazores Feb 9, 2023
b231f90
remove redundant first operator
andrewazores Feb 9, 2023
53989ca
remove more redundant nullsafe navs
andrewazores Feb 9, 2023
7bc14f3
replace <code> with <Label>
andrewazores Feb 9, 2023
e095b67
set description empty to remove redundant explanation
andrewazores Feb 9, 2023
020ecc2
rename button
andrewazores Feb 9, 2023
8adb794
memoize controller context
andrewazores Feb 9, 2023
b16b7a4
reduce negative margin for easier dragging and resizing
andrewazores Feb 9, 2023
0c7bc07
allow dynamic card height
andrewazores Feb 9, 2023
eb72df8
localize card content
andrewazores Feb 9, 2023
9727fd8
correct tests for localization
andrewazores Feb 9, 2023
8ad0f06
add typedefs for react-test-renderer
andrewazores Feb 9, 2023
6d5f0eb
remove redundant fragment wrapping
andrewazores Feb 9, 2023
3e58e6f
tmp? loading view
andrewazores Feb 9, 2023
d16c65a
refactor cleanup/simplify, also fixes tests
andrewazores Feb 9, 2023
1664e2a
add snapshot for loading state
andrewazores Feb 9, 2023
5ea6508
controller initialization bugfix
andrewazores Feb 9, 2023
137e619
use Continuous template instead, seems to generally support most metr…
andrewazores Feb 9, 2023
8925f57
reset history and perform cleanup between tests
andrewazores Feb 9, 2023
64fb8eb
set provider order to match real application
andrewazores Feb 9, 2023
1ed7d69
experiment with generic provider render function
andrewazores Feb 9, 2023
dc1842e
move generic function into Common, expect localized view in test
andrewazores Feb 9, 2023
aae97f6
temporarily disable pointer events on iframes while resizing
andrewazores Feb 9, 2023
a7c86a5
apply pointer events disabling during drag-and-drop
andrewazores Feb 9, 2023
63a5ca2
downgrade to BETA
andrewazores Feb 9, 2023
2cc6b03
lint fix
andrewazores Feb 9, 2023
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
22 changes: 22 additions & 0 deletions locales/en/public.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@
"OPEN_SOURCE_LICENSE": "Open Source License",
"VERSION": "version"
},
"CHART_CARD": {
"BUTTONS": {
"CREATE": {
"LABEL": "Create"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice these aria-labels have a different scheme from some other aria labels in the file
e.g.

  "DateTimePicker": {
    "ARIA_LABELS": {
      "DISPLAY_SELECTED_DATETIME": "Displayed selected datetime",
      "TABS": "Select a date or time tab"
    },
    "SELECTED_DATETIME": "Selected DateTime"
  },
  "MeridiemPicker": {
    "ARIA_LABELS": {
      "LISTBOX": "Select AM or PM"
    }
  },

Maybe either change this or we should change the other ones to keep consistency?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to document key format somewhere too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean just change the way that the key is split up? Or change the actual localized text?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the way the key is split up. Maybe we should have

"DateTimePicker": {
    "TABS": {  
          "LABEL": "Select a date or time tab"
     }

in the same format in this PR that you have done here. If so, then that's okay to change in a separate refactor PR.

Just keeping a consistent key format.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I don't really mind either way. I think there probably are/will be instances where the aria-label matches some text that is visually rendered, so specifying ARIA_LABELS as part of the key maybe doesn't make the most sense.

},
"POPOUT": {
"LABEL": "Pop out {{chartKind}} chart"
},
"SYNC": {
"LABEL": "Synchronize {{chartKind}} chart"
}
},
"NO_RECORDING": {
"DESCRIPTION": "Metrics cards display data taken from running flight recordings with the label <label>origin={{recordingName}}</label>. No such recordings are currently available.",
"TITLE": "No source recording"
}
},
"DashboardCardActionMenu": {
"RESET_SIZE": "Reset Size"
},
Expand Down Expand Up @@ -55,6 +72,11 @@
"LANGUAGE_REGION": "Language & Region",
"NOTIFICATION_MESSAGE": "Notifications & Messages"
},
"CHARTS_CONFIG": {
"DESCRIPTION": "",
"REFRESH_RATE_SETTING": "Configure the minimum time to wait between data refreshes. Individual metrics cards may still request updates on a faster cycle, but the client application instance will throttle update requests to the server according to this setting.",
"TITLE": "Dashboard Metrics Configuration"
},
"CREDENTIALS_STORAGE": {
"BACKEND": {
"DESCRIPTION": "Keep credentials in encrypted Cryostat backend storage. These credentials will be available to other users and will be used for Automated Rules.",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^27.0.2",
"@types/js-base64": "3.3.1",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"@vue/preload-webpack-plugin": "^2.0.0",
Expand Down
13 changes: 13 additions & 0 deletions src/app/CreateRecording/CreateRecording.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { RecordingLabel } from '@app/RecordingMetadata/RecordingLabel';
import { TemplateType } from '@app/Shared/Services/Api.service';
import { TargetView } from '@app/TargetView/TargetView';
import { Card, CardBody, Tab, Tabs } from '@patternfly/react-core';
Expand All @@ -45,8 +46,14 @@ import { CustomRecordingForm } from './CustomRecordingForm';
import { SnapshotRecordingForm } from './SnapshotRecordingForm';

export interface CreateRecordingProps {
restartExisting?: boolean;
name?: string;
templateName?: string;
templateType?: TemplateType;
labels?: RecordingLabel[];
duration?: number;
maxAge?: number;
maxSize?: number;
}

export interface EventTemplate {
Expand All @@ -63,8 +70,14 @@ const Comp: React.FC<RouteComponentProps<Record<string, never>, StaticContext, C

const prefilled = React.useMemo(
() => ({
restartExisting: props.location?.state?.restartExisting,
name: props.location?.state?.name,
templateName: props.location?.state?.templateName,
templateType: props.location?.state?.templateType,
labels: props.location?.state?.labels,
duration: props.location?.state?.duration,
maxAge: props.location?.state?.maxAge,
maxSize: props.location?.state?.maxSize,
}),
[props.location]
);
Expand Down
53 changes: 42 additions & 11 deletions src/app/CreateRecording/CustomRecordingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,14 @@ import { EventTemplate } from './CreateRecording';

export interface CustomRecordingFormProps {
prefilled?: {
restartExisting?: boolean;
name?: string;
templateName?: string;
templateType?: TemplateType;
labels?: RecordingLabel[];
duration?: number;
maxAge?: number;
maxSize?: number;
};
}

Expand All @@ -89,22 +95,29 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
const history = useHistory();
const addSubscription = useSubscriptions();

const [recordingName, setRecordingName] = React.useState('');
const [nameValid, setNameValid] = React.useState(ValidatedOptions.default);
const [continuous, setContinuous] = React.useState(false);
const [recordingName, setRecordingName] = React.useState(prefilled?.name || '');
const [nameValid, setNameValid] = React.useState(
prefilled?.name
? RecordingNamePattern.test(recordingName)
? ValidatedOptions.success
: ValidatedOptions.error
: ValidatedOptions.default
);
const [restartExisting, setRestartExisting] = React.useState(prefilled?.restartExisting || false);
const [continuous, setContinuous] = React.useState((prefilled?.duration || 30) < 1);
const [archiveOnStop, setArchiveOnStop] = React.useState(true);
const [duration, setDuration] = React.useState(30);
const [duration, setDuration] = React.useState(prefilled?.duration || 30);
const [durationUnit, setDurationUnit] = React.useState(1000);
const [durationValid, setDurationValid] = React.useState(ValidatedOptions.success);
const [templates, setTemplates] = React.useState([] as EventTemplate[]);
const [templateName, setTemplateName] = React.useState<string | undefined>(prefilled?.templateName);
const [templateType, setTemplateType] = React.useState<TemplateType | undefined>(prefilled?.templateType);
const [maxAge, setMaxAge] = React.useState(0);
const [maxAge, setMaxAge] = React.useState(prefilled?.maxAge || 0);
const [maxAgeUnits, setMaxAgeUnits] = React.useState(1);
const [maxSize, setMaxSize] = React.useState(0);
const [maxSize, setMaxSize] = React.useState(prefilled?.maxSize || 0);
const [maxSizeUnits, setMaxSizeUnits] = React.useState(1);
const [toDisk, setToDisk] = React.useState(true);
const [labels, setLabels] = React.useState([] as RecordingLabel[]);
const [labels, setLabels] = React.useState(prefilled?.labels || []);
const [labelsValid, setLabelsValid] = React.useState(ValidatedOptions.default);
const [loading, setLoading] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState('');
Expand All @@ -119,14 +132,21 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
.subscribe((resp) => {
setLoading(false);
if (resp && resp.ok) {
history.push('/recordings');
history.goBack();
}
})
);
},
[addSubscription, context.api, history, setLoading]
);

const handleRestartExistingChange = React.useCallback(
(checked) => {
setRestartExisting(checked);
},
[setRestartExisting]
);

const handleContinuousChange = React.useCallback(
(checked) => {
setContinuous(checked);
Expand Down Expand Up @@ -228,12 +248,12 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
const setRecordingOptions = React.useCallback(
(options: RecordingOptions) => {
// toDisk is not set, and defaults to true because of https://github.com/cryostatio/cryostat/issues/263
setMaxAge(options.maxAge || 0);
setMaxAge(prefilled?.maxAge || options.maxAge || 0);
setMaxAgeUnits(1);
setMaxSize(options.maxSize || 0);
setMaxSize(prefilled?.maxSize || options.maxSize || 0);
setMaxSizeUnits(1);
},
[setMaxAge, setMaxAgeUnits, setMaxSize, setMaxSizeUnits]
[setMaxAge, setMaxAgeUnits, setMaxSize, setMaxSizeUnits, prefilled]
);

const handleSubmit = React.useCallback(() => {
Expand All @@ -249,6 +269,7 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
}

const options: RecordingOptions = {
restart: restartExisting,
toDisk: toDisk,
maxAge: toDisk ? (continuous ? maxAge * maxAgeUnits : undefined) : undefined,
maxSize: toDisk ? maxSize * maxSizeUnits : undefined,
Expand Down Expand Up @@ -276,6 +297,7 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
nameValid,
notifications,
recordingName,
restartExisting,
toDisk,
handleCreateRecording,
]);
Expand Down Expand Up @@ -397,6 +419,15 @@ export const CustomRecordingForm: React.FC<CustomRecordingFormProps> = ({ prefil
onChange={handleRecordingNameChange}
validated={nameValid}
/>
<Checkbox
label="Restart if recording already exists"
andrewazores marked this conversation as resolved.
Show resolved Hide resolved
isChecked={restartExisting}
isDisabled={loading}
onChange={handleRestartExistingChange}
aria-label="restartExisting checkbox"
id="recording-restart-existing"
name="recording-restart-existing"
/>
</FormGroup>
<FormGroup
label="Duration"
Expand Down
2 changes: 2 additions & 0 deletions src/app/Dashboard/AddCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ const PropsConfigForm = ({ onChange, ...props }: PropsConfigFormProps) => {
onChange={handleNumeric(ctrl.key)}
onPlus={handleNumericStep(ctrl.key, 1)}
onMinus={handleNumericStep(ctrl.key, -1)}
min={ctrl.extras?.min}
max={ctrl.extras?.max}
/>
);
break;
Expand Down
Loading