mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Event annotations] Individual annotation editing from library (#163346)
## Summary Resolve https://github.com/elastic/kibana/issues/158774 Part of https://github.com/elastic/kibana/issues/159053 <img width="1920" alt="Screenshot 2023-09-13 at 2 00 25 PM" src="69cfe07e
-d442-462b-91c5-395d6040c383"> <img width="1920" alt="Screenshot 2023-09-13 at 2 00 09 PM" src="260aedbe
-31d0-415a-b387-10a9b13bf9a6"> <img width="1920" alt="Screenshot 2023-09-13 at 2 01 07 PM" src="9672010b
-d49b-4041-acf1-33d3baec1e9a"> ### Known issues - [ ] ~Responsive layout~ **Proposal:** don't optimize for mobile - [x] Recovering embeddable from problematic data view state - [x] margin around dimension buttons - [x] Functional test coverage ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
This commit is contained in:
parent
f94b4c2755
commit
172de682c5
59 changed files with 2739 additions and 554 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -364,6 +364,7 @@ packages/kbn-eslint-plugin-telemetry @elastic/actionable-observability
|
|||
x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security
|
||||
packages/kbn-event-annotation-common @elastic/kibana-visualizations
|
||||
packages/kbn-event-annotation-components @elastic/kibana-visualizations
|
||||
src/plugins/event_annotation_listing @elastic/kibana-visualizations
|
||||
src/plugins/event_annotation @elastic/kibana-visualizations
|
||||
x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops
|
||||
x-pack/plugins/event_log @elastic/response-ops
|
||||
|
|
34
.i18nrc.json
34
.i18nrc.json
|
@ -6,17 +6,12 @@
|
|||
"apmOss": "src/plugins/apm_oss",
|
||||
"autocomplete": "packages/kbn-securitysolution-autocomplete/src",
|
||||
"bfetch": "src/plugins/bfetch",
|
||||
"cases": [
|
||||
"packages/kbn-cases-components"
|
||||
],
|
||||
"cases": ["packages/kbn-cases-components"],
|
||||
"cellActions": "packages/kbn-cell-actions",
|
||||
"charts": "src/plugins/charts",
|
||||
"console": "src/plugins/console",
|
||||
"contentManagement": "packages/content-management",
|
||||
"core": [
|
||||
"src/core",
|
||||
"packages/core"
|
||||
],
|
||||
"core": ["src/core", "packages/core"],
|
||||
"customIntegrations": "src/plugins/custom_integrations",
|
||||
"customIntegrationsPackage": "packages/kbn-custom-integrations",
|
||||
"dashboard": "src/plugins/dashboard",
|
||||
|
@ -27,10 +22,7 @@
|
|||
"dataViews": "src/plugins/data_views",
|
||||
"defaultNavigation": "packages/default-nav",
|
||||
"devTools": "src/plugins/dev_tools",
|
||||
"discover": [
|
||||
"src/plugins/discover",
|
||||
"packages/kbn-discover-utils"
|
||||
],
|
||||
"discover": ["src/plugins/discover", "packages/kbn-discover-utils"],
|
||||
"savedSearch": "src/plugins/saved_search",
|
||||
"embeddableApi": "src/plugins/embeddable",
|
||||
"embeddableExamples": "examples/embeddable_examples",
|
||||
|
@ -52,6 +44,7 @@
|
|||
"expressionShape": "src/plugins/expression_shape",
|
||||
"expressionTagcloud": "src/plugins/chart_expressions/expression_tagcloud",
|
||||
"eventAnnotation": "src/plugins/event_annotation",
|
||||
"eventAnnotationListing": "src/plugins/event_annotation_listing",
|
||||
"eventAnnotationCommon": "packages/kbn-event-annotation-common",
|
||||
"eventAnnotationComponents": "packages/kbn-event-annotation-components",
|
||||
"fieldFormats": "src/plugins/field_formats",
|
||||
|
@ -110,17 +103,9 @@
|
|||
"languageDocumentationPopover": "packages/kbn-language-documentation-popover/src",
|
||||
"textBasedLanguages": "src/plugins/text_based_languages",
|
||||
"statusPage": "src/legacy/core_plugins/status_page",
|
||||
"telemetry": [
|
||||
"src/plugins/telemetry",
|
||||
"src/plugins/telemetry_management_section"
|
||||
],
|
||||
"timelion": [
|
||||
"src/plugins/vis_types/timelion"
|
||||
],
|
||||
"uiActions": [
|
||||
"src/plugins/ui_actions",
|
||||
"packages/kbn-ui-actions-browser"
|
||||
],
|
||||
"telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"],
|
||||
"timelion": ["src/plugins/vis_types/timelion"],
|
||||
"uiActions": ["src/plugins/ui_actions", "packages/kbn-ui-actions-browser"],
|
||||
"uiActionsEnhanced": "src/plugins/ui_actions_enhanced",
|
||||
"uiActionsExamples": "examples/ui_action_examples",
|
||||
"usageCollection": "src/plugins/usage_collection",
|
||||
|
@ -140,10 +125,7 @@
|
|||
"visTypeXy": "src/plugins/vis_types/xy",
|
||||
"visualizations": "src/plugins/visualizations",
|
||||
"visualizationUiComponents": "packages/kbn-visualization-ui-components",
|
||||
"unifiedDocViewer": [
|
||||
"src/plugins/unified_doc_viewer",
|
||||
"packages/kbn-unified-doc-viewer"
|
||||
],
|
||||
"unifiedDocViewer": ["src/plugins/unified_doc_viewer", "packages/kbn-unified-doc-viewer"],
|
||||
"unifiedSearch": "src/plugins/unified_search",
|
||||
"unifiedFieldList": "packages/kbn-unified-field-list",
|
||||
"unifiedHistogram": "src/plugins/unified_histogram",
|
||||
|
|
|
@ -102,6 +102,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|
|||
|The Event Annotation service contains expressions for event annotations
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/event_annotation_listing/README.md[eventAnnotationListing]
|
||||
|This plugin contains the library listing page for event annotation groups.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/expression_error/README.md[expressionError]
|
||||
|Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image.
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -222,9 +222,11 @@ Annotations support two placement types:
|
|||
|
||||
* *Custom query* — Displays annotations based on custom {es} queries. For detailed information about queries, check <<semi-structured-search>>.
|
||||
|
||||
Create the annotation layer.
|
||||
Any annotation layer can be saved as an annotation group to the *Visualize Library* in order to reuse it in other visualizations. Any changes made to the annotation group will be reflected in all visualizations to which it is added.
|
||||
|
||||
. In the layer pane, click *Add layer > Annotations*.
|
||||
Create a new annotation layer.
|
||||
|
||||
. In the layer pane, click *Add layer > Annotations > New annotation*.
|
||||
|
||||
. Select the {data-source} for the annotation.
|
||||
|
||||
|
@ -263,6 +265,20 @@ Specify the annotation appearance.
|
|||
|
||||
. To close, click *X*.
|
||||
|
||||
Save the annotation group to the library.
|
||||
|
||||
. In the layer pane, on your annotation layer, click image:images/lens_saveAnnotationLayerButton_8.9.0.png[Save button on annotations layer].
|
||||
|
||||
. Enter the *Title*, *Description*, and add any applicable <<managing-tags,*Tags*>>.
|
||||
|
||||
. Click *Save group*.
|
||||
|
||||
Add a library annotation group to a visualization.
|
||||
|
||||
. In the layer pane, click *Add layer > Annotations > Load from library*.
|
||||
|
||||
. Select the annotation group you want to use.
|
||||
|
||||
[float]
|
||||
[[add-reference-lines]]
|
||||
==== Add reference lines
|
||||
|
|
|
@ -401,6 +401,7 @@
|
|||
"@kbn/eso-plugin": "link:x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin",
|
||||
"@kbn/event-annotation-common": "link:packages/kbn-event-annotation-common",
|
||||
"@kbn/event-annotation-components": "link:packages/kbn-event-annotation-components",
|
||||
"@kbn/event-annotation-listing-plugin": "link:src/plugins/event_annotation_listing",
|
||||
"@kbn/event-annotation-plugin": "link:src/plugins/event_annotation",
|
||||
"@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log",
|
||||
"@kbn/event-log-plugin": "link:x-pack/plugins/event_log",
|
||||
|
|
|
@ -946,7 +946,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
|||
|
||||
if (!showFetchError && hasNoItems) {
|
||||
return (
|
||||
<PageTemplate panelled isEmptyState={true}>
|
||||
<PageTemplate isEmptyState={true}>
|
||||
<KibanaPageTemplate.Section
|
||||
aria-labelledby={hasInitialFetchReturned ? headingId : undefined}
|
||||
>
|
||||
|
|
|
@ -322,7 +322,6 @@ const AnnotationEditorControls = ({
|
|||
)}
|
||||
|
||||
<ColorPicker
|
||||
overwriteColor={currentAnnotation.color}
|
||||
defaultColor={isRange ? defaultAnnotationRangeColor : defaultAnnotationColor}
|
||||
showAlpha={isRange}
|
||||
setConfig={update}
|
||||
|
|
|
@ -97,6 +97,7 @@ export const ConfigPanelQueryAnnotation = ({
|
|||
})}
|
||||
>
|
||||
<FieldPicker
|
||||
compressed
|
||||
options={options}
|
||||
selectedOptions={
|
||||
selectedField
|
||||
|
|
|
@ -181,6 +181,7 @@ export function TooltipSection({
|
|||
data-test-subj={`lnsXY-annotation-tooltip-${index}`}
|
||||
>
|
||||
<FieldPicker
|
||||
compressed
|
||||
selectedOptions={
|
||||
value
|
||||
? [
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
htmlIdGenerator,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import type {
|
||||
EventAnnotationConfig,
|
||||
EventAnnotationGroupConfig,
|
||||
} from '@kbn/event-annotation-common';
|
||||
import { GroupEditorControls, isGroupValid } from './group_editor_controls';
|
||||
|
||||
export const GroupEditorFlyout = ({
|
||||
group,
|
||||
updateGroup,
|
||||
onClose: parentOnClose,
|
||||
onSave,
|
||||
savedObjectsTagging,
|
||||
dataViews,
|
||||
createDataView,
|
||||
queryInputServices,
|
||||
}: {
|
||||
group: EventAnnotationGroupConfig;
|
||||
updateGroup: (newGroup: EventAnnotationGroupConfig) => void;
|
||||
onClose: () => void;
|
||||
onSave: () => void;
|
||||
savedObjectsTagging: SavedObjectsTaggingApi;
|
||||
dataViews: DataView[];
|
||||
createDataView: (spec: DataViewSpec) => Promise<DataView>;
|
||||
queryInputServices: QueryInputServices;
|
||||
}) => {
|
||||
const flyoutHeadingId = useMemo(() => htmlIdGenerator()(), []);
|
||||
const flyoutBodyOverflowRef = useRef<Element | null>(null);
|
||||
useEffect(() => {
|
||||
if (!flyoutBodyOverflowRef.current) {
|
||||
flyoutBodyOverflowRef.current = document.querySelector('.euiFlyoutBody__overflow');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [hasAttemptedSave, setHasAttemptedSave] = useState(false);
|
||||
|
||||
const resetContentScroll = useCallback(
|
||||
() => flyoutBodyOverflowRef.current && flyoutBodyOverflowRef.current.scroll(0, 0),
|
||||
[]
|
||||
);
|
||||
|
||||
const [selectedAnnotation, _setSelectedAnnotation] = useState<EventAnnotationConfig>();
|
||||
const setSelectedAnnotation = useCallback(
|
||||
(newValue: EventAnnotationConfig | undefined) => {
|
||||
if ((!newValue && selectedAnnotation) || (newValue && !selectedAnnotation))
|
||||
resetContentScroll();
|
||||
_setSelectedAnnotation(newValue);
|
||||
},
|
||||
[resetContentScroll, selectedAnnotation]
|
||||
);
|
||||
|
||||
const onClose = () => (selectedAnnotation ? setSelectedAnnotation(undefined) : parentOnClose());
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size={'s'}>
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby={flyoutHeadingId}>
|
||||
<EuiTitle size="s">
|
||||
<h2 id={flyoutHeadingId}>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.groupEditorFlyout.title"
|
||||
defaultMessage="Edit annotation group"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<GroupEditorControls
|
||||
group={group}
|
||||
update={updateGroup}
|
||||
selectedAnnotation={selectedAnnotation}
|
||||
setSelectedAnnotation={setSelectedAnnotation}
|
||||
TagSelector={savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector}
|
||||
dataViews={dataViews}
|
||||
createDataView={createDataView}
|
||||
queryInputServices={queryInputServices}
|
||||
showValidation={hasAttemptedSave}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{selectedAnnotation ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="arrowLeft"
|
||||
data-test-subj="backToGroupSettings"
|
||||
onClick={() => setSelectedAnnotation(undefined)}
|
||||
>
|
||||
<FormattedMessage id="eventAnnotationComponents.edit.back" defaultMessage="Back" />
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="cancelGroupEdit" onClick={onClose}>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.edit.cancel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="save"
|
||||
data-test-subj="saveAnnotationGroup"
|
||||
fill
|
||||
onClick={() => {
|
||||
setHasAttemptedSave(true);
|
||||
|
||||
if (isGroupValid(group)) {
|
||||
onSave();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.edit.save"
|
||||
defaultMessage="Save annotation group"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -9,8 +9,4 @@
|
|||
// TODO - is this file needed?
|
||||
export { AnnotationEditorControls, annotationsIconSet } from './annotation_editor_controls';
|
||||
|
||||
export * from './group_editor_controls';
|
||||
|
||||
export * from './get_annotation_accessor';
|
||||
|
||||
export * from './table_list';
|
||||
|
|
|
@ -10,7 +10,7 @@ export {
|
|||
AnnotationEditorControls,
|
||||
annotationsIconSet,
|
||||
} from './components/annotation_editor_controls';
|
||||
export { EventAnnotationGroupTableList, getAnnotationAccessor } from './components';
|
||||
export { getAnnotationAccessor } from './components';
|
||||
export {
|
||||
defaultAnnotationColor,
|
||||
defaultAnnotationRangeColor,
|
||||
|
|
|
@ -10,4 +10,5 @@ module.exports = {
|
|||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-event-annotation-components'],
|
||||
setupFiles: ['jest-canvas-mock'],
|
||||
};
|
||||
|
|
|
@ -22,19 +22,11 @@
|
|||
"@kbn/unified-field-list",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/data-plugin",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/saved-objects-tagging-oss-plugin",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
"@kbn/dom-drag-drop",
|
||||
"@kbn/content-management-table-list-view-table",
|
||||
"@kbn/content-management-tabbed-table-list-view",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/event-annotation-common",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/saved-objects-finder-plugin",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-saved-objects-api-browser",
|
||||
"@kbn/expressions-plugin",
|
||||
"@kbn/content-management-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ pageLoadAssetSize:
|
|||
embeddableEnhanced: 22107
|
||||
enterpriseSearch: 50858
|
||||
esUiShared: 326654
|
||||
eventAnnotation: 48565
|
||||
eventAnnotation: 30000
|
||||
eventAnnotationListing: 25841
|
||||
exploratoryView: 74673
|
||||
expressionError: 22127
|
||||
expressionGauge: 25000
|
||||
|
|
|
@ -97,12 +97,12 @@ export const ColorPicker = ({
|
|||
onChange={handleColor}
|
||||
color={isDisabled ? '' : colorText}
|
||||
disabled={isDisabled}
|
||||
placeholder={
|
||||
defaultColor?.toUpperCase() ||
|
||||
i18n.translate('visualizationUiComponents.colorPicker.seriesColor.auto', {
|
||||
defaultMessage: 'Auto',
|
||||
})
|
||||
}
|
||||
placeholder={' '}
|
||||
onBlur={() => {
|
||||
if (!colorText) {
|
||||
setColorText(overwriteColor ?? defaultColor);
|
||||
}
|
||||
}}
|
||||
aria-label={inputLabel}
|
||||
showAlpha={showAlpha}
|
||||
swatches={
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { isPromise } from '@kbn/std';
|
||||
|
@ -21,83 +21,45 @@ interface Props {
|
|||
input?: EmbeddableInput;
|
||||
}
|
||||
|
||||
interface State {
|
||||
node?: ReactNode;
|
||||
}
|
||||
|
||||
export class EmbeddableRoot extends React.Component<Props, State> {
|
||||
private root?: React.RefObject<HTMLDivElement>;
|
||||
private alreadyMounted: boolean = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.root = React.createRef();
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
private updateNode = (node: MaybePromise<ReactNode>) => {
|
||||
if (isPromise(node)) {
|
||||
node.then(this.updateNode);
|
||||
export const EmbeddableRoot: React.FC<Props> = ({ embeddable, loading, error, input }) => {
|
||||
const [node, setNode] = useState<ReactNode | undefined>();
|
||||
const [embeddableHasMounted, setEmbeddableHasMounted] = useState(false);
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const updateNode = useCallback((newNode: MaybePromise<ReactNode>) => {
|
||||
if (isPromise(newNode)) {
|
||||
newNode.then(updateNode);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ node });
|
||||
};
|
||||
setNode(newNode);
|
||||
}, []);
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.root?.current || !this.props.embeddable) {
|
||||
useEffect(() => {
|
||||
if (!rootRef.current || !embeddable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.alreadyMounted = true;
|
||||
this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined);
|
||||
}
|
||||
setEmbeddableHasMounted(true);
|
||||
updateNode(embeddable.render(rootRef.current) ?? undefined);
|
||||
embeddable.render(rootRef.current);
|
||||
}, [updateNode, embeddable]);
|
||||
|
||||
public componentDidUpdate(prevProps?: Props) {
|
||||
let justRendered = false;
|
||||
if (this.root?.current && this.props.embeddable && !this.alreadyMounted) {
|
||||
this.alreadyMounted = true;
|
||||
this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined);
|
||||
justRendered = true;
|
||||
useEffect(() => {
|
||||
if (input && embeddable && embeddableHasMounted) {
|
||||
embeddable.updateInput(input);
|
||||
}
|
||||
}, [input, embeddable, embeddableHasMounted]);
|
||||
|
||||
if (
|
||||
!justRendered &&
|
||||
this.root &&
|
||||
this.root.current &&
|
||||
this.props.embeddable &&
|
||||
this.alreadyMounted &&
|
||||
this.props.input &&
|
||||
prevProps?.input !== this.props.input
|
||||
) {
|
||||
this.props.embeddable.updateInput(this.props.input);
|
||||
}
|
||||
}
|
||||
|
||||
public shouldComponentUpdate({ embeddable, error, input, loading }: Props, { node }: State) {
|
||||
return Boolean(
|
||||
error !== this.props.error ||
|
||||
loading !== this.props.loading ||
|
||||
embeddable !== this.props.embeddable ||
|
||||
(this.root && this.root.current && embeddable && !this.alreadyMounted) ||
|
||||
input !== this.props.input ||
|
||||
node !== this.state.node
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div ref={this.root}>{this.state.node}</div>
|
||||
{this.props.loading && <EuiLoadingSpinner data-test-subj="embedSpinner" />}
|
||||
{this.props.error && (
|
||||
<EmbeddableErrorHandler embeddable={this.props.embeddable} error={this.props.error}>
|
||||
{({ message }) => <EuiText data-test-subj="embedError">{message}</EuiText>}
|
||||
</EmbeddableErrorHandler>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div ref={rootRef}>{node}</div>
|
||||
{loading && <EuiLoadingSpinner data-test-subj="embedSpinner" />}
|
||||
{error && (
|
||||
<EmbeddableErrorHandler embeddable={embeddable} error={error}>
|
||||
{({ message }) => <EuiText data-test-subj="embedError">{message}</EuiText>}
|
||||
</EmbeddableErrorHandler>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ export { eventAnnotationGroup } from './event_annotation_group';
|
|||
export type { EventAnnotationGroupArgs } from './event_annotation_group';
|
||||
|
||||
export type { FetchEventAnnotationsArgs } from './fetch_event_annotations/types';
|
||||
export type { EventAnnotationArgs, EventAnnotationOutput } from './types';
|
||||
export type { EventAnnotationOutput } from './types';
|
||||
|
||||
export type {
|
||||
EventAnnotationGroupGetIn,
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"data",
|
||||
"savedObjectsFinder",
|
||||
"dataViews",
|
||||
"kibanaReact",
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"common"
|
||||
|
|
|
@ -10,7 +10,6 @@ import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
|
|||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
|
||||
import type { ExpressionsSetup } from '@kbn/expressions-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import {
|
||||
ContentManagementPublicSetup,
|
||||
ContentManagementPublicStart,
|
||||
|
@ -28,8 +27,6 @@ import {
|
|||
eventAnnotationGroup,
|
||||
} from '../common';
|
||||
import { getFetchEventAnnotations } from './fetch_event_annotations';
|
||||
import type { EventAnnotationListingPageServices } from './get_table_list';
|
||||
import { ANNOTATIONS_LISTING_VIEW_ID } from '../common/constants';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
|
||||
export interface EventAnnotationStartDependencies {
|
||||
|
@ -76,46 +73,6 @@ export class EventAnnotationPlugin
|
|||
defaultMessage: 'Annotation group',
|
||||
}),
|
||||
});
|
||||
|
||||
dependencies.visualizations.listingViewRegistry.add({
|
||||
title: i18n.translate('eventAnnotation.listingViewTitle', {
|
||||
defaultMessage: 'Annotation groups',
|
||||
}),
|
||||
id: ANNOTATIONS_LISTING_VIEW_ID,
|
||||
getTableList: async (props) => {
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
|
||||
const eventAnnotationService = await new EventAnnotationService(
|
||||
coreStart,
|
||||
pluginsStart.contentManagement
|
||||
).getService();
|
||||
|
||||
const ids = await pluginsStart.dataViews.getIds();
|
||||
const dataViews = await Promise.all(ids.map((id) => pluginsStart.dataViews.get(id)));
|
||||
|
||||
const services: EventAnnotationListingPageServices = {
|
||||
core: coreStart,
|
||||
savedObjectsTagging: pluginsStart.savedObjectsTagging,
|
||||
eventAnnotationService,
|
||||
PresentationUtilContextProvider: pluginsStart.presentationUtil.ContextProvider,
|
||||
dataViews,
|
||||
createDataView: pluginsStart.dataViews.create.bind(pluginsStart.dataViews),
|
||||
queryInputServices: {
|
||||
http: coreStart.http,
|
||||
docLinks: coreStart.docLinks,
|
||||
notifications: coreStart.notifications,
|
||||
uiSettings: coreStart.uiSettings,
|
||||
dataViews: pluginsStart.dataViews,
|
||||
unifiedSearch: pluginsStart.unifiedSearch,
|
||||
data: pluginsStart.data,
|
||||
storage: new Storage(localStorage),
|
||||
},
|
||||
};
|
||||
|
||||
const { getTableList } = await import('./get_table_list');
|
||||
return getTableList(props, services);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(
|
||||
|
|
|
@ -34,13 +34,6 @@
|
|||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/event-annotation-components",
|
||||
"@kbn/event-annotation-common",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/content-management-table-list-view-table",
|
||||
"@kbn/content-management-tabbed-table-list-view",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/saved-objects-tagging-oss-plugin",
|
||||
"@kbn/dom-drag-drop",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/content-management-utils"
|
||||
],
|
||||
"exclude": [
|
||||
|
|
6
src/plugins/event_annotation_listing/.i18nrc.json
Executable file
6
src/plugins/event_annotation_listing/.i18nrc.json
Executable file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"prefix": "eventAnnotationsApplication",
|
||||
"paths": {
|
||||
"eventAnnotationsApplication": "."
|
||||
}
|
||||
}
|
3
src/plugins/event_annotation_listing/README.md
Normal file
3
src/plugins/event_annotation_listing/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Event Annotation listing
|
||||
|
||||
This plugin contains the library listing page for event annotation groups.
|
19
src/plugins/event_annotation_listing/jest.config.js
Normal file
19
src/plugins/event_annotation_listing/jest.config.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/src/plugins/event_annotation_listing'],
|
||||
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/event_annotation_listing',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/src/plugins/event_annotation_listing/{common,public,server}/**/*.{ts,tsx}',
|
||||
],
|
||||
setupFiles: ['jest-canvas-mock'],
|
||||
};
|
30
src/plugins/event_annotation_listing/kibana.jsonc
Normal file
30
src/plugins/event_annotation_listing/kibana.jsonc
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/event-annotation-listing-plugin",
|
||||
"owner": "@elastic/kibana-visualizations",
|
||||
"description": "The listing page for event annotations.",
|
||||
"plugin": {
|
||||
"id": "eventAnnotationListing",
|
||||
"server": false,
|
||||
"browser": true,
|
||||
"requiredPlugins": [
|
||||
"savedObjectsManagement",
|
||||
"eventAnnotation",
|
||||
"data",
|
||||
"presentationUtil",
|
||||
"visualizations",
|
||||
"dataViews",
|
||||
"unifiedSearch",
|
||||
"kibanaUtils",
|
||||
"contentManagement",
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"savedObjectsTagging",
|
||||
"lens",
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
],
|
||||
"extraPublicDirs": []
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
exports[`group editor flyout renders controls 1`] = `
|
||||
Object {
|
||||
"TagSelector": [MockFunction],
|
||||
"createDataView": [MockFunction],
|
||||
"dataViews": Array [
|
||||
Object {
|
||||
"id": "some-id",
|
||||
|
@ -29,6 +28,7 @@ Object {
|
|||
"tags": Array [],
|
||||
"title": "My group",
|
||||
},
|
||||
"isAdHocDataView": [Function],
|
||||
"queryInputServices": Object {},
|
||||
"selectedAnnotation": undefined,
|
||||
"setSelectedAnnotation": [Function],
|
|
@ -24,7 +24,7 @@ import { euiThemeVars } from '@kbn/ui-theme';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { EventAnnotationConfig } from '@kbn/event-annotation-common';
|
||||
import { createCopiedAnnotation } from '@kbn/event-annotation-common';
|
||||
import { getAnnotationAccessor } from '..';
|
||||
import { getAnnotationAccessor } from '@kbn/event-annotation-components';
|
||||
|
||||
export const AnnotationList = ({
|
||||
annotations,
|
||||
|
@ -40,7 +40,7 @@ export const AnnotationList = ({
|
|||
setNewAnnotationId(uuidv4());
|
||||
}, [annotations.length]);
|
||||
|
||||
const addAnnotationText = i18n.translate('eventAnnotationComponents.annotationList.add', {
|
||||
const addAnnotationText = i18n.translate('eventAnnotationListing.annotationList.add', {
|
||||
defaultMessage: 'Add annotation',
|
||||
});
|
||||
|
||||
|
@ -88,14 +88,26 @@ export const AnnotationList = ({
|
|||
const [{ dragging }] = useDragDropContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
css={css`
|
||||
background-color: ${euiThemeVars.euiColorLightestShade};
|
||||
padding: ${euiThemeVars.euiSizeS};
|
||||
border-radius: ${euiThemeVars.euiBorderRadius};
|
||||
overflow: hidden;
|
||||
|
||||
.domDragDrop-isActiveGroup {
|
||||
padding: ${euiThemeVars.euiSizeS};
|
||||
margin: -${euiThemeVars.euiSizeS} -${euiThemeVars.euiSizeS} 0 -${euiThemeVars.euiSizeS};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ReorderProvider>
|
||||
{annotations.map((annotation, index) => (
|
||||
<div
|
||||
key={index}
|
||||
css={css`
|
||||
margin-top: ${euiThemeVars.euiSizeS};
|
||||
position: relative; // this is to properly contain the absolutely-positioned drop target in DragDrop
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
<DragDrop
|
||||
|
@ -119,7 +131,7 @@ export const AnnotationList = ({
|
|||
}}
|
||||
>
|
||||
<DimensionButton
|
||||
groupLabel={i18n.translate('eventAnnotationComponents.groupEditor.addAnnotation', {
|
||||
groupLabel={i18n.translate('eventAnnotationListing.groupEditor.addAnnotation', {
|
||||
defaultMessage: 'Annotations',
|
||||
})}
|
||||
onClick={() => selectAnnotation(annotation)}
|
||||
|
@ -136,34 +148,28 @@ export const AnnotationList = ({
|
|||
))}
|
||||
</ReorderProvider>
|
||||
|
||||
<div
|
||||
css={css`
|
||||
margin-top: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
<DragDrop
|
||||
order={[annotations.length]}
|
||||
getCustomDropTarget={DropTargetSwapDuplicateCombine.getCustomDropTarget}
|
||||
getAdditionalClassesOnDroppable={
|
||||
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
||||
}
|
||||
dropTypes={dragging ? ['duplicate_compatible'] : []}
|
||||
value={{
|
||||
id: 'addAnnotation',
|
||||
humanData: {
|
||||
label: addAnnotationText,
|
||||
},
|
||||
}}
|
||||
onDrop={({ id: sourceId }) => addNewAnnotation(sourceId)}
|
||||
>
|
||||
<DragDrop
|
||||
order={[annotations.length]}
|
||||
getCustomDropTarget={DropTargetSwapDuplicateCombine.getCustomDropTarget}
|
||||
getAdditionalClassesOnDroppable={
|
||||
DropTargetSwapDuplicateCombine.getAdditionalClassesOnDroppable
|
||||
}
|
||||
dropTypes={dragging ? ['duplicate_compatible'] : []}
|
||||
value={{
|
||||
id: 'addAnnotation',
|
||||
humanData: {
|
||||
label: addAnnotationText,
|
||||
},
|
||||
}}
|
||||
onDrop={({ id: sourceId }) => addNewAnnotation(sourceId)}
|
||||
>
|
||||
<EmptyDimensionButton
|
||||
dataTestSubj="addAnnotation"
|
||||
label={addAnnotationText}
|
||||
ariaLabel={addAnnotationText}
|
||||
onClick={() => addNewAnnotation()}
|
||||
/>
|
||||
</DragDrop>
|
||||
</div>
|
||||
<EmptyDimensionButton
|
||||
dataTestSubj="addAnnotation"
|
||||
label={addAnnotationText}
|
||||
ariaLabel={addAnnotationText}
|
||||
onClick={() => addNewAnnotation()}
|
||||
/>
|
||||
</DragDrop>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -16,7 +16,7 @@ import { EuiTextAreaProps, EuiTextProps } from '@elastic/eui';
|
|||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import { AnnotationEditorControls, ENABLE_INDIVIDUAL_ANNOTATION_EDITING } from '..';
|
||||
import { AnnotationEditorControls } from '@kbn/event-annotation-components';
|
||||
|
||||
jest.mock('@elastic/eui', () => {
|
||||
return {
|
||||
|
@ -68,15 +68,9 @@ describe('event annotation group editor', () => {
|
|||
}
|
||||
selectedAnnotation={undefined}
|
||||
setSelectedAnnotation={setSelectedAnnotationMock}
|
||||
createDataView={(spec) =>
|
||||
Promise.resolve({
|
||||
id: spec.id,
|
||||
title: spec.title,
|
||||
toSpec: () => spec,
|
||||
} as unknown as DataView)
|
||||
}
|
||||
queryInputServices={{} as QueryInputServices}
|
||||
showValidation={false}
|
||||
isAdHocDataView={() => false}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -174,63 +168,54 @@ describe('event annotation group editor', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
if (ENABLE_INDIVIDUAL_ANNOTATION_EDITING) {
|
||||
it('adds a new annotation group', () => {
|
||||
act(() => {
|
||||
wrapper.find('button[data-test-subj="addAnnotation"]').simulate('click');
|
||||
});
|
||||
// it('adds a new annotation group', () => {
|
||||
// act(() => {
|
||||
// wrapper.find('button[data-test-subj="addAnnotation"]').simulate('click');
|
||||
// });
|
||||
|
||||
expect(updateMock).toHaveBeenCalledTimes(2);
|
||||
const newAnnotations = (updateMock.mock.calls[0][0] as EventAnnotationGroupConfig)
|
||||
.annotations;
|
||||
expect(newAnnotations.length).toBe(group.annotations.length + 1);
|
||||
expect(wrapper.exists(AnnotationEditorControls)); // annotation controls opened
|
||||
// expect(updateMock).toHaveBeenCalledTimes(2);
|
||||
// const newAnnotations = (updateMock.mock.calls[0][0] as EventAnnotationGroupConfig).annotations;
|
||||
// expect(newAnnotations.length).toBe(group.annotations.length + 1);
|
||||
// expect(wrapper.exists(AnnotationEditorControls)); // annotation controls opened
|
||||
// });
|
||||
|
||||
it('incorporates annotation updates into group', () => {
|
||||
const annotations = [getDefaultManualAnnotation('1', ''), getDefaultManualAnnotation('2', '')];
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps({
|
||||
selectedAnnotation: annotations[0],
|
||||
group: { ...group, annotations },
|
||||
});
|
||||
});
|
||||
|
||||
it('incorporates annotation updates into group', () => {
|
||||
const annotations = [
|
||||
getDefaultManualAnnotation('1', ''),
|
||||
getDefaultManualAnnotation('2', ''),
|
||||
];
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps({
|
||||
selectedAnnotation: annotations[0],
|
||||
group: { ...group, annotations },
|
||||
});
|
||||
});
|
||||
|
||||
wrapper.find(AnnotationEditorControls).prop('onAnnotationChange')({
|
||||
...annotations[0],
|
||||
color: 'newColor',
|
||||
});
|
||||
|
||||
expect(updateMock).toHaveBeenCalledTimes(1);
|
||||
expect(updateMock.mock.calls[0][0].annotations[0].color).toBe('newColor');
|
||||
expect(setSelectedAnnotationMock).toHaveBeenCalledTimes(1);
|
||||
wrapper.find(AnnotationEditorControls).prop('onAnnotationChange')({
|
||||
...annotations[0],
|
||||
color: 'newColor',
|
||||
});
|
||||
|
||||
it('removes an annotation from a group', () => {
|
||||
const annotations = [
|
||||
getDefaultManualAnnotation('1', ''),
|
||||
getDefaultManualAnnotation('2', ''),
|
||||
];
|
||||
expect(updateMock).toHaveBeenCalledTimes(1);
|
||||
expect(updateMock.mock.calls[0][0].annotations[0].color).toBe('newColor');
|
||||
expect(setSelectedAnnotationMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps({
|
||||
group: { ...group, annotations },
|
||||
});
|
||||
it('removes an annotation from a group', () => {
|
||||
const annotations = [getDefaultManualAnnotation('1', ''), getDefaultManualAnnotation('2', '')];
|
||||
|
||||
act(() => {
|
||||
wrapper.setProps({
|
||||
group: { ...group, annotations },
|
||||
});
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="indexPattern-dimension-remove"]')
|
||||
.last()
|
||||
.simulate('click');
|
||||
});
|
||||
|
||||
expect(updateMock).toHaveBeenCalledTimes(1);
|
||||
expect(updateMock.mock.calls[0][0].annotations).toEqual(annotations.slice(0, 1));
|
||||
});
|
||||
}
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="indexPattern-dimension-remove"]')
|
||||
.last()
|
||||
.simulate('click');
|
||||
});
|
||||
|
||||
expect(updateMock).toHaveBeenCalledTimes(1);
|
||||
expect(updateMock.mock.calls[0][0].annotations).toEqual(annotations.slice(0, 1));
|
||||
});
|
||||
});
|
|
@ -16,26 +16,30 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { SavedObjectsTaggingApiUiComponent } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import type {
|
||||
EventAnnotationConfig,
|
||||
EventAnnotationGroupConfig,
|
||||
} from '@kbn/event-annotation-common';
|
||||
import { EVENT_ANNOTATION_APP_NAME } from '../../constants';
|
||||
import { AnnotationEditorControls } from '../annotation_editor_controls';
|
||||
import {
|
||||
EVENT_ANNOTATION_APP_NAME,
|
||||
AnnotationEditorControls,
|
||||
} from '@kbn/event-annotation-components';
|
||||
import { AnnotationList } from './annotation_list';
|
||||
|
||||
export const ENABLE_INDIVIDUAL_ANNOTATION_EDITING = false;
|
||||
|
||||
const isTitleValid = (title: string) => Boolean(title.length);
|
||||
|
||||
export const isGroupValid = (group: EventAnnotationGroupConfig) => isTitleValid(group.title);
|
||||
const isDataViewValid = (dataView: DataView | undefined) => Boolean(dataView?.id);
|
||||
|
||||
export const isGroupValid = (group: EventAnnotationGroupConfig, dataViews: DataView[]) =>
|
||||
isTitleValid(group.title) &&
|
||||
isDataViewValid(dataViews.find(({ id }) => id === group.indexPatternId));
|
||||
|
||||
export const GroupEditorControls = ({
|
||||
group,
|
||||
|
@ -43,10 +47,10 @@ export const GroupEditorControls = ({
|
|||
setSelectedAnnotation: _setSelectedAnnotation,
|
||||
selectedAnnotation,
|
||||
TagSelector,
|
||||
dataViews: globalDataViews,
|
||||
createDataView,
|
||||
dataViews,
|
||||
queryInputServices,
|
||||
showValidation,
|
||||
isAdHocDataView,
|
||||
}: {
|
||||
group: EventAnnotationGroupConfig;
|
||||
update: (group: EventAnnotationGroupConfig) => void;
|
||||
|
@ -54,19 +58,10 @@ export const GroupEditorControls = ({
|
|||
setSelectedAnnotation: (annotation: EventAnnotationConfig) => void;
|
||||
TagSelector: SavedObjectsTaggingApiUiComponent['SavedObjectSaveModalTagSelector'];
|
||||
dataViews: DataView[];
|
||||
createDataView: (spec: DataViewSpec) => Promise<DataView>;
|
||||
queryInputServices: QueryInputServices;
|
||||
showValidation: boolean;
|
||||
isAdHocDataView: (id: string) => boolean;
|
||||
}) => {
|
||||
// save the spec for the life of the component since the user might change their mind after selecting another data view
|
||||
const [adHocDataView, setAdHocDataView] = useState<DataView>();
|
||||
|
||||
useEffect(() => {
|
||||
if (group.dataViewSpec) {
|
||||
createDataView(group.dataViewSpec).then(setAdHocDataView);
|
||||
}
|
||||
}, [createDataView, group.dataViewSpec]);
|
||||
|
||||
const setSelectedAnnotation = useCallback(
|
||||
(newSelection: EventAnnotationConfig) => {
|
||||
update({
|
||||
|
@ -80,45 +75,38 @@ export const GroupEditorControls = ({
|
|||
[_setSelectedAnnotation, group, update]
|
||||
);
|
||||
|
||||
const dataViews = useMemo(() => {
|
||||
const items = [...globalDataViews];
|
||||
if (adHocDataView) {
|
||||
items.push(adHocDataView);
|
||||
}
|
||||
return items;
|
||||
}, [adHocDataView, globalDataViews]);
|
||||
|
||||
const currentDataView = useMemo(
|
||||
() => dataViews.find((dataView) => dataView.id === group.indexPatternId) || dataViews[0],
|
||||
() => dataViews.find((dataView) => dataView.id === group.indexPatternId),
|
||||
[dataViews, group.indexPatternId]
|
||||
);
|
||||
|
||||
return !selectedAnnotation ? (
|
||||
<>
|
||||
<EuiTitle
|
||||
size="xs"
|
||||
size="xxs"
|
||||
css={css`
|
||||
margin-bottom: ${euiThemeVars.euiSize};
|
||||
`}
|
||||
>
|
||||
<h4>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.groupEditor.details"
|
||||
id="eventAnnotationListing.groupEditor.details"
|
||||
defaultMessage="Details"
|
||||
/>
|
||||
</h4>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiForm>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationComponents.groupEditor.title', {
|
||||
label={i18n.translate('eventAnnotationListing.groupEditor.title', {
|
||||
defaultMessage: 'Title',
|
||||
})}
|
||||
isInvalid={showValidation && !isTitleValid(group.title)}
|
||||
error={i18n.translate('eventAnnotationComponents.groupEditor.titleRequired', {
|
||||
error={i18n.translate('eventAnnotationListing.groupEditor.titleRequired', {
|
||||
defaultMessage: 'A title is required.',
|
||||
})}
|
||||
>
|
||||
<EuiFieldText
|
||||
compressed
|
||||
data-test-subj="annotationGroupTitle"
|
||||
value={group.title}
|
||||
isInvalid={showValidation && !isTitleValid(group.title)}
|
||||
|
@ -131,19 +119,20 @@ export const GroupEditorControls = ({
|
|||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationComponents.groupEditor.description', {
|
||||
label={i18n.translate('eventAnnotationListing.groupEditor.description', {
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
labelAppend={
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.groupEditor.optional"
|
||||
id="eventAnnotationListing.groupEditor.optional"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<EuiTextArea
|
||||
compressed
|
||||
data-test-subj="annotationGroupDescription"
|
||||
value={group.description}
|
||||
onChange={({ target: { value } }) =>
|
||||
|
@ -158,6 +147,7 @@ export const GroupEditorControls = ({
|
|||
<TagSelector
|
||||
initialSelection={group.tags}
|
||||
markOptional
|
||||
compressed
|
||||
onTagsSelected={(tags: string[]) =>
|
||||
update({
|
||||
...group,
|
||||
|
@ -166,46 +156,92 @@ export const GroupEditorControls = ({
|
|||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{ENABLE_INDIVIDUAL_ANNOTATION_EDITING && (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationComponents.groupEditor.dataView', {
|
||||
defaultMessage: 'Data view',
|
||||
})}
|
||||
>
|
||||
<EuiSelect
|
||||
data-test-subj="annotationDataViewSelection"
|
||||
options={dataViews.map(({ id: value, title, name }) => ({
|
||||
value,
|
||||
text: name ?? title,
|
||||
}))}
|
||||
value={group.indexPatternId}
|
||||
onChange={({ target: { value } }) =>
|
||||
update({
|
||||
...group,
|
||||
indexPatternId: value,
|
||||
dataViewSpec:
|
||||
value === adHocDataView?.id ? adHocDataView.toSpec(false) : undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationComponents.groupEditor.addAnnotation', {
|
||||
defaultMessage: 'Annotations',
|
||||
})}
|
||||
>
|
||||
<AnnotationList
|
||||
annotations={group.annotations}
|
||||
selectAnnotation={setSelectedAnnotation}
|
||||
update={(newAnnotations) => update({ ...group, annotations: newAnnotations })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
)}
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationListing.groupEditor.dataView', {
|
||||
defaultMessage: 'Data view',
|
||||
})}
|
||||
isInvalid={!isDataViewValid(currentDataView)}
|
||||
error={
|
||||
!isDataViewValid(currentDataView)
|
||||
? i18n.translate('eventAnnotationListing.groupEditor.dataViewMissingError', {
|
||||
defaultMessage: 'The previously selected data view no longer exists.',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
compressed
|
||||
data-test-subj="annotationDataViewSelection"
|
||||
isInvalid={!isDataViewValid(currentDataView)}
|
||||
options={dataViews.map(({ id: value, title, name }) => ({
|
||||
value,
|
||||
text: name ?? title,
|
||||
}))}
|
||||
value={isDataViewValid(currentDataView) ? group.indexPatternId : undefined}
|
||||
hasNoInitialSelection={true}
|
||||
onChange={({ target: { value } }) => {
|
||||
const selectedDataView = dataViews.find(({ id }) => id === value);
|
||||
|
||||
if (!selectedDataView?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
update({
|
||||
...group,
|
||||
indexPatternId: value,
|
||||
dataViewSpec: isAdHocDataView(selectedDataView.id)
|
||||
? selectedDataView.toSpec(false)
|
||||
: undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
<div
|
||||
css={css`
|
||||
margin-top: ${euiThemeVars.euiSize};
|
||||
padding-top: ${euiThemeVars.euiSize};
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -${euiThemeVars.euiSize};
|
||||
left: -${euiThemeVars.euiSize};
|
||||
border-top: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<EuiTitle
|
||||
size="xxs"
|
||||
css={css`
|
||||
margin-bottom: ${euiThemeVars.euiSize};
|
||||
`}
|
||||
>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupEditor.annotations"
|
||||
defaultMessage="Annotations"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiForm>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('eventAnnotationListing.groupEditor.annotationGroupLabel', {
|
||||
defaultMessage: 'Date histogram axis',
|
||||
})}
|
||||
>
|
||||
<AnnotationList
|
||||
annotations={group.annotations}
|
||||
selectAnnotation={currentDataView ? setSelectedAnnotation : () => {}}
|
||||
update={(newAnnotations) => update({ ...group, annotations: newAnnotations })}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
) : currentDataView ? (
|
||||
<AnnotationEditorControls
|
||||
annotation={selectedAnnotation}
|
||||
onAnnotationChange={(changes) => setSelectedAnnotation({ ...selectedAnnotation, ...changes })}
|
||||
|
@ -214,5 +250,5 @@ export const GroupEditorControls = ({
|
|||
queryInputServices={queryInputServices}
|
||||
appName={EVENT_ANNOTATION_APP_NAME}
|
||||
/>
|
||||
);
|
||||
) : null;
|
||||
};
|
|
@ -16,6 +16,7 @@ import { GroupEditorControls } from './group_editor_controls';
|
|||
import { GroupEditorFlyout } from './group_editor_flyout';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import { EmbeddableComponent } from '@kbn/lens-plugin/public';
|
||||
|
||||
const simulateButtonClick = (component: ShallowWrapper, selector: string) => {
|
||||
(component.find(selector) as ShallowWrapper<Parameters<typeof EuiButton>[0]>).prop('onClick')!(
|
||||
|
@ -27,6 +28,7 @@ const SELECTORS = {
|
|||
SAVE_BUTTON: '[data-test-subj="saveAnnotationGroup"]',
|
||||
CANCEL_BUTTON: '[data-test-subj="cancelGroupEdit"]',
|
||||
BACK_BUTTON: '[data-test-subj="backToGroupSettings"]',
|
||||
TOP_BACK_BUTTON: '[data-test-subj="backToGroupSettingsTop"]',
|
||||
};
|
||||
|
||||
const assertGroupEditingState = (component: ShallowWrapper) => {
|
||||
|
@ -59,14 +61,15 @@ describe('group editor flyout', () => {
|
|||
let onSave: jest.Mock;
|
||||
let onClose: jest.Mock;
|
||||
let updateGroup: jest.Mock;
|
||||
const LensEmbeddableComponent: EmbeddableComponent = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
const mountComponent = (groupToUse: EventAnnotationGroupConfig) => {
|
||||
onSave = jest.fn();
|
||||
onClose = jest.fn();
|
||||
updateGroup = jest.fn();
|
||||
component = shallow(
|
||||
return shallow(
|
||||
<GroupEditorFlyout
|
||||
group={group}
|
||||
group={groupToUse}
|
||||
onSave={onSave}
|
||||
onClose={onClose}
|
||||
updateGroup={updateGroup}
|
||||
|
@ -79,8 +82,16 @@ describe('group editor flyout', () => {
|
|||
savedObjectsTagging={mockTaggingApi}
|
||||
createDataView={jest.fn()}
|
||||
queryInputServices={{} as QueryInputServices}
|
||||
LensEmbeddableComponent={LensEmbeddableComponent}
|
||||
searchSessionId={'searchSessionId'}
|
||||
refreshSearchSession={jest.fn()}
|
||||
timePickerQuickRanges={[]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component = mountComponent(group);
|
||||
});
|
||||
|
||||
it('renders controls', () => {
|
||||
|
@ -112,17 +123,20 @@ describe('group editor flyout', () => {
|
|||
|
||||
expect(updateGroup).toHaveBeenCalledWith(newGroup);
|
||||
});
|
||||
test('specific annotation editing', () => {
|
||||
assertGroupEditingState(component);
|
||||
test.each([SELECTORS.BACK_BUTTON, SELECTORS.TOP_BACK_BUTTON])(
|
||||
'specific annotation editing',
|
||||
(backButtonSelector) => {
|
||||
assertGroupEditingState(component);
|
||||
|
||||
component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation);
|
||||
component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation);
|
||||
|
||||
assertAnnotationEditingState(component);
|
||||
assertAnnotationEditingState(component);
|
||||
|
||||
component.find(SELECTORS.BACK_BUTTON).simulate('click');
|
||||
component.find(backButtonSelector).simulate('click');
|
||||
|
||||
assertGroupEditingState(component);
|
||||
});
|
||||
assertGroupEditingState(component);
|
||||
}
|
||||
);
|
||||
it('removes active annotation instead of signaling close', () => {
|
||||
component.find(GroupEditorControls).prop('setSelectedAnnotation')(annotation);
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
htmlIdGenerator,
|
||||
useIsWithinBreakpoints,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import type {
|
||||
EventAnnotationConfig,
|
||||
EventAnnotationGroupConfig,
|
||||
} from '@kbn/event-annotation-common';
|
||||
import { css } from '@emotion/react';
|
||||
import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GroupEditorControls, isGroupValid } from './group_editor_controls';
|
||||
import { GroupPreview } from './group_preview';
|
||||
|
||||
export const GroupEditorFlyout = ({
|
||||
group,
|
||||
updateGroup,
|
||||
onClose: parentOnClose,
|
||||
onSave,
|
||||
savedObjectsTagging,
|
||||
dataViews: globalDataViews,
|
||||
createDataView,
|
||||
LensEmbeddableComponent,
|
||||
queryInputServices,
|
||||
searchSessionId,
|
||||
refreshSearchSession,
|
||||
timePickerQuickRanges,
|
||||
}: {
|
||||
group: EventAnnotationGroupConfig;
|
||||
updateGroup: (newGroup: EventAnnotationGroupConfig) => void;
|
||||
onClose: () => void;
|
||||
onSave: () => void;
|
||||
savedObjectsTagging: SavedObjectsTaggingApi;
|
||||
dataViews: DataView[];
|
||||
createDataView: (spec: DataViewSpec) => Promise<DataView>;
|
||||
LensEmbeddableComponent: LensEmbeddableComponent;
|
||||
queryInputServices: QueryInputServices;
|
||||
searchSessionId: string;
|
||||
refreshSearchSession: () => void;
|
||||
timePickerQuickRanges: Array<{ from: string; to: string; display: string }> | undefined;
|
||||
}) => {
|
||||
const flyoutHeadingId = useMemo(() => htmlIdGenerator()(), []);
|
||||
const flyoutBodyOverflowRef = useRef<Element | null>(null);
|
||||
useEffect(() => {
|
||||
if (!flyoutBodyOverflowRef.current) {
|
||||
flyoutBodyOverflowRef.current = document.querySelector('.euiFlyoutBody__overflow');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [hasAttemptedSave, setHasAttemptedSave] = useState(false);
|
||||
|
||||
const resetContentScroll = useCallback(
|
||||
() => flyoutBodyOverflowRef.current && flyoutBodyOverflowRef.current.scroll(0, 0),
|
||||
[]
|
||||
);
|
||||
|
||||
// save the spec for the life of the component since the user might change their mind after selecting another data view
|
||||
const [adHocDataView, setAdHocDataView] = useState<DataView>();
|
||||
|
||||
useEffect(() => {
|
||||
if (group.dataViewSpec) {
|
||||
createDataView(group.dataViewSpec).then(setAdHocDataView);
|
||||
}
|
||||
}, [createDataView, group.dataViewSpec]);
|
||||
|
||||
const dataViews = useMemo(() => {
|
||||
const items = [...globalDataViews];
|
||||
if (adHocDataView) {
|
||||
items.push(adHocDataView);
|
||||
}
|
||||
return items;
|
||||
}, [adHocDataView, globalDataViews]);
|
||||
|
||||
const [selectedAnnotation, _setSelectedAnnotation] = useState<EventAnnotationConfig>();
|
||||
const setSelectedAnnotation = useCallback(
|
||||
(newValue: EventAnnotationConfig | undefined) => {
|
||||
if ((!newValue && selectedAnnotation) || (newValue && !selectedAnnotation))
|
||||
resetContentScroll();
|
||||
_setSelectedAnnotation(newValue);
|
||||
},
|
||||
[resetContentScroll, selectedAnnotation]
|
||||
);
|
||||
const onClose = () => (selectedAnnotation ? setSelectedAnnotation(undefined) : parentOnClose());
|
||||
|
||||
const showPreview = !useIsWithinBreakpoints(['xs', 's', 'm']);
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
paddingSize="m"
|
||||
size="l"
|
||||
hideCloseButton
|
||||
outsideClickCloses={false}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
`}
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
${showPreview ? 'width: 360px;' : ''}
|
||||
border-right: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
`}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder aria-labelledby={flyoutHeadingId}>
|
||||
<EuiTitle size="xs">
|
||||
<h2 id={flyoutHeadingId}>
|
||||
{selectedAnnotation ? (
|
||||
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="sortLeft"
|
||||
aria-label={i18n.translate('eventAnnotationListing.edit.back', {
|
||||
defaultMessage: 'Back',
|
||||
})}
|
||||
onClick={() => setSelectedAnnotation(undefined)}
|
||||
data-test-subj="backToGroupSettingsTop"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupEditorFlyout.titleWithAnnotation"
|
||||
defaultMessage="Date histogram axis annotation"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupEditorFlyout.title"
|
||||
defaultMessage="Edit annotation group"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<GroupEditorControls
|
||||
group={group}
|
||||
update={updateGroup}
|
||||
selectedAnnotation={selectedAnnotation}
|
||||
setSelectedAnnotation={setSelectedAnnotation}
|
||||
TagSelector={savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector}
|
||||
dataViews={dataViews}
|
||||
queryInputServices={queryInputServices}
|
||||
showValidation={hasAttemptedSave}
|
||||
isAdHocDataView={(id) => id === adHocDataView?.id}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
{selectedAnnotation ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
flush="both"
|
||||
iconType="sortLeft"
|
||||
data-test-subj="backToGroupSettings"
|
||||
onClick={() => setSelectedAnnotation(undefined)}
|
||||
>
|
||||
<FormattedMessage id="eventAnnotationListing.edit.back" defaultMessage="Back" />
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="cancelGroupEdit"
|
||||
onClick={onClose}
|
||||
size="s"
|
||||
flush="both"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.edit.cancel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
iconType="save"
|
||||
data-test-subj="saveAnnotationGroup"
|
||||
fill
|
||||
onClick={() => {
|
||||
setHasAttemptedSave(true);
|
||||
|
||||
if (isGroupValid(group, dataViews)) {
|
||||
onSave();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.edit.save"
|
||||
defaultMessage="Save annotation group"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexItem>
|
||||
{showPreview && (
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
background-color: ${euiThemeVars.euiColorLightestShade};
|
||||
`}
|
||||
>
|
||||
<GroupPreview
|
||||
group={group}
|
||||
dataViews={dataViews}
|
||||
LensEmbeddableComponent={LensEmbeddableComponent}
|
||||
searchSessionId={searchSessionId}
|
||||
refreshSearchSession={refreshSearchSession}
|
||||
timePickerQuickRanges={timePickerQuickRanges}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getDefaultManualAnnotation } from '@kbn/event-annotation-common';
|
||||
import type { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
|
||||
import React from 'react';
|
||||
import {
|
||||
DataView,
|
||||
DataViewField,
|
||||
DataViewFieldMap,
|
||||
IIndexPatternFieldList,
|
||||
} from '@kbn/data-views-plugin/common';
|
||||
import {
|
||||
EmbeddableComponent,
|
||||
FieldBasedIndexPatternColumn,
|
||||
TypedLensByValueInput,
|
||||
} from '@kbn/lens-plugin/public';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { GroupPreview } from './group_preview';
|
||||
import { LensByValueInput } from '@kbn/lens-plugin/public/embeddable';
|
||||
import { DATA_LAYER_ID, DATE_HISTOGRAM_COLUMN_ID, getCurrentTimeField } from './lens_attributes';
|
||||
import moment from 'moment';
|
||||
|
||||
class EuiSuperDatePickerTestHarness {
|
||||
public static get currentCommonlyUsedRange() {
|
||||
return screen.queryByTestId('superDatePickerShowDatesButton')?.textContent ?? '';
|
||||
}
|
||||
|
||||
// TODO - add assertion with date formatting
|
||||
public static get currentRange() {
|
||||
if (screen.queryByTestId('superDatePickerShowDatesButton')) {
|
||||
// showing a commonly-used range
|
||||
return { from: '', to: '' };
|
||||
}
|
||||
|
||||
return {
|
||||
from: screen.getByTestId('superDatePickerstartDatePopoverButton').textContent,
|
||||
to: screen.getByTestId('superDatePickerendDatePopoverButton').textContent,
|
||||
};
|
||||
}
|
||||
|
||||
static togglePopover() {
|
||||
userEvent.click(screen.getByRole('button', { name: 'Date quick select' }));
|
||||
}
|
||||
|
||||
static async selectCommonlyUsedRange(label: string) {
|
||||
if (!screen.queryByText('Commonly used')) this.togglePopover();
|
||||
|
||||
// Using fireEvent here because userEvent erroneously claims that
|
||||
// pointer-events is set to 'none'.
|
||||
//
|
||||
// I have verified that this fixed on the latest version of the @testing-library/user-event package
|
||||
fireEvent.click(await screen.findByText(label));
|
||||
}
|
||||
|
||||
static refresh() {
|
||||
userEvent.click(screen.getByRole('button', { name: 'Refresh' }));
|
||||
}
|
||||
}
|
||||
|
||||
describe('group editor preview', () => {
|
||||
const annotation = getDefaultManualAnnotation('my-id', 'some-timestamp');
|
||||
|
||||
const group: EventAnnotationGroupConfig = {
|
||||
annotations: [annotation],
|
||||
description: '',
|
||||
tags: [],
|
||||
indexPatternId: 'some-id',
|
||||
title: 'My group',
|
||||
ignoreGlobalFilters: false,
|
||||
};
|
||||
|
||||
const BRUSH_RANGE = [0, 100];
|
||||
|
||||
const LensEmbeddableComponent: EmbeddableComponent = (props) => (
|
||||
<div>
|
||||
<div data-test-subj="chartTimeRange">{JSON.stringify(props.timeRange)}</div>
|
||||
<div data-test-subj="chartSearchSessionId">{props.searchSessionId}</div>
|
||||
<div data-test-subj="lensAttributes">
|
||||
{JSON.stringify((props as LensByValueInput).attributes)}
|
||||
</div>
|
||||
<button
|
||||
data-test-subj="brushEnd"
|
||||
onClick={() =>
|
||||
props.onBrushEnd?.({
|
||||
table: {} as Datatable,
|
||||
range: BRUSH_RANGE,
|
||||
column: 0,
|
||||
preventDefault: jest.fn(),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getEmbeddableTimeRange = () => {
|
||||
const serialized = screen.getByTestId('chartTimeRange').textContent;
|
||||
return serialized ? JSON.parse(serialized) : null;
|
||||
};
|
||||
|
||||
const getEmbeddableSearchSessionId = () => {
|
||||
return screen.getByTestId('chartSearchSessionId').textContent;
|
||||
};
|
||||
|
||||
const getLensAttributes = () => {
|
||||
const serialized = screen.queryByTestId('lensAttributes')?.textContent;
|
||||
return serialized ? JSON.parse(serialized) : null;
|
||||
};
|
||||
|
||||
let rerender: (ui: React.ReactElement<any, string | React.JSXElementConstructor<any>>) => void;
|
||||
|
||||
const defaultProps: Parameters<typeof GroupPreview>[0] = {
|
||||
group,
|
||||
dataViews: [
|
||||
{
|
||||
id: 'some-id',
|
||||
title: 'My Data View',
|
||||
timeFieldName: '@timestamp',
|
||||
fields: {
|
||||
getByType: jest.fn<DataViewField[], []>(() => [
|
||||
{
|
||||
type: 'date',
|
||||
name: '@timestamp',
|
||||
} as DataViewField,
|
||||
{
|
||||
type: 'date',
|
||||
name: 'other-time-field',
|
||||
} as DataViewField,
|
||||
]),
|
||||
} as unknown as IIndexPatternFieldList & { toSpec: () => DataViewFieldMap },
|
||||
} as DataView,
|
||||
{
|
||||
id: 'a-different-id',
|
||||
title: 'My Data View',
|
||||
timeFieldName: 'other-time-field',
|
||||
fields: {
|
||||
getByType: jest.fn<DataViewField[], []>(() => [
|
||||
{
|
||||
type: 'date',
|
||||
name: '@timestamp',
|
||||
} as DataViewField,
|
||||
{
|
||||
type: 'date',
|
||||
name: 'other-time-field',
|
||||
} as DataViewField,
|
||||
]),
|
||||
} as unknown as IIndexPatternFieldList & { toSpec: () => DataViewFieldMap },
|
||||
} as DataView,
|
||||
],
|
||||
LensEmbeddableComponent,
|
||||
searchSessionId: 'some-search-session-id',
|
||||
refreshSearchSession: jest.fn(),
|
||||
timePickerQuickRanges: [{ from: 'now/d', to: 'now/d', display: 'Today' }],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const renderResult = render(
|
||||
<I18nProvider>
|
||||
<GroupPreview {...defaultProps} />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
rerender = renderResult.rerender;
|
||||
});
|
||||
|
||||
it('updates the chart time range', async () => {
|
||||
// default
|
||||
expect(EuiSuperDatePickerTestHarness.currentCommonlyUsedRange).toBe('Last 15 minutes');
|
||||
expect(getEmbeddableTimeRange()).toEqual({ from: 'now-15m', to: 'now' });
|
||||
|
||||
// from time picker
|
||||
await EuiSuperDatePickerTestHarness.selectCommonlyUsedRange('Today');
|
||||
|
||||
expect(EuiSuperDatePickerTestHarness.currentCommonlyUsedRange).toBe('Today');
|
||||
expect(getEmbeddableTimeRange()).toEqual({ from: 'now/d', to: 'now/d' });
|
||||
|
||||
// from chart brush
|
||||
userEvent.click(screen.getByTestId('brushEnd'));
|
||||
|
||||
const format = 'MMM D, YYYY @ HH:mm:ss.SSS'; // from https://github.com/elastic/eui/blob/6a30eba7c2a154691c96a1d17c8b2f3506d351a3/src/components/date_picker/super_date_picker/super_date_picker.tsx#L222;
|
||||
expect(EuiSuperDatePickerTestHarness.currentRange).toEqual({
|
||||
from: moment(BRUSH_RANGE[0]).format(format),
|
||||
to: moment(BRUSH_RANGE[1]).format(format),
|
||||
});
|
||||
expect(getEmbeddableTimeRange()).toEqual({
|
||||
from: new Date(BRUSH_RANGE[0]).toISOString(),
|
||||
to: new Date(BRUSH_RANGE[1]).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the time field', async () => {
|
||||
EuiSuperDatePickerTestHarness.togglePopover();
|
||||
|
||||
const select = screen.getByRole('combobox', { name: 'Time field' });
|
||||
|
||||
expect(select).toHaveValue('@timestamp');
|
||||
expect(getCurrentTimeField(getLensAttributes())).toBe('@timestamp');
|
||||
|
||||
userEvent.selectOptions(select, 'other-time-field');
|
||||
|
||||
expect(select).toHaveValue('other-time-field');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getCurrentTimeField(getLensAttributes())).toBe('other-time-field');
|
||||
});
|
||||
});
|
||||
|
||||
it('refreshes the chart data', () => {
|
||||
expect(defaultProps.refreshSearchSession).not.toHaveBeenCalled();
|
||||
expect(getEmbeddableSearchSessionId()).toBe(defaultProps.searchSessionId);
|
||||
|
||||
EuiSuperDatePickerTestHarness.refresh();
|
||||
|
||||
expect(defaultProps.refreshSearchSession).toHaveBeenCalled();
|
||||
|
||||
rerender(
|
||||
<I18nProvider>
|
||||
<GroupPreview {...defaultProps} searchSessionId="new-search-session-id" />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
expect(getEmbeddableSearchSessionId()).toBe('new-search-session-id');
|
||||
});
|
||||
|
||||
describe('data views', () => {
|
||||
const assertDataView = (id: string, attributes: TypedLensByValueInput['attributes']) =>
|
||||
expect(attributes.references[0].id).toBe(id);
|
||||
const assertTimeField = (fieldName: string, attributes: TypedLensByValueInput['attributes']) =>
|
||||
expect(
|
||||
(
|
||||
attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[
|
||||
DATE_HISTOGRAM_COLUMN_ID
|
||||
] as FieldBasedIndexPatternColumn
|
||||
).sourceField
|
||||
).toBe(fieldName);
|
||||
|
||||
it('uses correct data view', async () => {
|
||||
assertDataView(group.indexPatternId, getLensAttributes());
|
||||
|
||||
rerender(
|
||||
<I18nProvider>
|
||||
<GroupPreview {...defaultProps} group={{ ...group, indexPatternId: 'a-different-id' }} />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
assertDataView('a-different-id', getLensAttributes());
|
||||
assertTimeField('other-time-field', getLensAttributes());
|
||||
});
|
||||
});
|
||||
|
||||
it('supports ad-hoc data view', () => {
|
||||
const adHocDataView = {
|
||||
id: 'adhoc-1',
|
||||
title: 'my-pattern*',
|
||||
timeFieldName: '@timestamp',
|
||||
sourceFilters: [],
|
||||
fieldFormats: {},
|
||||
runtimeFieldMap: {},
|
||||
fieldAttrs: {},
|
||||
allowNoIndex: false,
|
||||
name: 'My ad-hoc data view',
|
||||
};
|
||||
|
||||
rerender(
|
||||
<I18nProvider>
|
||||
<GroupPreview
|
||||
{...defaultProps}
|
||||
group={{ ...group, indexPatternId: '', dataViewSpec: adHocDataView }}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
waitFor(() => {
|
||||
const attributes = getLensAttributes();
|
||||
expect(attributes.references).toHaveLength(0);
|
||||
expect(attributes.state.adHocDataViews![adHocDataView.id!]).toEqual(adHocDataView);
|
||||
expect(attributes.state.internalReferences).toHaveLength(1);
|
||||
expect(attributes.state.internalReferences![0].id).toBe(adHocDataView.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles missing data view', async () => {
|
||||
rerender(
|
||||
<I18nProvider>
|
||||
<GroupPreview {...defaultProps} group={{ ...group, indexPatternId: 'doesnt-exist' }} />
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'Select a valid data view' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(getLensAttributes()).toBeNull(); // chart shouldn't be rendered
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiSelect,
|
||||
EuiSuperDatePicker,
|
||||
EuiSuperDatePickerProps,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import type {
|
||||
EmbeddableComponent as LensEmbeddableComponent,
|
||||
TypedLensByValueInput,
|
||||
} from '@kbn/lens-plugin/public';
|
||||
import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
|
||||
import { css } from '@emotion/react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getLensAttributes } from './lens_attributes';
|
||||
|
||||
export const GroupPreview = ({
|
||||
group,
|
||||
dataViews,
|
||||
LensEmbeddableComponent,
|
||||
searchSessionId,
|
||||
refreshSearchSession,
|
||||
timePickerQuickRanges,
|
||||
}: {
|
||||
group: EventAnnotationGroupConfig;
|
||||
dataViews: DataView[];
|
||||
LensEmbeddableComponent: LensEmbeddableComponent;
|
||||
searchSessionId: string;
|
||||
refreshSearchSession: () => void;
|
||||
timePickerQuickRanges: Array<{ from: string; to: string; display: string }> | undefined;
|
||||
}) => {
|
||||
const [chartTimeRange, setChartTimeRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' });
|
||||
const commonlyUsedRanges = useMemo(
|
||||
() =>
|
||||
timePickerQuickRanges?.map(
|
||||
({ from, to, display }: { from: string; to: string; display: string }) => {
|
||||
return {
|
||||
start: from,
|
||||
end: to,
|
||||
label: display,
|
||||
};
|
||||
}
|
||||
) ?? [],
|
||||
[timePickerQuickRanges]
|
||||
);
|
||||
|
||||
const customQuickSelectRender = useCallback<
|
||||
Required<EuiSuperDatePickerProps>['customQuickSelectRender']
|
||||
>(
|
||||
({ quickSelect, commonlyUsedRanges: ranges, customQuickSelectPanels }) =>
|
||||
(
|
||||
<>
|
||||
{customQuickSelectPanels}
|
||||
{quickSelect}
|
||||
{ranges}
|
||||
</>
|
||||
) as React.ReactNode,
|
||||
[]
|
||||
);
|
||||
|
||||
const currentDataView = useMemo(
|
||||
() => dataViews.find((dataView) => dataView.id === group.indexPatternId),
|
||||
[dataViews, group.indexPatternId]
|
||||
);
|
||||
|
||||
const timeFieldNames = useMemo(
|
||||
() => currentDataView?.fields.getByType('date').map((field) => field.name) ?? [],
|
||||
[currentDataView?.fields]
|
||||
);
|
||||
|
||||
// We can assume that there is at least one time field because we don't allow annotation groups to be created without one
|
||||
const defaultTimeFieldName = useMemo(
|
||||
() => currentDataView?.timeFieldName ?? timeFieldNames[0],
|
||||
[currentDataView?.timeFieldName, timeFieldNames]
|
||||
);
|
||||
|
||||
const [currentTimeFieldName, setCurrentTimeFieldName] = useState<string>(defaultTimeFieldName);
|
||||
|
||||
const [lensAttributes, setLensAttributes] = useState<TypedLensByValueInput['attributes']>(
|
||||
getLensAttributes(group, currentTimeFieldName)
|
||||
);
|
||||
|
||||
// we don't use currentDataView directly to hide/show the missing prompt because we want to delay
|
||||
// the embeddable render until the lensAttributes have been updated in useDebounce
|
||||
// in the case that the user selects a new data view
|
||||
const [showMissingDataViewPrompt, setShowMissingDataViewPrompt] = useState<boolean>(
|
||||
!currentDataView
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentTimeFieldName(defaultTimeFieldName);
|
||||
}, [defaultTimeFieldName]);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
setLensAttributes(getLensAttributes(group, currentTimeFieldName));
|
||||
setShowMissingDataViewPrompt(!currentDataView);
|
||||
},
|
||||
250,
|
||||
[group, currentTimeFieldName]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h3
|
||||
css={css`
|
||||
white-space: nowrap;
|
||||
`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupPreview.preview"
|
||||
defaultMessage="Preview"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
width: 336px;
|
||||
margin-top: -5px;
|
||||
margin-bottom: -5px;
|
||||
`}
|
||||
grow={false}
|
||||
>
|
||||
<EuiSuperDatePicker
|
||||
onTimeChange={({ start: from, end: to }) => setChartTimeRange({ from, to })}
|
||||
onRefresh={({ start: from, end: to }) => {
|
||||
setChartTimeRange({ from, to });
|
||||
refreshSearchSession();
|
||||
}}
|
||||
start={chartTimeRange.from}
|
||||
end={chartTimeRange.to}
|
||||
compressed
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
updateButtonProps={{
|
||||
iconOnly: true,
|
||||
fill: false,
|
||||
}}
|
||||
customQuickSelectRender={customQuickSelectRender}
|
||||
customQuickSelectPanels={[
|
||||
{
|
||||
title: i18n.translate('eventAnnotationListing.timeField', {
|
||||
defaultMessage: 'Time field',
|
||||
}),
|
||||
content: (
|
||||
<EuiSelect
|
||||
aria-label={i18n.translate('eventAnnotationListing.timeField', {
|
||||
defaultMessage: 'Time field',
|
||||
})}
|
||||
options={timeFieldNames.map((name) => ({
|
||||
text: name,
|
||||
}))}
|
||||
value={currentTimeFieldName}
|
||||
onChange={(e) => setCurrentTimeFieldName(e.target.value)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
css={css`
|
||||
.euiFlyoutBody__overflowContent {
|
||||
height: 100%;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{!showMissingDataViewPrompt ? (
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
height: 100%;
|
||||
`}
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem grow={0}>
|
||||
<div
|
||||
css={css`
|
||||
& > div {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<LensEmbeddableComponent
|
||||
data-test-subj="chart"
|
||||
id="annotation-library-preview"
|
||||
timeRange={chartTimeRange}
|
||||
attributes={lensAttributes}
|
||||
onBrushEnd={({ range }) =>
|
||||
setChartTimeRange({
|
||||
from: new Date(range[0]).toISOString(),
|
||||
to: new Date(range[1]).toISOString(),
|
||||
})
|
||||
}
|
||||
searchSessionId={searchSessionId}
|
||||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
height: 100%;
|
||||
`}
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
color="danger"
|
||||
data-test-subj="missingDataViewPrompt"
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupPreview.missingDataViewTitle"
|
||||
defaultMessage="Select a valid data view"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.groupPreview.missingDataViewDescription"
|
||||
defaultMessage="The previously selected data view no longer exists. Please select a valid data view in order to preview and use this annotation group."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { GroupEditorFlyout } from './group_editor_flyout';
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common';
|
||||
import { FieldBasedIndexPatternColumn, TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
import { XYPersistedByValueAnnotationLayerConfig } from '@kbn/lens-plugin/public/async_services';
|
||||
|
||||
export const DATA_LAYER_ID = 'data-layer-id';
|
||||
export const DATE_HISTOGRAM_COLUMN_ID = 'date-histogram-column-id';
|
||||
const ANNOTATION_LAYER_ID = 'annotation-layer-id';
|
||||
|
||||
export const getLensAttributes = (group: EventAnnotationGroupConfig, timeField: string) =>
|
||||
({
|
||||
title: 'Line visualization with annotation layer', // TODO - should this be translated?
|
||||
description: '',
|
||||
visualizationType: 'lnsXY',
|
||||
type: 'lens',
|
||||
state: {
|
||||
visualization: {
|
||||
legend: {
|
||||
isVisible: true,
|
||||
position: 'right',
|
||||
},
|
||||
valueLabels: 'hide',
|
||||
fittingFunction: 'None',
|
||||
axisTitlesVisibilitySettings: {
|
||||
x: true,
|
||||
yLeft: true,
|
||||
yRight: true,
|
||||
},
|
||||
tickLabelsVisibilitySettings: {
|
||||
x: true,
|
||||
yLeft: true,
|
||||
yRight: true,
|
||||
},
|
||||
labelsOrientation: {
|
||||
x: 0,
|
||||
yLeft: 0,
|
||||
yRight: 0,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
x: true,
|
||||
yLeft: true,
|
||||
yRight: true,
|
||||
},
|
||||
preferredSeriesType: 'line',
|
||||
layers: [
|
||||
{
|
||||
layerId: DATA_LAYER_ID,
|
||||
accessors: ['a7264a99-cd42-4b3f-855f-05364df71a71'],
|
||||
position: 'top',
|
||||
seriesType: 'line',
|
||||
showGridlines: false,
|
||||
layerType: 'data',
|
||||
xAccessor: [DATE_HISTOGRAM_COLUMN_ID],
|
||||
},
|
||||
{
|
||||
layerId: ANNOTATION_LAYER_ID,
|
||||
layerType: 'annotations',
|
||||
persistanceType: 'byValue',
|
||||
...group,
|
||||
} as XYPersistedByValueAnnotationLayerConfig,
|
||||
],
|
||||
},
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
filters: [],
|
||||
datasourceStates: {
|
||||
formBased: {
|
||||
layers: {
|
||||
[DATA_LAYER_ID]: {
|
||||
columns: {
|
||||
[DATE_HISTOGRAM_COLUMN_ID]: {
|
||||
label: 'timestamp',
|
||||
dataType: 'date',
|
||||
operationType: 'date_histogram',
|
||||
sourceField: timeField,
|
||||
isBucketed: true,
|
||||
scale: 'interval',
|
||||
params: {
|
||||
interval: 'auto',
|
||||
includeEmptyRows: true,
|
||||
dropPartials: false,
|
||||
},
|
||||
} as FieldBasedIndexPatternColumn,
|
||||
'a7264a99-cd42-4b3f-855f-05364df71a71': {
|
||||
label: 'Count of records',
|
||||
dataType: 'number',
|
||||
operationType: 'count',
|
||||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
sourceField: '___records___',
|
||||
params: {
|
||||
emptyAsNull: true,
|
||||
},
|
||||
} as FieldBasedIndexPatternColumn,
|
||||
},
|
||||
columnOrder: [DATE_HISTOGRAM_COLUMN_ID, 'a7264a99-cd42-4b3f-855f-05364df71a71'],
|
||||
incompleteColumns: {},
|
||||
sampling: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
textBased: {
|
||||
layers: {},
|
||||
},
|
||||
},
|
||||
|
||||
...(group.dataViewSpec
|
||||
? {
|
||||
internalReferences: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
id: group.dataViewSpec.id!,
|
||||
name: `indexpattern-datasource-layer-${DATA_LAYER_ID}`,
|
||||
},
|
||||
{
|
||||
type: 'index-pattern',
|
||||
id: group.dataViewSpec.id!,
|
||||
name: `xy-visualization-layer-${ANNOTATION_LAYER_ID}`,
|
||||
},
|
||||
],
|
||||
adHocDataViews: {
|
||||
[group.dataViewSpec.id!]: group.dataViewSpec,
|
||||
},
|
||||
}
|
||||
: { internalReferences: [], adHocDataViews: {} }),
|
||||
},
|
||||
references: group.dataViewSpec
|
||||
? []
|
||||
: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
id: group.indexPatternId,
|
||||
name: `indexpattern-datasource-layer-${DATA_LAYER_ID}`,
|
||||
},
|
||||
],
|
||||
} as TypedLensByValueInput['attributes']);
|
||||
|
||||
export const getCurrentTimeField = (attributes: TypedLensByValueInput['attributes']) => {
|
||||
return (
|
||||
attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[
|
||||
DATE_HISTOGRAM_COLUMN_ID
|
||||
] as FieldBasedIndexPatternColumn
|
||||
).sourceField;
|
||||
};
|
|
@ -16,7 +16,7 @@ import {
|
|||
TableListViewTable,
|
||||
type UserContentCommonSchema,
|
||||
} from '@kbn/content-management-table-list-view-table';
|
||||
import type { EventAnnotationServiceType } from '../types';
|
||||
import type { EventAnnotationServiceType } from '@kbn/event-annotation-components/types';
|
||||
import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import {
|
||||
|
@ -31,6 +31,7 @@ import { DataView } from '@kbn/data-views-plugin/common';
|
|||
import { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import { toastsServiceMock } from '@kbn/core-notifications-browser-mocks/src/toasts_service.mock';
|
||||
import { IToasts } from '@kbn/core-notifications-browser';
|
||||
import { ISessionService } from '@kbn/data-plugin/public';
|
||||
|
||||
describe('annotation list view', () => {
|
||||
const adHocDVId = 'ad-hoc';
|
||||
|
@ -51,6 +52,7 @@ describe('annotation list view', () => {
|
|||
let wrapper: ShallowWrapper<typeof EventAnnotationGroupTableList>;
|
||||
let mockEventAnnotationService: EventAnnotationServiceType;
|
||||
let mockToasts: IToasts;
|
||||
const searchSessionStartMethod = jest.fn<string, []>(() => 'some-session-id');
|
||||
|
||||
beforeEach(() => {
|
||||
mockEventAnnotationService = {
|
||||
|
@ -95,6 +97,10 @@ describe('annotation list view', () => {
|
|||
queryInputServices={{} as QueryInputServices}
|
||||
toasts={mockToasts}
|
||||
navigateToLens={() => {}}
|
||||
LensEmbeddableComponent={() => <div />}
|
||||
sessionService={
|
||||
{ start: searchSessionStartMethod } as Partial<ISessionService> as ISessionService
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -190,6 +196,7 @@ describe('annotation list view', () => {
|
|||
expect(mockEventAnnotationService.loadAnnotationGroup).toHaveBeenCalledWith('1234');
|
||||
|
||||
expect(wrapper.find(GroupEditorFlyout).exists()).toBeTruthy();
|
||||
expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('some-session-id');
|
||||
|
||||
const updatedGroup = { ...group, tags: ['my-new-tag'] };
|
||||
|
||||
|
@ -210,6 +217,24 @@ describe('annotation list view', () => {
|
|||
); // (should refresh list)
|
||||
});
|
||||
|
||||
it('refreshes the search session', async () => {
|
||||
act(() => {
|
||||
wrapper.find(TableListViewTable).prop('editItem')!({
|
||||
id: '1234',
|
||||
} as UserContentCommonSchema);
|
||||
});
|
||||
|
||||
// wait one tick to give promise time to settle
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('some-session-id');
|
||||
|
||||
searchSessionStartMethod.mockReturnValue('new-session-id');
|
||||
wrapper.find(GroupEditorFlyout).prop('refreshSearchSession')();
|
||||
|
||||
expect(wrapper.find(GroupEditorFlyout).prop('searchSessionId')).toBe('new-session-id');
|
||||
});
|
||||
|
||||
it('opens editor when title is clicked', async () => {
|
||||
act(() => {
|
||||
wrapper.find(TableListViewTable).prop('onClickTitle')!({
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { TableListViewTable } from '@kbn/content-management-table-list-view-table';
|
||||
import type { TableListTabParentProps } from '@kbn/content-management-tabbed-table-list-view';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -16,13 +16,16 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug
|
|||
import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import { IToasts } from '@kbn/core-notifications-browser';
|
||||
import { EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui';
|
||||
import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public';
|
||||
import type {
|
||||
EventAnnotationGroupConfig,
|
||||
EventAnnotationGroupContent,
|
||||
} from '@kbn/event-annotation-common';
|
||||
import type { EventAnnotationServiceType } from '../types';
|
||||
import { ISessionService, UI_SETTINGS } from '@kbn/data-plugin/public';
|
||||
import { EventAnnotationServiceType } from '@kbn/event-annotation-components';
|
||||
import { css } from '@emotion/react';
|
||||
import { GroupEditorFlyout } from './group_editor_flyout';
|
||||
|
||||
export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit';
|
||||
|
@ -35,7 +38,7 @@ const getCustomColumn = (dataViews: DataView[]) => {
|
|||
|
||||
return {
|
||||
field: 'dataView',
|
||||
name: i18n.translate('eventAnnotationComponents.tableList.dataView', {
|
||||
name: i18n.translate('eventAnnotationListing.tableList.dataView', {
|
||||
defaultMessage: 'Data view',
|
||||
}),
|
||||
sortable: false,
|
||||
|
@ -44,7 +47,24 @@ const getCustomColumn = (dataViews: DataView[]) => {
|
|||
<div>
|
||||
{record.attributes.dataViewSpec
|
||||
? record.attributes.dataViewSpec.name
|
||||
: dataViewNameMap[record.attributes.indexPatternId]}
|
||||
: dataViewNameMap[record.attributes.indexPatternId] ?? (
|
||||
<EuiText size="s" color={'danger'}>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationListing.tableList.dataView.missing"
|
||||
defaultMessage="{errorIcon} No longer exists"
|
||||
values={{
|
||||
errorIcon: (
|
||||
<EuiIcon
|
||||
type="error"
|
||||
css={css`
|
||||
margin-top: -3px;
|
||||
`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
@ -53,6 +73,7 @@ const getCustomColumn = (dataViews: DataView[]) => {
|
|||
export const EventAnnotationGroupTableList = ({
|
||||
uiSettings,
|
||||
eventAnnotationService,
|
||||
sessionService,
|
||||
visualizeCapabilities,
|
||||
savedObjectsTagging,
|
||||
parentProps,
|
||||
|
@ -61,9 +82,11 @@ export const EventAnnotationGroupTableList = ({
|
|||
queryInputServices,
|
||||
toasts,
|
||||
navigateToLens,
|
||||
LensEmbeddableComponent,
|
||||
}: {
|
||||
uiSettings: IUiSettingsClient;
|
||||
eventAnnotationService: EventAnnotationServiceType;
|
||||
sessionService: ISessionService;
|
||||
visualizeCapabilities: Record<string, boolean | Record<string, boolean>>;
|
||||
savedObjectsTagging: SavedObjectsTaggingApi;
|
||||
parentProps: TableListTabParentProps;
|
||||
|
@ -72,10 +95,23 @@ export const EventAnnotationGroupTableList = ({
|
|||
queryInputServices: QueryInputServices;
|
||||
toasts: IToasts;
|
||||
navigateToLens: () => void;
|
||||
LensEmbeddableComponent: LensEmbeddableComponent;
|
||||
}) => {
|
||||
const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
|
||||
const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
|
||||
|
||||
const [searchSessionId, setSearchSessionId] = useState<string>(sessionService.start());
|
||||
|
||||
const refreshSearchSession = useCallback(() => {
|
||||
setSearchSessionId(sessionService.start());
|
||||
}, [sessionService]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
sessionService.clear();
|
||||
};
|
||||
}, [sessionService]);
|
||||
|
||||
const [refreshListBouncer, setRefreshListBouncer] = useState(false);
|
||||
|
||||
const refreshList = useCallback(() => {
|
||||
|
@ -138,15 +174,20 @@ export const EventAnnotationGroupTableList = ({
|
|||
savedObjectsTagging={savedObjectsTagging}
|
||||
dataViews={dataViews}
|
||||
createDataView={createDataView}
|
||||
LensEmbeddableComponent={LensEmbeddableComponent}
|
||||
queryInputServices={queryInputServices}
|
||||
searchSessionId={searchSessionId}
|
||||
refreshSearchSession={refreshSearchSession}
|
||||
timePickerQuickRanges={uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES)}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableListViewTable<EventAnnotationGroupContent>
|
||||
id="eventAnnotation"
|
||||
refreshListBouncer={refreshListBouncer}
|
||||
tableCaption={i18n.translate('eventAnnotationComponents.tableList.listTitle', {
|
||||
tableCaption={i18n.translate('eventAnnotationListing.tableList.listTitle', {
|
||||
defaultMessage: 'Annotation Library',
|
||||
})}
|
||||
findItems={fetchItems}
|
||||
|
@ -162,11 +203,12 @@ export const EventAnnotationGroupTableList = ({
|
|||
customTableColumn={getCustomColumn(dataViews)}
|
||||
emptyPrompt={
|
||||
<EuiEmptyPrompt
|
||||
color="transparent"
|
||||
title={
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.tableList.emptyPrompt.title"
|
||||
id="eventAnnotationListing.tableList.emptyPrompt.title"
|
||||
defaultMessage="Create your first annotation in Lens"
|
||||
/>
|
||||
</h2>
|
||||
|
@ -175,27 +217,26 @@ export const EventAnnotationGroupTableList = ({
|
|||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.tableList.emptyPrompt.body"
|
||||
defaultMessage="You can create and save annotations for use across multiple visualization in the
|
||||
Lens visualization editor."
|
||||
id="eventAnnotationListing.tableList.emptyPrompt.body"
|
||||
defaultMessage="You can create and save annotations for use across multiple visualizations in the Lens editor."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton onClick={navigateToLens}>
|
||||
<FormattedMessage
|
||||
id="eventAnnotationComponents.tableList.emptyPrompt.cta"
|
||||
defaultMessage="Create new annotation in Lens"
|
||||
id="eventAnnotationListing.tableList.emptyPrompt.cta"
|
||||
defaultMessage="Create annotation in Lens"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
iconType="flag"
|
||||
/>
|
||||
}
|
||||
entityName={i18n.translate('eventAnnotationComponents.tableList.entityName', {
|
||||
entityName={i18n.translate('eventAnnotationListing.tableList.entityName', {
|
||||
defaultMessage: 'annotation group',
|
||||
})}
|
||||
entityNamePlural={i18n.translate('eventAnnotationComponents.tableList.entityNamePlural', {
|
||||
entityNamePlural={i18n.translate('eventAnnotationListing.tableList.entityNamePlural', {
|
||||
defaultMessage: 'annotation groups',
|
||||
})}
|
||||
onClickTitle={editItem}
|
9
src/plugins/event_annotation_listing/public/constants.ts
Normal file
9
src/plugins/event_annotation_listing/public/constants.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const EVENT_ANNOTATION_APP_NAME = 'annotations';
|
|
@ -16,8 +16,10 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug
|
|||
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryInputServices } from '@kbn/visualization-ui-components';
|
||||
import { RootDragDropProvider } from '@kbn/dom-drag-drop';
|
||||
import { EventAnnotationGroupTableList } from '@kbn/event-annotation-components';
|
||||
import type { EventAnnotationServiceType } from '.';
|
||||
import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
|
||||
import type { EmbeddableComponent as LensEmbeddableComponent } from '@kbn/lens-plugin/public';
|
||||
import { ISessionService } from '@kbn/data-plugin/public';
|
||||
import { EventAnnotationGroupTableList } from './components/table_list';
|
||||
|
||||
export interface EventAnnotationListingPageServices {
|
||||
core: CoreStart;
|
||||
|
@ -27,6 +29,8 @@ export interface EventAnnotationListingPageServices {
|
|||
dataViews: DataView[];
|
||||
createDataView: (spec: DataViewSpec) => Promise<DataView>;
|
||||
queryInputServices: QueryInputServices;
|
||||
LensEmbeddableComponent: LensEmbeddableComponent;
|
||||
sessionService: ISessionService;
|
||||
}
|
||||
|
||||
export const getTableList = (
|
||||
|
@ -54,6 +58,8 @@ export const getTableList = (
|
|||
createDataView={services.createDataView}
|
||||
queryInputServices={services.queryInputServices}
|
||||
navigateToLens={() => services.core.application.navigateToApp('lens')}
|
||||
LensEmbeddableComponent={services.LensEmbeddableComponent}
|
||||
sessionService={services.sessionService}
|
||||
/>
|
||||
</TableListViewKibanaProvider>
|
||||
</RootDragDropProvider>
|
26
src/plugins/event_annotation_listing/public/index.ts
Normal file
26
src/plugins/event_annotation_listing/public/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EventAnnotationListingPlugin } from './plugin';
|
||||
export const plugin = () => new EventAnnotationListingPlugin();
|
||||
export type {
|
||||
EventAnnotationListingPluginSetup as eventAnnotationListingPluginSetup,
|
||||
EventAnnotationListingPluginStart as eventAnnotationListingPluginStart,
|
||||
} from './plugin';
|
||||
export {
|
||||
defaultAnnotationColor,
|
||||
defaultAnnotationRangeColor,
|
||||
isRangeAnnotationConfig,
|
||||
isManualPointAnnotationConfig,
|
||||
isQueryAnnotationConfig,
|
||||
} from '@kbn/event-annotation-common';
|
||||
export {
|
||||
AnnotationEditorControls,
|
||||
annotationsIconSet,
|
||||
getAnnotationAccessor,
|
||||
} from '@kbn/event-annotation-components';
|
101
src/plugins/event_annotation_listing/public/plugin.ts
Normal file
101
src/plugins/event_annotation_listing/public/plugin.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
|
||||
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public/types';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EventAnnotationPluginStart } from '@kbn/event-annotation-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { EventAnnotationListingPageServices } from './get_table_list';
|
||||
|
||||
export interface EventAnnotationListingStartDependencies {
|
||||
savedObjectsManagement: SavedObjectsManagementPluginStart;
|
||||
eventAnnotation: EventAnnotationPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
savedObjectsTagging: SavedObjectTaggingPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
contentManagement: ContentManagementPublicStart;
|
||||
lens: LensPublicStart;
|
||||
}
|
||||
|
||||
interface SetupDependencies {
|
||||
visualizations: VisualizationsSetup;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type EventAnnotationListingPluginStart = void;
|
||||
export type EventAnnotationListingPluginSetup = void;
|
||||
|
||||
/** @public */
|
||||
export class EventAnnotationListingPlugin
|
||||
implements
|
||||
Plugin<
|
||||
EventAnnotationListingPluginSetup,
|
||||
EventAnnotationListingPluginStart,
|
||||
SetupDependencies,
|
||||
EventAnnotationListingStartDependencies
|
||||
>
|
||||
{
|
||||
public setup(
|
||||
core: CoreSetup<EventAnnotationListingStartDependencies>,
|
||||
dependencies: SetupDependencies
|
||||
) {
|
||||
dependencies.visualizations.listingViewRegistry.add({
|
||||
title: i18n.translate('eventAnnotationListing.listingViewTitle', {
|
||||
defaultMessage: 'Annotation groups',
|
||||
}),
|
||||
id: 'annotations',
|
||||
getTableList: async (props) => {
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
|
||||
const eventAnnotationService = await pluginsStart.eventAnnotation.getService();
|
||||
|
||||
const ids = await pluginsStart.dataViews.getIds();
|
||||
const dataViews = await Promise.all(ids.map((id) => pluginsStart.dataViews.get(id)));
|
||||
|
||||
const services: EventAnnotationListingPageServices = {
|
||||
core: coreStart,
|
||||
LensEmbeddableComponent: pluginsStart.lens.EmbeddableComponent,
|
||||
savedObjectsTagging: pluginsStart.savedObjectsTagging,
|
||||
eventAnnotationService,
|
||||
PresentationUtilContextProvider: pluginsStart.presentationUtil.ContextProvider,
|
||||
dataViews,
|
||||
createDataView: pluginsStart.dataViews.create.bind(pluginsStart.dataViews),
|
||||
sessionService: pluginsStart.data.search.session,
|
||||
queryInputServices: {
|
||||
http: coreStart.http,
|
||||
docLinks: coreStart.docLinks,
|
||||
notifications: coreStart.notifications,
|
||||
uiSettings: coreStart.uiSettings,
|
||||
dataViews: pluginsStart.dataViews,
|
||||
unifiedSearch: pluginsStart.unifiedSearch,
|
||||
data: pluginsStart.data,
|
||||
storage: new Storage(localStorage),
|
||||
},
|
||||
};
|
||||
|
||||
const { getTableList } = await import('./get_table_list');
|
||||
return getTableList(props, services);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: object): void {
|
||||
// nothing to do here
|
||||
}
|
||||
}
|
48
src/plugins/event_annotation_listing/tsconfig.json
Normal file
48
src/plugins/event_annotation_listing/tsconfig.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
"public/**/*",
|
||||
"server/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/data-plugin",
|
||||
"@kbn/i18n",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/saved-objects-management-plugin",
|
||||
"@kbn/saved-objects-tagging-plugin",
|
||||
"@kbn/presentation-util-plugin",
|
||||
"@kbn/visualizations-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/visualization-ui-components",
|
||||
"@kbn/dom-drag-drop",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/saved-objects-tagging-oss-plugin",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/content-management-table-list-view-table",
|
||||
"@kbn/content-management-tabbed-table-list-view",
|
||||
"@kbn/content-management-plugin",
|
||||
"@kbn/event-annotation-plugin",
|
||||
"@kbn/event-annotation-components",
|
||||
"@kbn/event-annotation-common",
|
||||
"@kbn/lens-plugin",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/expressions-plugin",
|
||||
"@kbn/es-query",
|
||||
"@kbn/core-ui-settings-browser",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-saved-objects-api-browser"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
182
test/functional/apps/visualize/group3/_annotation_listing.ts
Normal file
182
test/functional/apps/visualize/group3/_annotation_listing.ts
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['visualize', 'annotationEditor']);
|
||||
const listingTable = getService('listingTable');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const log = getService('log');
|
||||
|
||||
describe('annotation listing page', function () {
|
||||
before(async function () {
|
||||
await kibanaServer.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/annotation_listing_page_search'
|
||||
);
|
||||
// we need to test the missing data view scenario, so delete one of them
|
||||
// (can't just omit it from the archive because Kibana won't import with broken references)
|
||||
log.info(`deleting one data view to replicate missing data view scenario...`);
|
||||
await kibanaServer.request({
|
||||
method: 'DELETE',
|
||||
path: `/api/data_views/data_view/data-view-to-delete`,
|
||||
});
|
||||
|
||||
await PageObjects.visualize.gotoVisualizationLandingPage();
|
||||
await PageObjects.visualize.selectAnnotationsTab();
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
log.info(`unloading annotations and data views`);
|
||||
await kibanaServer.importExport.unload(
|
||||
'test/functional/fixtures/kbn_archiver/annotation_listing_page_search'
|
||||
);
|
||||
});
|
||||
|
||||
describe('search', function () {
|
||||
afterEach(async function () {
|
||||
await listingTable.clearSearchFilter();
|
||||
});
|
||||
|
||||
describe('by text', () => {
|
||||
it('matches on the first word', async function () {
|
||||
await listingTable.searchForItemWithName('search');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
|
||||
it('matches the second word', async function () {
|
||||
await listingTable.searchForItemWithName('for');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
|
||||
it('matches the second word prefix', async function () {
|
||||
await listingTable.searchForItemWithName('fo');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
|
||||
it('does not match mid word', async function () {
|
||||
await listingTable.searchForItemWithName('earc');
|
||||
// custom timeout so this test moves faster
|
||||
await listingTable.expectItemsCount('eventAnnotation', 0, 1000);
|
||||
});
|
||||
|
||||
it('is case insensitive', async function () {
|
||||
await listingTable.searchForItemWithName('SEARCH');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
|
||||
it('is using AND operator', async function () {
|
||||
await listingTable.searchForItemWithName('search banana');
|
||||
// custom timeout so this test moves faster
|
||||
await listingTable.expectItemsCount('eventAnnotation', 0, 1000);
|
||||
});
|
||||
|
||||
it('matches on description', async function () {
|
||||
await listingTable.searchForItemWithName('i have a description');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by tag', () => {
|
||||
it('filters by tag', async () => {
|
||||
await listingTable.selectFilterTags('tag');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 7);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', function () {
|
||||
it('deletes some groups', async function () {
|
||||
await listingTable.deleteItem('to delete 1');
|
||||
await listingTable.deleteItem('to delete 2');
|
||||
await listingTable.searchForItemWithName('to delete');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 0, 1000);
|
||||
await listingTable.clearSearchFilter();
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
it('edits group metadata', async function () {
|
||||
await listingTable.clickItemLink('eventAnnotation', 'group 3');
|
||||
await PageObjects.annotationEditor.editGroupMetadata({
|
||||
title: 'edited title',
|
||||
description: 'edited description',
|
||||
});
|
||||
await PageObjects.annotationEditor.saveGroup();
|
||||
|
||||
await listingTable.searchForItemWithName('edited title');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
|
||||
await listingTable.searchForItemWithName('edited description');
|
||||
await listingTable.expectItemsCount('eventAnnotation', 1);
|
||||
});
|
||||
|
||||
describe('individual annotations', () => {
|
||||
it('edits an existing annotation', async function () {
|
||||
await listingTable.clickItemLink('eventAnnotation', 'edited title');
|
||||
expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(1);
|
||||
|
||||
await PageObjects.annotationEditor.openAnnotation();
|
||||
await PageObjects.annotationEditor.configureAnnotation({
|
||||
query: 'my query',
|
||||
lineThickness: 5,
|
||||
color: '#FF0000',
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a new annotation', async function () {
|
||||
await PageObjects.annotationEditor.addAnnotation({
|
||||
query: 'other query',
|
||||
lineThickness: 3,
|
||||
color: '#00FF00',
|
||||
});
|
||||
|
||||
retry.try(async () => {
|
||||
expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes an annotation', async function () {
|
||||
await PageObjects.annotationEditor.removeAnnotation();
|
||||
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(1);
|
||||
});
|
||||
|
||||
await PageObjects.annotationEditor.saveGroup();
|
||||
await listingTable.clearSearchFilter();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('data view switching', () => {
|
||||
it('recovers from missing data view', async () => {
|
||||
await listingTable.clickItemLink('eventAnnotation', 'missing data view');
|
||||
|
||||
await retry.try(async () => {
|
||||
expect(await PageObjects.annotationEditor.showingMissingDataViewPrompt()).to.be(true);
|
||||
});
|
||||
|
||||
await retry.try(async () => {
|
||||
await PageObjects.annotationEditor.editGroupMetadata({
|
||||
dataView: 'logs*',
|
||||
});
|
||||
expect(await PageObjects.annotationEditor.showingMissingDataViewPrompt()).to.be(false);
|
||||
expect(await find.byCssSelector('canvas')).to.be.ok();
|
||||
});
|
||||
|
||||
await PageObjects.annotationEditor.saveGroup();
|
||||
});
|
||||
|
||||
it('recovers from missing field in data view', () => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -30,5 +30,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./_visualize_listing'));
|
||||
loadTestFile(require.resolve('./_add_to_dashboard.ts'));
|
||||
loadTestFile(require.resolve('./_pie_chart'));
|
||||
loadTestFile(require.resolve('./_annotation_listing'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,832 @@
|
|||
{
|
||||
"attributes": {
|
||||
"fieldAttrs": "{}",
|
||||
"fieldFormatMap": "{}",
|
||||
"fields": "[]",
|
||||
"name": "logs*",
|
||||
"runtimeFieldMap": "{}",
|
||||
"sourceFilters": "[]",
|
||||
"timeFieldName": "@timestamp",
|
||||
"title": "logs*",
|
||||
"typeMeta": "{}"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-07T17:23:20.906Z",
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"managed": false,
|
||||
"references": [],
|
||||
"type": "index-pattern",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"updated_at": "2023-09-11T15:50:59.227Z",
|
||||
"version": "WzIyNywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"fieldAttrs": "{}",
|
||||
"fieldFormatMap": "{\"hour_of_day\":{}}",
|
||||
"fields": "[]",
|
||||
"name": "To Delete!",
|
||||
"runtimeFieldMap": "{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}",
|
||||
"sourceFilters": "[]",
|
||||
"timeFieldName": "timestamp",
|
||||
"title": "kibana_sample_data_logs",
|
||||
"typeMeta": "{}"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-07T17:23:20.906Z",
|
||||
"id": "data-view-to-delete",
|
||||
"managed": false,
|
||||
"references": [],
|
||||
"type": "index-pattern",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"updated_at": "2023-09-11T15:50:59.227Z",
|
||||
"version": "WzIyNywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 19"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T14:00:20.704Z",
|
||||
"id": "b6071e00-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T14:00:20.704Z",
|
||||
"version": "WzMxNywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "i have a description",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 21"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T14:00:12.994Z",
|
||||
"id": "b16eaa20-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T14:00:12.994Z",
|
||||
"version": "WzMxNiwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "search for me"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T14:01:40.768Z",
|
||||
"id": "e5bfc2f0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T14:02:00.597Z",
|
||||
"version": "WzMyMCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "to delete 1"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:53.977Z",
|
||||
"id": "a618e690-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T14:00:06.429Z",
|
||||
"version": "WzMxNSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "to delete 2"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:47.961Z",
|
||||
"id": "a282ee90-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:47.961Z",
|
||||
"version": "WzMxMiwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 16"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:42.118Z",
|
||||
"id": "9f075c60-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:42.118Z",
|
||||
"version": "WzMxMSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "missing data view"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:36.447Z",
|
||||
"id": "9ba608f0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "data-view-to-delete",
|
||||
"name": "event-annotation-group_dataView-ref-data-view-to-delete",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:36.447Z",
|
||||
"version": "WzMxMCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 14"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:30.997Z",
|
||||
"id": "98666e50-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:30.997Z",
|
||||
"version": "WzMwOSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 13"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:25.669Z",
|
||||
"id": "95397150-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:25.669Z",
|
||||
"version": "WzMwOCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 12"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:20.653Z",
|
||||
"id": "923c0fd0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:20.653Z",
|
||||
"version": "WzMwNywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 11"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:14.944Z",
|
||||
"id": "8ed4f000-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:14.944Z",
|
||||
"version": "WzMwNiwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 10"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:09.600Z",
|
||||
"id": "8ba58200-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:09.600Z",
|
||||
"version": "WzMwNSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 9"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:59:02.338Z",
|
||||
"id": "87514310-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:59:02.338Z",
|
||||
"version": "WzMwNCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 8"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:55.464Z",
|
||||
"id": "83388680-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:55.464Z",
|
||||
"version": "WzMwMywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"color": "#dac7c4",
|
||||
"description": "a tag to filter by",
|
||||
"name": "tag"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-08T18:40:42.018Z",
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"managed": false,
|
||||
"references": [],
|
||||
"type": "tag",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"updated_at": "2023-09-08T18:40:42.018Z",
|
||||
"version": "WzI3NDUsMV0="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 7"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:46.671Z",
|
||||
"id": "7dfad1f0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:46.671Z",
|
||||
"version": "WzMwMiwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 6"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:37.886Z",
|
||||
"id": "78be55e0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:37.886Z",
|
||||
"version": "WzMwMSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 5"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:32.312Z",
|
||||
"id": "756bcf80-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:32.312Z",
|
||||
"version": "WzMwMCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 4"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:26.947Z",
|
||||
"id": "72392d30-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:26.947Z",
|
||||
"version": "WzI5OSwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 3"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:21.136Z",
|
||||
"id": "6ec27d00-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:21.136Z",
|
||||
"version": "WzI5OCwxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 2"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:15.691Z",
|
||||
"id": "6b83a5b0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:15.691Z",
|
||||
"version": "WzI5NywxXQ=="
|
||||
}
|
||||
|
||||
{
|
||||
"attributes": {
|
||||
"annotations": [
|
||||
{
|
||||
"icon": "triangle",
|
||||
"id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843",
|
||||
"key": {
|
||||
"timestamp": "2023-09-13T16:30:00.000Z",
|
||||
"type": "point_in_time"
|
||||
},
|
||||
"label": "Event",
|
||||
"type": "manual"
|
||||
}
|
||||
],
|
||||
"dataViewSpec": null,
|
||||
"description": "",
|
||||
"ignoreGlobalFilters": true,
|
||||
"title": "group 1"
|
||||
},
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"created_at": "2023-09-12T13:58:07.740Z",
|
||||
"id": "66c66bc0-5174-11ee-a5c4-7dce2e3293a7",
|
||||
"managed": false,
|
||||
"references": [
|
||||
{
|
||||
"id": "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
"type": "index-pattern"
|
||||
},
|
||||
{
|
||||
"id": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"name": "36a8f020-4e77-11ee-bb97-116581699678",
|
||||
"type": "tag"
|
||||
}
|
||||
],
|
||||
"type": "event-annotation-group",
|
||||
"updated_at": "2023-09-12T13:58:07.740Z",
|
||||
"version": "WzI5NiwxXQ=="
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { FtrService } from '../ftr_provider_context';
|
||||
|
||||
export class AnnotationEditorPageObject extends FtrService {
|
||||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||
private readonly retry = this.ctx.getService('retry');
|
||||
|
||||
/**
|
||||
* Fills out group metadata
|
||||
*/
|
||||
public async editGroupMetadata(metadata: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
dataView?: string;
|
||||
}) {
|
||||
if (metadata.title) {
|
||||
await this.testSubjects.setValue('annotationGroupTitle', metadata.title);
|
||||
}
|
||||
|
||||
if (metadata.description) {
|
||||
await this.testSubjects.setValue('annotationGroupDescription', metadata.description);
|
||||
}
|
||||
|
||||
if (metadata.dataView) {
|
||||
await this.testSubjects.setValue('annotationDataViewSelection', metadata.dataView);
|
||||
}
|
||||
}
|
||||
|
||||
public async saveGroup() {
|
||||
await this.testSubjects.click('saveAnnotationGroup');
|
||||
}
|
||||
|
||||
public async getAnnotationCount() {
|
||||
const triggers = await this.testSubjects.findAll('lns-dimensionTrigger');
|
||||
return triggers.length;
|
||||
}
|
||||
|
||||
public async openAnnotation() {
|
||||
await this.testSubjects.click('lns-dimensionTrigger');
|
||||
}
|
||||
|
||||
public async configureAnnotation(config: {
|
||||
query: string;
|
||||
lineThickness: number;
|
||||
color: string;
|
||||
}) {
|
||||
await this.testSubjects.click('lnsXY_annotation_query');
|
||||
|
||||
const queryInput = await this.testSubjects.find('annotation-query-based-query-input');
|
||||
await queryInput.type(config.query);
|
||||
|
||||
await this.testSubjects.setValue('lnsXYThickness', '' + config.lineThickness);
|
||||
|
||||
await this.testSubjects.setValue(
|
||||
'euiColorPickerAnchor indexPattern-dimension-colorPicker',
|
||||
config.color
|
||||
);
|
||||
|
||||
await this.retry.waitFor('annotation editor UI to close', async () => {
|
||||
await this.testSubjects.click('backToGroupSettings');
|
||||
return !(await this.testSubjects.exists('backToGroupSettings'));
|
||||
});
|
||||
}
|
||||
|
||||
public async addAnnotation(config: { query: string; lineThickness: number; color: string }) {
|
||||
await this.testSubjects.click('addAnnotation');
|
||||
await this.configureAnnotation(config);
|
||||
}
|
||||
|
||||
public async removeAnnotation() {
|
||||
await this.testSubjects.click('indexPattern-dimension-remove');
|
||||
}
|
||||
|
||||
public async showingMissingDataViewPrompt() {
|
||||
return await this.testSubjects.exists('missingDataViewPrompt');
|
||||
}
|
||||
}
|
|
@ -34,8 +34,10 @@ import { DashboardPageControls } from './dashboard_page_controls';
|
|||
import { UnifiedSearchPageObject } from './unified_search_page';
|
||||
import { UnifiedFieldListPageObject } from './unified_field_list';
|
||||
import { FilesManagementPageObject } from './files_management';
|
||||
import { AnnotationEditorPageObject } from './annotation_library_editor_page';
|
||||
|
||||
export const pageObjects = {
|
||||
annotationEditor: AnnotationEditorPageObject,
|
||||
common: CommonPageObject,
|
||||
console: ConsolePageObject,
|
||||
context: ContextPageObject,
|
||||
|
|
|
@ -10,7 +10,12 @@ import expect from '@kbn/expect';
|
|||
import { FtrService } from '../ftr_provider_context';
|
||||
|
||||
type AppName = keyof typeof PREFIX_MAP;
|
||||
const PREFIX_MAP = { visualize: 'vis', dashboard: 'dashboard', map: 'map' };
|
||||
const PREFIX_MAP = {
|
||||
visualize: 'vis',
|
||||
dashboard: 'dashboard',
|
||||
map: 'map',
|
||||
eventAnnotation: 'eventAnnotation',
|
||||
};
|
||||
|
||||
export class ListingTableService extends FtrService {
|
||||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||
|
@ -186,10 +191,11 @@ export class ListingTableService extends FtrService {
|
|||
/**
|
||||
* Returns items count on landing page
|
||||
*/
|
||||
public async expectItemsCount(appName: AppName, count: number) {
|
||||
public async expectItemsCount(appName: AppName, count: number, findTimeout?: number) {
|
||||
await this.retry.try(async () => {
|
||||
const elements = await this.find.allByCssSelector(
|
||||
`[data-test-subj^="${PREFIX_MAP[appName]}ListingTitleLink"]`
|
||||
`[data-test-subj^="${PREFIX_MAP[appName]}ListingTitleLink"]`,
|
||||
findTimeout ?? 10000
|
||||
);
|
||||
expect(elements.length).to.equal(count);
|
||||
});
|
||||
|
|
|
@ -722,6 +722,8 @@
|
|||
"@kbn/event-annotation-common/*": ["packages/kbn-event-annotation-common/*"],
|
||||
"@kbn/event-annotation-components": ["packages/kbn-event-annotation-components"],
|
||||
"@kbn/event-annotation-components/*": ["packages/kbn-event-annotation-components/*"],
|
||||
"@kbn/event-annotation-listing-plugin": ["src/plugins/event_annotation_listing"],
|
||||
"@kbn/event-annotation-listing-plugin/*": ["src/plugins/event_annotation_listing/*"],
|
||||
"@kbn/event-annotation-plugin": ["src/plugins/event_annotation"],
|
||||
"@kbn/event-annotation-plugin/*": ["src/plugins/event_annotation/*"],
|
||||
"@kbn/event-log-fixture-plugin": ["x-pack/test/plugin_api_integration/plugins/event_log"],
|
||||
|
|
|
@ -461,7 +461,6 @@ export class Embeddable
|
|||
this.expressionRenderer = deps.expressionRenderer;
|
||||
this.initializeSavedVis(initialInput)
|
||||
.then(() => {
|
||||
this.loadUserMessages();
|
||||
this.reload();
|
||||
})
|
||||
.catch((e) => this.onFatalError(e));
|
||||
|
@ -595,17 +594,6 @@ export class Embeddable
|
|||
private fullAttributes: LensSavedObjectAttributes | undefined;
|
||||
|
||||
public getUserMessages: UserMessagesGetter = (locationId, filters) => {
|
||||
return filterAndSortUserMessages(
|
||||
[...this._userMessages, ...Object.values(this.additionalUserMessages)],
|
||||
locationId,
|
||||
filters ?? {}
|
||||
);
|
||||
};
|
||||
|
||||
private _userMessages: UserMessage[] = [];
|
||||
|
||||
// loads all available user messages
|
||||
private loadUserMessages() {
|
||||
const userMessages: UserMessage[] = [];
|
||||
|
||||
userMessages.push(
|
||||
|
@ -672,8 +660,12 @@ export class Embeddable
|
|||
}) ?? [])
|
||||
);
|
||||
|
||||
this._userMessages = userMessages;
|
||||
}
|
||||
return filterAndSortUserMessages(
|
||||
[...userMessages, ...Object.values(this.additionalUserMessages)],
|
||||
locationId,
|
||||
filters ?? {}
|
||||
);
|
||||
};
|
||||
|
||||
private additionalUserMessages: Record<string, UserMessage> = {};
|
||||
|
||||
|
@ -908,12 +900,7 @@ export class Embeddable
|
|||
|
||||
this.activeData = newActiveData;
|
||||
|
||||
// Refresh messanges if info type is found as with active data
|
||||
// these messages can be enriched
|
||||
if (this._userMessages.some(({ severity }) => severity === 'info')) {
|
||||
this.loadUserMessages();
|
||||
this.renderUserMessages();
|
||||
}
|
||||
this.renderUserMessages();
|
||||
};
|
||||
|
||||
private onRender: ExpressionWrapperProps['onRender$'] = () => {
|
||||
|
@ -992,6 +979,7 @@ export class Embeddable
|
|||
return;
|
||||
}
|
||||
super.render(domNode as HTMLElement);
|
||||
|
||||
if (this.input.onLoad) {
|
||||
this.input.onLoad(true);
|
||||
}
|
||||
|
@ -1291,7 +1279,6 @@ export class Embeddable
|
|||
|
||||
this.expression = ast;
|
||||
|
||||
this.loadUserMessages();
|
||||
this.reload();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -86,6 +86,8 @@ export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceI
|
|||
showInspector?: boolean;
|
||||
};
|
||||
|
||||
export type EmbeddableComponent = React.ComponentType<EmbeddableComponentProps>;
|
||||
|
||||
interface PluginsStartDependencies {
|
||||
uiActions: UiActionsStart;
|
||||
embeddable: EmbeddableStart;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { LensPlugin } from './plugin';
|
|||
|
||||
export type {
|
||||
EmbeddableComponentProps,
|
||||
EmbeddableComponent,
|
||||
TypedLensByValueInput,
|
||||
} from './embeddable/embeddable_component';
|
||||
export type {
|
||||
|
|
|
@ -113,10 +113,7 @@ import { visualizeDashboardVisualizePanelction } from './trigger_actions/dashboa
|
|||
|
||||
import type { LensEmbeddableInput } from './embeddable';
|
||||
import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory';
|
||||
import {
|
||||
EmbeddableComponentProps,
|
||||
getEmbeddableComponent,
|
||||
} from './embeddable/embeddable_component';
|
||||
import { EmbeddableComponent, getEmbeddableComponent } from './embeddable/embeddable_component';
|
||||
import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy';
|
||||
import type { SaveModalContainerProps } from './app_plugin/save_modal_container';
|
||||
|
||||
|
@ -209,7 +206,7 @@ export interface LensPublicStart {
|
|||
*
|
||||
* @experimental
|
||||
*/
|
||||
EmbeddableComponent: React.ComponentType<EmbeddableComponentProps>;
|
||||
EmbeddableComponent: EmbeddableComponent;
|
||||
/**
|
||||
* React component which can be used to embed a Lens Visualization Save Modal Component.
|
||||
* See `x-pack/examples/embedded_lens_example` for exemplary usage.
|
||||
|
|
|
@ -7030,7 +7030,6 @@
|
|||
"visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "Couleur de cette dimension : {hex}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.auto": "Auto",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.label": "Couleur de la série",
|
||||
"visualizationUiComponents.colorPicker.tooltip.auto": "Lens choisit automatiquement des couleurs à votre place sauf si vous spécifiez une couleur personnalisée.",
|
||||
"visualizationUiComponents.colorPicker.tooltip.custom": "Effacez la couleur personnalisée pour revenir au mode \"Auto\".",
|
||||
|
@ -39430,7 +39429,6 @@
|
|||
"eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "Basculer pour ignorer les filtres globaux pour l'annotation",
|
||||
"eventAnnotation.group.args.annotationGroups": "Groupe d'annotations",
|
||||
"eventAnnotation.group.description": "Groupe d'annotations d'événement",
|
||||
"eventAnnotation.listingViewTitle": "Groupes d'annotations",
|
||||
"eventAnnotation.manualAnnotation.args.color": "Couleur de la ligne",
|
||||
"eventAnnotation.manualAnnotation.args.icon": "Icône facultative utilisée pour les lignes d'annotation",
|
||||
"eventAnnotation.manualAnnotation.args.id": "ID de l'annotation",
|
||||
|
|
|
@ -7046,7 +7046,6 @@
|
|||
"visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "このディメンションの色:{hex}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.auto": "自動",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.label": "系列色",
|
||||
"visualizationUiComponents.colorPicker.tooltip.auto": "カスタム色を指定しない場合、Lensは自動的に色を選択します。",
|
||||
"visualizationUiComponents.colorPicker.tooltip.custom": "[自動]モードに戻すには、カスタム色をオフにしてください。",
|
||||
|
@ -39421,7 +39420,6 @@
|
|||
"eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "注釈のグローバルフィルターを無視するスイッチ",
|
||||
"eventAnnotation.group.args.annotationGroups": "注釈グループ",
|
||||
"eventAnnotation.group.description": "イベント注釈グループ",
|
||||
"eventAnnotation.listingViewTitle": "注釈グループ",
|
||||
"eventAnnotation.manualAnnotation.args.color": "行の色",
|
||||
"eventAnnotation.manualAnnotation.args.icon": "注釈行で使用される任意のアイコン",
|
||||
"eventAnnotation.manualAnnotation.args.id": "注釈のID",
|
||||
|
|
|
@ -7045,7 +7045,6 @@
|
|||
"visualizationUiComponents.dimensionButtonIcon.colorIndicatorLabel": "此维度的颜色:{hex}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderKql": "{example}",
|
||||
"visualizationUiComponents.queryInput.queryPlaceholderLucene": "{example}",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.auto": "自动",
|
||||
"visualizationUiComponents.colorPicker.seriesColor.label": "系列颜色",
|
||||
"visualizationUiComponents.colorPicker.tooltip.auto": "Lens 自动为您选取颜色,除非您指定定制颜色。",
|
||||
"visualizationUiComponents.colorPicker.tooltip.custom": "清除定制颜色以返回到“自动”模式。",
|
||||
|
@ -39415,7 +39414,6 @@
|
|||
"eventAnnotation.group.args.annotationConfigs.ignoreGlobalFilters.help": "进行切换以忽略标注的全局筛选",
|
||||
"eventAnnotation.group.args.annotationGroups": "标注组",
|
||||
"eventAnnotation.group.description": "事件标注组",
|
||||
"eventAnnotation.listingViewTitle": "标注组",
|
||||
"eventAnnotation.manualAnnotation.args.color": "线条的颜色",
|
||||
"eventAnnotation.manualAnnotation.args.icon": "用于标注线条的可选图标",
|
||||
"eventAnnotation.manualAnnotation.args.id": "标注的 ID",
|
||||
|
|
|
@ -4393,6 +4393,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/event-annotation-listing-plugin@link:src/plugins/event_annotation_listing":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/event-annotation-plugin@link:src/plugins/event_annotation":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue