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

add dashboard context menus #490

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions opensearch_dashboards.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
{
"id": "alertingDashboards",
"version": "2.5.0.0",
"opensearchDashboardsVersion": "2.5.0",
"opensearchDashboardsVersion": "2.3.0",
"configPath": ["opensearch_alerting"],
"requiredPlugins": [],
"requiredPlugins": [
"uiActions",
"dashboard",
"embeddable",
"opensearchDashboardsReact",
"savedObjects"
],
"server": true,
"ui": true
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
}
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.16.5",
"@elastic/elastic-eslint-config-kibana": "link:../../packages/opensearch-eslint-config-opensearch-dashboards",
"@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards",
"cypress": "^6.0.0",
"husky": "^3.0.0",
"lint-staged": "^10.0.0",
"@babel/plugin-transform-modules-commonjs": "^7.16.5"
"lint-staged": "^10.0.0"
},
"dependencies": {
"brace": "0.11.1",
Expand Down
68 changes: 68 additions & 0 deletions public/actions/alerting_dashboard_action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { IEmbeddable } from '../../../../src/plugins/dashboard/public/embeddable_plugin';
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
} from '../../../../src/plugins/dashboard/public';
import { IncompatibleActionError, createAction } from '../../../../src/plugins/ui_actions/public';
import { isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
import { Action, DEFAULT_ACTION } from '../../../../src/plugins/ui_actions/public';

export const ACTION_ALERTING = 'alerting';

function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer {
return embeddable.type === DASHBOARD_CONTAINER_TYPE;
}

export interface ActionContext {
embeddable: IEmbeddable;
}

export interface CreateOptions {
grouping: Action['grouping'];
title: JSX.Element | string;
icon: EuiIconType;
id: string;
order: number;
onClick: Function;
}

export const createAlertingAction = ({
grouping,
title,
icon,
id,
order,
onClick,
}: CreateOptions) =>
createAction({
id,
order,
getDisplayName: ({ embeddable }: ActionContext) => {
if (!embeddable.parent || !isDashboard(embeddable.parent)) {
throw new IncompatibleActionError();
}
return title;
},
getIconType: () => icon,
type: DEFAULT_ACTION,
grouping,
// Do not show actions for certin visualizations
isCompatible: async ({ embeddable }: ActionContext) => {
const paramsType = embeddable.vis?.params?.type;
const seriesParams = embeddable.vis?.params?.seriesParams || [];
const series = embeddable.vis?.params?.series || [];
const isLineGraph =
seriesParams.find((item) => item.type === 'line') ||
series.find((item) => item.chart_type === 'line');
const isValidVis = isLineGraph && paramsType !== 'table';
return Boolean(embeddable.parent && isDashboard(embeddable.parent) && isValidVis);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}

onClick({ embeddable });
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import {
EuiFlexItem,
EuiFlexGroup,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiTitle,
EuiSpacer,
EuiButton,
EuiButtonEmpty,
EuiFormFieldset,
EuiCheckableCard,
} from '@elastic/eui';
import './styles.scss';
import CreateNew from './CreateNew';
import AssociateExisting from './AssociateExisting';

function AddAlertingMonitor({
embeddable,
closeFlyout,
core,
services,
mode,
setMode,
monitors,
selectedMonitorId,
setSelectedMonitorId,
index,
}) {
const onCreate = () => {
console.log(`Current mode: ${mode}`);
const event = new Event('createMonitor');
document.dispatchEvent(event);
closeFlyout();
};

return (
<div className="add-alerting-monitor">
<EuiFlyoutHeader hasBorder>
<EuiTitle>
<h2 id="add-alerting-monitor__title">Add alerting monitor</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<div className="add-alerting-monitor__scroll">
<EuiFormFieldset
legend={{
display: 'hidden',
children: (
<EuiTitle>
<span>Options to create a new monitor or associate an existing monitor</span>
</EuiTitle>
),
}}
className="add-alerting-monitor__modes"
>
{[
{
id: 'add-alerting-monitor__create',
label: 'Create new monitor',
value: 'create',
},
{
id: 'add-alerting-monitor__existing',
label: 'Associate existing monitor',
value: 'existing',
},
].map((option) => (
<EuiCheckableCard
{...{
...option,
key: option.id,
name: option.id,
checked: option.value === mode,
onChange: () => setMode(option.value),
}}
/>
))}
</EuiFormFieldset>
<EuiSpacer size="m" />
{mode === 'create' && (
<CreateNew {...{ embeddable, closeFlyout, core, services, index }} />
)}
{mode === 'existing' && (
<AssociateExisting
{...{
embeddable,
closeFlyout,
core,
services,
monitors,
selectedMonitorId,
setSelectedMonitorId,
}}
/>
)}
</div>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={closeFlyout}>Cancel</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={onCreate} fill>
{mode === 'existing' ? 'Associate' : 'Create'} monitor
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</div>
);
}

export default AddAlertingMonitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { shallow } from 'enzyme';
import AddAlertingMonitor from './AddAlertingMonitor';

describe('AddAlertingMonitor', () => {
test('renders', () => {
const wrapper = shallow(<AddAlertingMonitor {...{ embeddable: { getTitle: () => '' } }} />);
expect(wrapper).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useMemo } from 'react';
import {
EuiTitle,
EuiSpacer,
EuiIcon,
EuiText,
EuiComboBox,
EuiLoadingSpinner,
EuiLink,
EuiFlexGroup,
EuiFlexItem,
EuiHealth,
EuiHorizontalRule,
} from '@elastic/eui';
import { stateToLabel } from '../../../../utils/contextMenu/monitors';
import { dateOptionsShort } from '../../../../utils/contextMenu/helpers';
import './styles.scss';

function AssociateExisting({ monitors, selectedMonitorId, setSelectedMonitorId }) {
const selectedOptions = useMemo(() => {
if (!monitors || !selectedMonitorId) {
return [];
}

const monitor = (monitors || []).find((monitor) => monitor.id === selectedMonitorId);
return monitor ? [{ label: monitor.name }] : [];
}, [selectedMonitorId, monitors]);
const monitor = useMemo(
() =>
monitors && selectedMonitorId && monitors.find((monitor) => monitor.id === selectedMonitorId),
[selectedMonitorId, monitors]
);
const options = useMemo(() => {
if (!monitors) {
return [];
}

return monitors.map((monitor) => ({
label: monitor.name,
}));
}, [monitors]);

return (
<div className="associate-existing">
<EuiText size="xs">
<p>
This is a short description of the feature to get users excited. Learn more in the
documentation.{' '}
<a
href="https://opensearch.org/docs/latest/monitoring-plugins/alerting/index/"
target="_blank"
>
Learn more <EuiIcon type="popout" />
</a>
</p>
</EuiText>
<EuiSpacer size="l" />
<EuiTitle size="s">
<h3>Select monitor to associate</h3>
</EuiTitle>
<EuiSpacer size="m" />
{!monitors && <EuiLoadingSpinner size="l" />}
{monitors && (
<EuiComboBox
id="associate-existing__select"
options={options}
selectedOptions={selectedOptions}
onChange={(selectedOptions) => {
let id = null;

if (selectedOptions && selectedOptions.length) {
const match = monitors.find((monitor) => monitor.name === selectedOptions[0].label);
id = match && match.id;
}

setSelectedMonitorId(id);
}}
aria-label="Select monitor to associate"
isClearable
singleSelection
placeholder="Search a monitor"
/>
)}
<EuiSpacer size="xl" />
{monitor && (
<>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem>
<EuiText>
<h4>{monitor.name}</h4>
</EuiText>
<EuiSpacer size="s" />
<EuiHealth color={stateToLabel[monitor.state].color}>
{stateToLabel[monitor.state].label}
</EuiHealth>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink href="/app/alerting" external>
View monitor page
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule margin="m" />
<ul className="associate-existing__monitor-details">
{[
['Type', (monitor) => monitor.type],
['Indexes', (monitor) => monitor.indexes],
['Triggers', (monitor) => monitor.triggers.length],
['Active alerts', (monitor) => monitor.activeAlerts],
[
'Last alert',
(monitor) =>
new Intl.DateTimeFormat('default', dateOptionsShort).format(monitor.date),
],
].map(([label, getValue]) => (
<li key={label}>
<EuiText>
<strong>{label}</strong>: {getValue(monitor)}
</EuiText>
</li>
))}
</ul>
</>
)}
</div>
);
}

export default AssociateExisting;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { shallow } from 'enzyme';
import AssociateExisting from './AssociateExisting';

describe('AssociateExisting', () => {
test('renders', () => {
const wrapper = shallow(<AssociateExisting {...{ embeddable: { getTitle: () => '' } }} />);
expect(wrapper).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import AssociateExisting from './AssociateExisting';

export default AssociateExisting;
Loading