Skip to content

Commit

Permalink
Add view events flyout
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Feb 3, 2023
1 parent 2b82edd commit 5c1cbb8
Show file tree
Hide file tree
Showing 38 changed files with 831 additions and 31 deletions.
4 changes: 4 additions & 0 deletions src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ interface Props {
SavedObjectFinder: React.ComponentType<any>;
stateTransfer?: EmbeddableStateTransfer;
hideHeader?: boolean;
hasBorder?: boolean;
hasShadow?: boolean;
}

interface State {
Expand Down Expand Up @@ -234,6 +236,8 @@ export class EmbeddablePanel extends React.Component<Props, State> {
paddingSize="none"
role="figure"
aria-labelledby={headerId}
hasBorder={this.props.hasBorder}
hasShadow={this.props.hasShadow}
>
{!this.props.hideHeader && (
<PanelHeader
Expand Down
13 changes: 12 additions & 1 deletion src/plugins/embeddable/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ export interface EmbeddableStart extends PersistableState<EmbeddableInput> {
getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer;
}

export type EmbeddablePanelHOC = React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>;
export type EmbeddablePanelHOC = React.FC<{
embeddable: IEmbeddable;
hideHeader?: boolean;
hasBorder?: boolean;
hasShadow?: boolean;
}>;

export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
private readonly embeddableFactoryDefinitions: Map<
Expand Down Expand Up @@ -168,12 +173,18 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
const getEmbeddablePanelHoc = (stateTransfer?: EmbeddableStateTransfer) => ({
embeddable,
hideHeader,
hasBorder,
hasShadow,
}: {
embeddable: IEmbeddable;
hideHeader?: boolean;
hasBorder?: boolean;
hasShadow?: boolean;
}) => (
<EmbeddablePanel
hideHeader={hideHeader}
hasBorder={hasBorder}
hasShadow={hasShadow}
embeddable={embeddable}
stateTransfer={stateTransfer ? stateTransfer : this.outgoingOnlyStateTransfer}
getActions={uiActions.getTriggerCompatibleActions}
Expand Down
1 change: 1 addition & 0 deletions src/plugins/ui_actions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export {
visualizeFieldTrigger,
VISUALIZE_GEO_FIELD_TRIGGER,
visualizeGeoFieldTrigger,
OPEN_EVENTS_FLYOUT_TRIGGER,
} from './triggers';
export {
TriggerContextMapping,
Expand Down
1 change: 1 addition & 0 deletions src/plugins/ui_actions/public/triggers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export * from './apply_filter_trigger';
export * from './visualize_field_trigger';
export * from './visualize_geo_field_trigger';
export * from './default_trigger';
export * from './open_events_flyout_trigger';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const OPEN_EVENTS_FLYOUT_TRIGGER = 'OPEN_EVENTS_FLYOUT_TRIGGER';
21 changes: 15 additions & 6 deletions src/plugins/vis_augmenter/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,28 @@ export enum VisLayerTypes {
PointInTimeEvents = 'PointInTimeEvents',
}

export type PluginResourceType = string;

export interface PluginResource {
type: PluginResourceType;
id: string;
name: string;
urlPath: string;
}

export interface VisLayer {
type: keyof typeof VisLayerTypes;
name: string;
originPlugin: string;
pluginResource: PluginResource;
}

export type VisLayers = VisLayer[];

// resourceId & resourceName are required so that the
// events flyout can partition data based on these attributes
// (e.g., partitioning anomalies based on the detector they came from)
// persisting the ID so we can reference the rest of the metadata
// stored at the VisLayer level. Needed for tooltip formatting and
// partitioning the data in the View Events page
export interface EventMetadata {
resourceId: string;
resourceName: string;
pluginResourceId: string;
tooltip?: string;
}

Expand Down
11 changes: 10 additions & 1 deletion src/plugins/vis_augmenter/opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,14 @@
"version": "opensearchDashboards",
"server": true,
"ui": true,
"requiredPlugins": ["data", "savedObjects", "opensearchDashboardsUtils", "expressions"]
"requiredPlugins": [
"data",
"savedObjects",
"opensearchDashboardsUtils",
"expressions",
"visualizations",
"uiActions",
"embeddable"
],
"requiredBundles": ["opensearchDashboardsReact"]
}
6 changes: 6 additions & 0 deletions src/plugins/vis_augmenter/public/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { OPEN_EVENTS_FLYOUT_ACTION, OpenEventsFlyoutAction } from './open_events_flyout_action';
32 changes: 32 additions & 0 deletions src/plugins/vis_augmenter/public/actions/open_events_flyout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { CoreStart } from 'src/core/public';
import { toMountPoint } from '../../../opensearch_dashboards_react/public';
import { ViewEventsFlyout } from '../components';

interface Props {
core: CoreStart;
savedObjectId: string;
}

export async function openViewEventsFlyout(props: Props) {
const flyoutSession = props.core.overlays.openFlyout(
toMountPoint(
<ViewEventsFlyout
onClose={() => {
if (flyoutSession) {
flyoutSession.close();
}
}}
savedObjectId={props.savedObjectId}
/>
),
{
'data-test-subj': 'viewEventsFlyout',
ownFocus: true,
}
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { i18n } from '@osd/i18n';
import { CoreStart } from 'opensearch-dashboards/public';
import {
Action,
ActionExecutionContext,
IncompatibleActionError,
} from '../../../ui_actions/public';
import { AugmentVisContext } from '../triggers';
import { openViewEventsFlyout } from './open_events_flyout';

export const OPEN_EVENTS_FLYOUT_ACTION = 'OPEN_EVENTS_FLYOUT_ACTION';

export class OpenEventsFlyoutAction implements Action<AugmentVisContext> {
public readonly type = OPEN_EVENTS_FLYOUT_ACTION;
public readonly id = OPEN_EVENTS_FLYOUT_ACTION;
public order = 1;

constructor(private core: CoreStart) {}

public getIconType(context: ActionExecutionContext<AugmentVisContext>) {
return undefined;
}

public getDisplayName() {
return i18n.translate('visAugmenter.displayName', {
defaultMessage: 'Open View Events flyout',
});
}

public async isCompatible({ savedObjectId }: AugmentVisContext) {
return true;
}

public async execute({ savedObjectId }: AugmentVisContext) {
if (!(await this.isCompatible({ savedObjectId }))) {
throw new IncompatibleActionError();
}
openViewEventsFlyout({
core: this.core,
savedObjectId,
});
}
}
32 changes: 32 additions & 0 deletions src/plugins/vis_augmenter/public/components/base_vis_item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { getEmbeddable } from '../services';
import { VisualizeEmbeddable } from '../../../visualizations/public';
import './styles.scss';

interface Props {
embeddable: VisualizeEmbeddable;
}

export function BaseVisItem(props: Props) {
const PanelComponent = getEmbeddable().getEmbeddablePanel();

return (
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem className="view-events-flyout__visDescription" grow={false} />
<EuiFlexItem grow={true} className="view-events-flyout__baseVis">
<PanelComponent
embeddable={props.embeddable}
hideHeader={true}
hasBorder={false}
hasShadow={false}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
70 changes: 70 additions & 0 deletions src/plugins/vis_augmenter/public/components/date_range_item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import moment from 'moment';
import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiIcon,
prettyDuration,
EuiButton,
} from '@elastic/eui';
import './styles.scss';
import { TimeRange } from '../../../data/common';
import { DATE_RANGE_FORMAT } from './view_events_flyout';

interface Props {
timeRange: TimeRange;
reload: () => void;
}

export function DateRangeItem(props: Props) {
const [lastUpdatedTime, setLastUpdatedTime] = useState<string>(
moment(Date.now()).format(DATE_RANGE_FORMAT)
);

const durationText = prettyDuration(
props.timeRange.from,
props.timeRange.to,
[],
DATE_RANGE_FORMAT
);

return (
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
className="view-events-flyout__dateRangeHeader"
>
<EuiFlexItem grow={false}>
<EuiIcon type="calendar" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>{durationText}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
iconType={'refresh'}
isDisabled={false}
onClick={() => {
props.reload();
setLastUpdatedTime(moment(Date.now()).format(DATE_RANGE_FORMAT));
}}
>
Refresh
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued" style={{ whiteSpace: 'pre-line' }}>
{`This view is not updated to load the latest events automatically.
Last updated: ${lastUpdatedTime}`}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
47 changes: 47 additions & 0 deletions src/plugins/vis_augmenter/public/components/event_vis_item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink, EuiNotificationBadge } from '@elastic/eui';
import { getEmbeddable, getCore } from '../services';
import './styles.scss';
import { EventVisEmbeddableItem } from './';

interface Props {
item: EventVisEmbeddableItem;
}

export function EventVisItem(props: Props) {
const PanelComponent = getEmbeddable().getEmbeddablePanel();
const baseUrl = getCore().http.basePath;
const { name, urlPath } = props.item.visLayer.pluginResource;

return (
<>
<EuiSpacer size="l" />
<EuiFlexGroup direction="row" gutterSize="s">
<EuiFlexItem grow={false} className="view-events-flyout__visDescription">
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiLink href={`${baseUrl.prepend(`${urlPath}`)}`}>{name}</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiNotificationBadge color="subdued">3</EuiNotificationBadge>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

<EuiFlexItem grow={true} className="view-events-flyout__eventVis">
<PanelComponent
embeddable={props.item.embeddable}
hideHeader={true}
hasBorder={false}
hasShadow={false}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}
10 changes: 10 additions & 0 deletions src/plugins/vis_augmenter/public/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export {
ViewEventsFlyout,
EventVisEmbeddablesMap,
EventVisEmbeddableItem,
} from './view_events_flyout';
19 changes: 19 additions & 0 deletions src/plugins/vis_augmenter/public/components/loading_flyout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiFlyoutBody, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';

export function LoadingFlyout() {
return (
<EuiFlyoutBody>
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutBody>
);
}
Loading

0 comments on commit 5c1cbb8

Please sign in to comment.