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 9, 2023
1 parent a990ab9 commit 63c138d
Show file tree
Hide file tree
Showing 41 changed files with 1,125 additions and 25 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';
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"]
}
7 changes: 7 additions & 0 deletions src/plugins/vis_augmenter/public/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { OPEN_EVENTS_FLYOUT_ACTION, OpenEventsFlyoutAction } from './open_events_flyout_action';
export { VIEW_EVENTS_OPTION_ACTION, ViewEventsOptionAction } from './view_events_option_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,52 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { i18n } from '@osd/i18n';
import { CoreStart } from 'opensearch-dashboards/public';
import { Action, 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';

/**
* This action is identical to VIEW_EVENTS_OPTION_ACTION, but with different context.
* This is because the chart doesn't persist the embeddable, which is the default
* context used by the CONTEXT_MENU_TRIGGER. Because of that, we need a separate
* one that can be persisted in the chart - in this case, the AugmentVisContext,
* which is just a saved object ID.
*/

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() {
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,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { i18n } from '@osd/i18n';
import { get } from 'lodash';
import { CoreStart } from 'opensearch-dashboards/public';
import { VisualizeEmbeddable } from '../../../visualizations/public';
import { EmbeddableContext } from '../../../embeddable/public';
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { Action, IncompatibleActionError } from '../../../ui_actions/public';
import { openViewEventsFlyout } from './open_events_flyout';

export const VIEW_EVENTS_OPTION_ACTION = 'VIEW_EVENTS_OPTION_ACTION';

export class ViewEventsOptionAction implements Action<EmbeddableContext> {
public readonly type = VIEW_EVENTS_OPTION_ACTION;
public readonly id = VIEW_EVENTS_OPTION_ACTION;
public order = 1;

constructor(private core: CoreStart) {}

public getIconType(): EuiIconType {
return 'apmTrace';
}

public getDisplayName() {
return i18n.translate('dashboard.actions.viewEvents.displayName', {
defaultMessage: 'View Events',
});
}

public async isCompatible({ embeddable }: EmbeddableContext) {
// TODO: add the logic for compatibility here, probably from some helper fn.
// see https://github.com/opensearch-project/OpenSearch-Dashboards/issues/3268
return true;
}

public async execute({ embeddable }: EmbeddableContext) {
if (!(await this.isCompatible({ embeddable }))) {
throw new IncompatibleActionError();
}

const visEmbeddable = embeddable as VisualizeEmbeddable;
const savedObjectId = get(visEmbeddable.getInput(), 'savedObjectId', '');

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>
);
}
64 changes: 64 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,64 @@
/*
* 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 { 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">
<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>
);
}
25 changes: 25 additions & 0 deletions src/plugins/vis_augmenter/public/components/error_flyout_body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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

interface Props {
errorMessage: string;
}

export function ErrorFlyoutBody(props: Props) {
return (
<EuiFlyoutBody>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<EuiCallOut color="danger" iconType="alert">
{props.errorMessage}
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutBody>
);
}
Loading

0 comments on commit 63c138d

Please sign in to comment.