mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Event annotations] Lens design improvements (#159057)
This commit is contained in:
parent
b697c1d651
commit
1d82d2cb61
23 changed files with 501 additions and 286 deletions
|
@ -120,6 +120,9 @@ export interface EventAnnotationGroupSearchQuery {
|
|||
searchFields?: string[];
|
||||
}
|
||||
|
||||
export type EventAnnotationGroupSearchIn = SearchIn<EventAnnotationGroupContentType, {}>;
|
||||
export type EventAnnotationGroupSearchIn = SearchIn<
|
||||
EventAnnotationGroupContentType,
|
||||
EventAnnotationGroupSearchQuery
|
||||
>;
|
||||
|
||||
export type EventAnnotationGroupSearchOut = SearchResult<EventAnnotationGroupSavedObject>;
|
||||
|
|
|
@ -67,35 +67,37 @@ export const EventAnnotationGroupSavedObjectFinder = ({
|
|||
direction="column"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
titleSize="xs"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyPromptTitle"
|
||||
defaultMessage="Start by adding an annotation layer"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<EuiFlexItem>
|
||||
<EuiEmptyPrompt
|
||||
titleSize="xs"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyPromptDescription"
|
||||
defaultMessage="There are currently no annotations available to select from the library. Create a new layer to add annotations."
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyPromptTitle"
|
||||
defaultMessage="Start by adding an annotation layer"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
actions={
|
||||
<EuiButton onClick={() => onCreateNew()} size="s">
|
||||
<FormattedMessage
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyCTA"
|
||||
defaultMessage="Create annotation layer"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyPromptDescription"
|
||||
defaultMessage="There are currently no annotations available to select from the library. Create a new layer to add annotations."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
actions={
|
||||
<EuiButton onClick={() => onCreateNew()} size="s">
|
||||
<FormattedMessage
|
||||
id="eventAnnotation.eventAnnotationGroup.savedObjectFinder.emptyCTA"
|
||||
defaultMessage="Create annotation layer"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<SavedObjectFinder
|
||||
|
|
|
@ -34,7 +34,8 @@ import {
|
|||
isQueryAnnotationConfig,
|
||||
} from './helpers';
|
||||
import { EventAnnotationGroupSavedObjectFinder } from '../components/event_annotation_group_saved_object_finder';
|
||||
import {
|
||||
import { CONTENT_ID } from '../../common/content_management';
|
||||
import type {
|
||||
EventAnnotationGroupCreateIn,
|
||||
EventAnnotationGroupCreateOut,
|
||||
EventAnnotationGroupDeleteIn,
|
||||
|
@ -106,7 +107,7 @@ export function getEventAnnotationService(
|
|||
savedObjectId: string
|
||||
): Promise<EventAnnotationGroupConfig> => {
|
||||
const savedObject = await client.get<EventAnnotationGroupGetIn, EventAnnotationGroupGetOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
id: savedObjectId,
|
||||
});
|
||||
|
||||
|
@ -117,6 +118,29 @@ export function getEventAnnotationService(
|
|||
return mapSavedObjectToGroupConfig(savedObject.item);
|
||||
};
|
||||
|
||||
const groupExistsWithTitle = async (title: string): Promise<boolean> => {
|
||||
const { hits } = await client.search<
|
||||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
contentTypeId: CONTENT_ID,
|
||||
query: {
|
||||
text: title,
|
||||
},
|
||||
options: {
|
||||
searchFields: ['title'],
|
||||
},
|
||||
});
|
||||
|
||||
for (const hit of hits) {
|
||||
if (hit.attributes.title.toLowerCase() === title.toLowerCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const findAnnotationGroupContent = async (
|
||||
searchTerm: string,
|
||||
pageSize: number,
|
||||
|
@ -138,7 +162,7 @@ export function getEventAnnotationService(
|
|||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
query: {
|
||||
text: searchOptions.search,
|
||||
},
|
||||
|
@ -153,7 +177,7 @@ export function getEventAnnotationService(
|
|||
const deleteAnnotationGroups = async (ids: string[]): Promise<void> => {
|
||||
for (const id of ids) {
|
||||
await client.delete<EventAnnotationGroupDeleteIn, EventAnnotationGroupDeleteOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
@ -223,7 +247,7 @@ export function getEventAnnotationService(
|
|||
|
||||
const groupSavedObjectId = (
|
||||
await client.create<EventAnnotationGroupCreateIn, EventAnnotationGroupCreateOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
data: {
|
||||
...attributes,
|
||||
},
|
||||
|
@ -243,7 +267,7 @@ export function getEventAnnotationService(
|
|||
const { attributes, references } = getAnnotationGroupAttributesAndReferences(group);
|
||||
|
||||
await client.update<EventAnnotationGroupUpdateIn, EventAnnotationGroupUpdateOut>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
id: annotationGroupId,
|
||||
data: {
|
||||
...attributes,
|
||||
|
@ -259,7 +283,7 @@ export function getEventAnnotationService(
|
|||
EventAnnotationGroupSearchIn,
|
||||
EventAnnotationGroupSearchOut
|
||||
>({
|
||||
contentTypeId: EVENT_ANNOTATION_GROUP_TYPE,
|
||||
contentTypeId: CONTENT_ID,
|
||||
query: {
|
||||
text: '*',
|
||||
},
|
||||
|
@ -270,6 +294,7 @@ export function getEventAnnotationService(
|
|||
|
||||
return {
|
||||
loadAnnotationGroup,
|
||||
groupExistsWithTitle,
|
||||
updateAnnotationGroup,
|
||||
createAnnotationGroup,
|
||||
deleteAnnotationGroups,
|
||||
|
|
|
@ -14,6 +14,7 @@ import { EventAnnotationConfig, EventAnnotationGroupConfig } from '../../common'
|
|||
|
||||
export interface EventAnnotationServiceType {
|
||||
loadAnnotationGroup: (savedObjectId: string) => Promise<EventAnnotationGroupConfig>;
|
||||
groupExistsWithTitle: (title: string) => Promise<boolean>;
|
||||
findAnnotationGroupContent: (
|
||||
searchTerm: string,
|
||||
pageSize: number,
|
||||
|
|
|
@ -268,9 +268,11 @@ export class EventAnnotationGroupStorage
|
|||
EventAnnotationGroupSearchQuery,
|
||||
EventAnnotationGroupSearchQuery
|
||||
>(options);
|
||||
|
||||
if (optionsError) {
|
||||
throw Boom.badRequest(`Invalid payload. ${optionsError.message}`);
|
||||
}
|
||||
|
||||
const { searchFields = ['title^3', 'description'], types = [SO_TYPE] } = optionsToLatest;
|
||||
|
||||
const { included, excluded } = query.tags ?? {};
|
||||
|
|
|
@ -28,9 +28,11 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
|
|||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
error="A title is required"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
isInvalid={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Title"
|
||||
|
@ -85,43 +87,41 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
|
|||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "fy4vru",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
align-items: center;
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
`;
|
||||
|
@ -154,9 +154,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
|
|||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
error="A title is required"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
isInvalid={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Title"
|
||||
|
@ -211,43 +213,41 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
|
|||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "fy4vru",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
align-items: center;
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
`;
|
||||
|
@ -280,9 +280,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
|
|||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
error="A title is required"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
isInvalid={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Title"
|
||||
|
@ -337,43 +339,41 @@ exports[`SavedObjectSaveModal should render matching snapshot when custom isVali
|
|||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "fy4vru",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
align-items: center;
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
`;
|
||||
|
@ -410,9 +410,11 @@ exports[`SavedObjectSaveModal should render matching snapshot when given options
|
|||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
error="A title is required"
|
||||
fullWidth={true}
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={false}
|
||||
isInvalid={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Title"
|
||||
|
@ -477,43 +479,41 @@ exports[`SavedObjectSaveModal should render matching snapshot when given options
|
|||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "fy4vru",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
align-items: center;
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={true}
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="saveCancelButton"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="confirmSaveSavedObjectButton"
|
||||
fill={true}
|
||||
form="generated-id_form"
|
||||
isLoading={false}
|
||||
size="m"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
`;
|
||||
|
|
|
@ -30,7 +30,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import React from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export interface OnSaveProps {
|
||||
newTitle: string;
|
||||
|
@ -93,11 +92,19 @@ export class SavedObjectSaveModal extends React.Component<Props, SaveModalState>
|
|||
|
||||
const hasColumns = !!this.props.rightOptions;
|
||||
|
||||
const titleInputValid =
|
||||
hasAttemptedSubmit &&
|
||||
((!isTitleDuplicateConfirmed && hasTitleDuplicate) || title.length === 0);
|
||||
|
||||
const formBodyContent = (
|
||||
<>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={<FormattedMessage id="savedObjects.saveModal.titleLabel" defaultMessage="Title" />}
|
||||
isInvalid={titleInputValid}
|
||||
error={i18n.translate('savedObjects.saveModal.titleRequired', {
|
||||
defaultMessage: 'A title is required',
|
||||
})}
|
||||
>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
|
@ -105,10 +112,7 @@ export class SavedObjectSaveModal extends React.Component<Props, SaveModalState>
|
|||
data-test-subj="savedObjectTitle"
|
||||
value={title}
|
||||
onChange={this.onTitleChange}
|
||||
isInvalid={
|
||||
hasAttemptedSubmit &&
|
||||
((!isTitleDuplicateConfirmed && hasTitleDuplicate) || title.length === 0)
|
||||
}
|
||||
isInvalid={titleInputValid}
|
||||
aria-describedby={this.state.hasTitleDuplicate ? duplicateWarningId : undefined}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -167,20 +171,19 @@ export class SavedObjectSaveModal extends React.Component<Props, SaveModalState>
|
|||
</EuiForm>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter
|
||||
css={css`
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow>{this.renderCopyOnSave()}</EuiFlexItem>
|
||||
<EuiButtonEmpty data-test-subj="saveCancelButton" onClick={this.props.onClose}>
|
||||
<FormattedMessage
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
{this.renderConfirmButton()}
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
{this.props.showCopyOnSave && <EuiFlexItem grow>{this.renderCopyOnSave()}</EuiFlexItem>}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty data-test-subj="saveCancelButton" onClick={this.props.onClose}>
|
||||
<FormattedMessage
|
||||
id="savedObjects.saveModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{this.renderConfirmButton()}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
|
@ -351,10 +354,6 @@ export class SavedObjectSaveModal extends React.Component<Props, SaveModalState>
|
|||
};
|
||||
|
||||
private renderCopyOnSave = () => {
|
||||
if (!this.props.showCopyOnSave) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
data-test-subj="saveAsNewCheckbox"
|
||||
|
|
|
@ -47,6 +47,7 @@ export const getSharedActions = ({
|
|||
openLayerSettings,
|
||||
onCloneLayer,
|
||||
onRemoveLayer,
|
||||
customRemoveModalText,
|
||||
}: {
|
||||
onRemoveLayer: () => void;
|
||||
onCloneLayer: () => void;
|
||||
|
@ -54,12 +55,12 @@ export const getSharedActions = ({
|
|||
layerId: string;
|
||||
isOnlyLayer: boolean;
|
||||
activeVisualization: Visualization;
|
||||
visualizationState: unknown;
|
||||
layerType?: LayerType;
|
||||
isTextBasedLanguage?: boolean;
|
||||
hasLayerSettings: boolean;
|
||||
openLayerSettings: () => void;
|
||||
core: Pick<CoreStart, 'overlays' | 'theme'>;
|
||||
customRemoveModalText?: { title?: string; description?: string };
|
||||
}) => [
|
||||
getOpenLayerSettingsAction({
|
||||
hasLayerSettings,
|
||||
|
@ -77,6 +78,7 @@ export const getSharedActions = ({
|
|||
layerType,
|
||||
isOnlyLayer,
|
||||
core,
|
||||
customModalText: customRemoveModalText,
|
||||
}),
|
||||
];
|
||||
|
||||
|
@ -189,7 +191,7 @@ export const LayerActions = (props: LayerActionsProps) => {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={outsideListAction.displayName}>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
size="s"
|
||||
iconType={outsideListAction.icon}
|
||||
color={outsideListAction.color ?? 'text'}
|
||||
data-test-subj={outsideListAction['data-test-subj']}
|
||||
|
|
|
@ -34,13 +34,15 @@ interface RemoveLayerAction {
|
|||
layerType?: LayerType;
|
||||
isOnlyLayer: boolean;
|
||||
core: Pick<CoreStart, 'overlays' | 'theme'>;
|
||||
customModalText?: { title?: string; description?: string };
|
||||
}
|
||||
|
||||
const SKIP_DELETE_MODAL_KEY = 'skipDeleteModal';
|
||||
|
||||
const getCopy = (
|
||||
layerType: LayerType,
|
||||
isOnlyLayer?: boolean
|
||||
isOnlyLayer?: boolean,
|
||||
customModalText: { title?: string; description?: string } | undefined = undefined
|
||||
): { buttonLabel: string; modalTitle: string; modalBody: string } => {
|
||||
if (isOnlyLayer && layerType === 'data') {
|
||||
return {
|
||||
|
@ -64,34 +66,46 @@ const getCopy = (
|
|||
case 'data':
|
||||
return {
|
||||
buttonLabel,
|
||||
modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteVis', {
|
||||
defaultMessage: 'Delete visualization layer?',
|
||||
}),
|
||||
modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteVis', {
|
||||
defaultMessage: `Deleting this layer removes the visualization and its configurations. `,
|
||||
}),
|
||||
modalTitle:
|
||||
customModalText?.title ??
|
||||
i18n.translate('xpack.lens.modalTitle.title.deleteVis', {
|
||||
defaultMessage: 'Delete visualization layer?',
|
||||
}),
|
||||
modalBody:
|
||||
customModalText?.description ??
|
||||
i18n.translate('xpack.lens.layer.confirmModal.deleteVis', {
|
||||
defaultMessage: `Deleting this layer removes the visualization and its configurations. `,
|
||||
}),
|
||||
};
|
||||
|
||||
case 'annotations':
|
||||
return {
|
||||
buttonLabel,
|
||||
modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteAnnotations', {
|
||||
defaultMessage: 'Delete annotations layer?',
|
||||
}),
|
||||
modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteAnnotation', {
|
||||
defaultMessage: `Deleting this layer removes the annotations and their configurations. `,
|
||||
}),
|
||||
modalTitle:
|
||||
customModalText?.title ??
|
||||
i18n.translate('xpack.lens.modalTitle.title.deleteAnnotations', {
|
||||
defaultMessage: 'Delete annotation group?',
|
||||
}),
|
||||
modalBody:
|
||||
customModalText?.description ??
|
||||
i18n.translate('xpack.lens.layer.confirmModal.deleteAnnotation', {
|
||||
defaultMessage: `Deleting this layer removes the annotations and their configurations. `,
|
||||
}),
|
||||
};
|
||||
|
||||
case 'referenceLine':
|
||||
return {
|
||||
buttonLabel,
|
||||
modalTitle: i18n.translate('xpack.lens.modalTitle.title.deleteReferenceLines', {
|
||||
defaultMessage: 'Delete reference lines layer?',
|
||||
}),
|
||||
modalBody: i18n.translate('xpack.lens.layer.confirmModal.deleteRefLine', {
|
||||
defaultMessage: `Deleting this layer removes the reference lines and their configurations. `,
|
||||
}),
|
||||
modalTitle:
|
||||
customModalText?.title ??
|
||||
i18n.translate('xpack.lens.modalTitle.title.deleteReferenceLines', {
|
||||
defaultMessage: 'Delete reference lines layer?',
|
||||
}),
|
||||
modalBody:
|
||||
customModalText?.description ??
|
||||
i18n.translate('xpack.lens.layer.confirmModal.deleteRefLine', {
|
||||
defaultMessage: `Deleting this layer removes the reference lines and their configurations. `,
|
||||
}),
|
||||
};
|
||||
|
||||
default:
|
||||
|
@ -193,7 +207,8 @@ const RemoveConfirmModal = ({
|
|||
export const getRemoveLayerAction = (props: RemoveLayerAction): LayerAction => {
|
||||
const { buttonLabel, modalTitle, modalBody } = getCopy(
|
||||
props.layerType || LayerTypes.DATA,
|
||||
props.isOnlyLayer
|
||||
props.isOnlyLayer,
|
||||
props.customModalText
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -364,7 +364,6 @@ export function LayerPanel(
|
|||
...getSharedActions({
|
||||
layerId,
|
||||
activeVisualization,
|
||||
visualizationState,
|
||||
core,
|
||||
layerIndex,
|
||||
layerType: activeVisualization.getLayerType(layerId, visualizationState),
|
||||
|
@ -378,6 +377,10 @@ export function LayerPanel(
|
|||
openLayerSettings: () => setPanelSettingsOpen(true),
|
||||
onCloneLayer,
|
||||
onRemoveLayer: () => onRemoveLayer(layerId),
|
||||
customRemoveModalText: activeVisualization.getCustomRemoveLayerText?.(
|
||||
layerId,
|
||||
visualizationState
|
||||
),
|
||||
}),
|
||||
].filter((i) => i.isCompatible),
|
||||
[
|
||||
|
|
|
@ -40,15 +40,7 @@ export const StaticHeader = ({
|
|||
<EuiTitle size="xxs">
|
||||
<h5>{label}</h5>
|
||||
</EuiTitle>
|
||||
{indicator && (
|
||||
<div
|
||||
css={css`
|
||||
padding-bottom: 3px;
|
||||
`}
|
||||
>
|
||||
{indicator}
|
||||
</div>
|
||||
)}
|
||||
{indicator}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -1118,6 +1118,15 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
|
|||
isSaveable?: boolean
|
||||
) => LayerAction[];
|
||||
|
||||
/**
|
||||
* This method is a clunky solution to the problem, but I'm banking on the confirm modal being removed
|
||||
* with undo/redo anyways
|
||||
*/
|
||||
getCustomRemoveLayerText?: (
|
||||
layerId: string,
|
||||
state: T
|
||||
) => { title?: string; description?: string } | undefined;
|
||||
|
||||
/** returns the type string of the given layer */
|
||||
getLayerType: (layerId: string, state?: T) => LayerType | undefined;
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ describe('annotation group save action', () => {
|
|||
describe('save routine', () => {
|
||||
const layerId = 'mylayerid';
|
||||
|
||||
const layer: XYByValueAnnotationLayerConfig = {
|
||||
const byValueLayer: XYByValueAnnotationLayerConfig = {
|
||||
layerId,
|
||||
layerType: 'annotations',
|
||||
indexPatternId: 'some-index-pattern',
|
||||
|
@ -144,10 +144,11 @@ describe('annotation group save action', () => {
|
|||
legend: { isVisible: true, position: 'bottom' },
|
||||
layers: [{ layerId } as XYAnnotationLayerConfig],
|
||||
} as XYState,
|
||||
layer,
|
||||
layer: byValueLayer,
|
||||
setState: jest.fn(),
|
||||
eventAnnotationService: {
|
||||
createAnnotationGroup: jest.fn(() => Promise.resolve({ id: savedId })),
|
||||
groupExistsWithTitle: jest.fn(() => Promise.resolve(false)),
|
||||
updateAnnotationGroup: jest.fn(),
|
||||
loadAnnotationGroup: jest.fn(),
|
||||
toExpression: jest.fn(),
|
||||
|
@ -162,7 +163,7 @@ describe('annotation group save action', () => {
|
|||
newTags: ['my-tag'],
|
||||
newCopyOnSave: false,
|
||||
isTitleDuplicateConfirmed: false,
|
||||
onTitleDuplicate: () => {},
|
||||
onTitleDuplicate: jest.fn(),
|
||||
},
|
||||
dataViews,
|
||||
goToAnnotationLibrary: () => Promise.resolve(),
|
||||
|
@ -321,5 +322,78 @@ describe('annotation group save action', () => {
|
|||
|
||||
expect(props.toasts.addSuccess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each`
|
||||
existingGroup | newCopyOnSave | titleChanged | isTitleDuplicateConfirmed | expectPreventSave
|
||||
${false} | ${false} | ${false} | ${false} | ${true}
|
||||
${false} | ${false} | ${false} | ${true} | ${false}
|
||||
${true} | ${false} | ${false} | ${false} | ${false}
|
||||
${true} | ${true} | ${false} | ${false} | ${true}
|
||||
${true} | ${true} | ${false} | ${true} | ${false}
|
||||
`(
|
||||
'checks duplicate title when saving group',
|
||||
async ({
|
||||
existingGroup,
|
||||
newCopyOnSave,
|
||||
titleChanged,
|
||||
isTitleDuplicateConfirmed,
|
||||
expectPreventSave,
|
||||
}) => {
|
||||
(props.eventAnnotationService.groupExistsWithTitle as jest.Mock).mockResolvedValueOnce(
|
||||
true
|
||||
);
|
||||
|
||||
const oldTitle = 'old title';
|
||||
let layer: XYAnnotationLayerConfig = byValueLayer;
|
||||
if (existingGroup) {
|
||||
const byReferenceLayer: XYByReferenceAnnotationLayerConfig = {
|
||||
...props.layer,
|
||||
annotationGroupId: 'my-group-id',
|
||||
__lastSaved: {
|
||||
...props.layer,
|
||||
title: oldTitle,
|
||||
description: 'description',
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
layer = byReferenceLayer;
|
||||
}
|
||||
|
||||
const newTitle = titleChanged ? 'my changed title' : oldTitle;
|
||||
|
||||
await onSave({
|
||||
...props,
|
||||
layer,
|
||||
modalOnSaveProps: {
|
||||
...props.modalOnSaveProps,
|
||||
newTitle,
|
||||
isTitleDuplicateConfirmed,
|
||||
newCopyOnSave,
|
||||
},
|
||||
});
|
||||
|
||||
if (expectPreventSave) {
|
||||
expect(props.eventAnnotationService.updateAnnotationGroup).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.eventAnnotationService.createAnnotationGroup).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.modalOnSaveProps.closeModal).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.setState).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.toasts.addSuccess).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.modalOnSaveProps.onTitleDuplicate).toHaveBeenCalled();
|
||||
} else {
|
||||
expect(props.modalOnSaveProps.onTitleDuplicate).not.toHaveBeenCalled();
|
||||
|
||||
expect(props.modalOnSaveProps.closeModal).toHaveBeenCalled();
|
||||
|
||||
expect(props.setState).toHaveBeenCalled();
|
||||
|
||||
expect(props.toasts.addSuccess).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -133,6 +133,28 @@ const saveAnnotationGroupToLibrary = async (
|
|||
return { id: savedId, config: groupConfig };
|
||||
};
|
||||
|
||||
const shouldStopBecauseDuplicateTitle = async (
|
||||
newTitle: string,
|
||||
existingTitle: string,
|
||||
newCopyOnSave: ModalOnSaveProps['newCopyOnSave'],
|
||||
onTitleDuplicate: ModalOnSaveProps['onTitleDuplicate'],
|
||||
isTitleDuplicateConfirmed: ModalOnSaveProps['isTitleDuplicateConfirmed'],
|
||||
eventAnnotationService: EventAnnotationServiceType
|
||||
) => {
|
||||
if (isTitleDuplicateConfirmed || (newTitle === existingTitle && !newCopyOnSave)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const duplicateExists = await eventAnnotationService.groupExistsWithTitle(newTitle);
|
||||
|
||||
if (duplicateExists) {
|
||||
onTitleDuplicate();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** @internal exported for testing only */
|
||||
export const onSave = async ({
|
||||
state,
|
||||
|
@ -140,7 +162,15 @@ export const onSave = async ({
|
|||
setState,
|
||||
eventAnnotationService,
|
||||
toasts,
|
||||
modalOnSaveProps: { newTitle, newDescription, newTags, closeModal, newCopyOnSave },
|
||||
modalOnSaveProps: {
|
||||
newTitle,
|
||||
newDescription,
|
||||
newTags,
|
||||
closeModal,
|
||||
newCopyOnSave,
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
},
|
||||
dataViews,
|
||||
goToAnnotationLibrary,
|
||||
}: {
|
||||
|
@ -153,6 +183,17 @@ export const onSave = async ({
|
|||
dataViews: DataViewsContract;
|
||||
goToAnnotationLibrary: () => Promise<void>;
|
||||
}) => {
|
||||
const shouldStop = await shouldStopBecauseDuplicateTitle(
|
||||
newTitle,
|
||||
isByReferenceAnnotationsLayer(layer) ? layer.__lastSaved.title : '',
|
||||
newCopyOnSave,
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed,
|
||||
eventAnnotationService
|
||||
);
|
||||
|
||||
if (shouldStop) return;
|
||||
|
||||
let savedInfo: Awaited<ReturnType<typeof saveAnnotationGroupToLibrary>>;
|
||||
try {
|
||||
savedInfo = await saveAnnotationGroupToLibrary(
|
||||
|
@ -205,27 +246,25 @@ export const onSave = async ({
|
|||
text: ((element) =>
|
||||
render(
|
||||
<I18nProvider>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.successToastBody"
|
||||
defaultMessage="View or manage in the {link}."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
data-test-subj="lnsAnnotationLibraryLink"
|
||||
onClick={() => goToAnnotationLibrary()}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.annotationLibrary',
|
||||
{
|
||||
defaultMessage: 'annotation library',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.successToastBody"
|
||||
defaultMessage="View or manage in the {link}."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
data-test-subj="lnsAnnotationLibraryLink"
|
||||
onClick={() => goToAnnotationLibrary()}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary.annotationLibrary',
|
||||
{
|
||||
defaultMessage: 'annotation library',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
element
|
||||
)) as MountPoint,
|
||||
|
@ -258,7 +297,7 @@ export const getSaveLayerAction = ({
|
|||
const displayName = i18n.translate(
|
||||
'xpack.lens.xyChart.annotations.saveAnnotationGroupToLibrary',
|
||||
{
|
||||
defaultMessage: 'Save annotation group',
|
||||
defaultMessage: 'Save to library',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -416,16 +416,6 @@ export const getAnnotationsConfiguration = ({
|
|||
}) => {
|
||||
const groupLabel = getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) });
|
||||
|
||||
const emptyButtonLabels = {
|
||||
buttonAriaLabel: i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', {
|
||||
defaultMessage: 'Add an annotation to {groupLabel}',
|
||||
values: { groupLabel },
|
||||
}),
|
||||
buttonLabel: i18n.translate('xpack.lens.configure.emptyConfigClick', {
|
||||
defaultMessage: 'Add an annotation',
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
groups: [
|
||||
{
|
||||
|
@ -445,7 +435,6 @@ export const getAnnotationsConfiguration = ({
|
|||
supportFieldFormat: false,
|
||||
enableDimensionEditor: true,
|
||||
filterOperations: () => false,
|
||||
labels: emptyButtonLabels,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -70,6 +70,7 @@ export function LoadAnnotationLibraryFlyout({
|
|||
<div
|
||||
css={css`
|
||||
padding: ${euiThemeVars.euiSize};
|
||||
height: 100%;
|
||||
`}
|
||||
>
|
||||
<EventAnnotationGroupSavedObjectFinder
|
||||
|
|
|
@ -3638,7 +3638,7 @@ describe('xy_visualization', () => {
|
|||
Object {
|
||||
"data-test-subj": "lnsXY_annotationLayer_saveToLibrary",
|
||||
"description": "Saves annotation group as separate saved object",
|
||||
"displayName": "Save annotation group",
|
||||
"displayName": "Save to library",
|
||||
"execute": [Function],
|
||||
"icon": "save",
|
||||
"isCompatible": true,
|
||||
|
@ -3899,4 +3899,52 @@ describe('xy_visualization', () => {
|
|||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getCustomRemoveLayerText', () => {
|
||||
it('should NOT return custom text for the remove layer button if not by-reference', () => {
|
||||
expect(xyVisualization.getCustomRemoveLayerText!('first', exampleState())).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return custom text for the remove layer button if by-reference', () => {
|
||||
const layerId = 'layer-id';
|
||||
|
||||
const commonProps = {
|
||||
layerId,
|
||||
layerType: 'annotations' as const,
|
||||
indexPatternId: 'some-index-pattern',
|
||||
ignoreGlobalFilters: false,
|
||||
annotations: [
|
||||
{
|
||||
id: 'some-annotation-id',
|
||||
type: 'manual',
|
||||
key: {
|
||||
type: 'point_in_time',
|
||||
timestamp: 'timestamp',
|
||||
},
|
||||
} as PointInTimeEventAnnotationConfig,
|
||||
],
|
||||
};
|
||||
|
||||
const layer: XYByReferenceAnnotationLayerConfig = {
|
||||
...commonProps,
|
||||
annotationGroupId: 'some-group-id',
|
||||
__lastSaved: {
|
||||
...commonProps,
|
||||
title: 'My saved object title',
|
||||
description: 'some description',
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
expect(
|
||||
xyVisualization.getCustomRemoveLayerText!(layerId, {
|
||||
...exampleState(),
|
||||
layers: [layer],
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"title": "Delete \\"My saved object title\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -97,6 +97,7 @@ import {
|
|||
getVisualizationType,
|
||||
isAnnotationsLayer,
|
||||
isBucketed,
|
||||
isByReferenceAnnotationsLayer,
|
||||
isDataLayer,
|
||||
isNumericDynamicMetric,
|
||||
isReferenceLayer,
|
||||
|
@ -304,6 +305,14 @@ export const getXyVisualization = ({
|
|||
return actions;
|
||||
},
|
||||
|
||||
getCustomRemoveLayerText(layerId, state) {
|
||||
const layerIndex = state.layers.findIndex((l) => l.layerId === layerId);
|
||||
const layer = state.layers[layerIndex];
|
||||
if (layer && isByReferenceAnnotationsLayer(layer)) {
|
||||
return { title: `Delete "${layer.__lastSaved.title}"` };
|
||||
}
|
||||
},
|
||||
|
||||
hasLayerSettings({ state, layerId: currentLayerId }) {
|
||||
const layer = state.layers?.find(({ layerId }) => layerId === currentLayerId);
|
||||
return { data: Boolean(layer && isAnnotationsLayer(layer)), appearance: false };
|
||||
|
|
|
@ -161,7 +161,7 @@ export const isPersistedByValueAnnotationsLayer = (
|
|||
(layer.persistanceType === 'byValue' || !layer.persistanceType);
|
||||
|
||||
export const isByReferenceAnnotationsLayer = (
|
||||
layer: XYAnnotationLayerConfig
|
||||
layer: XYLayerConfig
|
||||
): layer is XYByReferenceAnnotationLayerConfig =>
|
||||
'annotationGroupId' in layer && '__lastSaved' in layer;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { ToolbarButton } from '@kbn/kibana-react-plugin/public';
|
||||
import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { css } from '@emotion/react';
|
||||
import { getIgnoreGlobalFilterIcon } from '../../../shared_components/ignore_global_filter/data_view_picker_icon';
|
||||
import type {
|
||||
VisualizationLayerHeaderContentProps,
|
||||
|
@ -96,13 +97,20 @@ function AnnotationsLayerHeader({
|
|||
}
|
||||
indicator={
|
||||
hasUnsavedChanges && (
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.lens.xyChart.unsavedChanges', {
|
||||
defaultMessage: 'Unsaved changes',
|
||||
})}
|
||||
type="dot"
|
||||
color={euiThemeVars.euiColorSuccess}
|
||||
/>
|
||||
<div
|
||||
css={css`
|
||||
padding-bottom: 3px;
|
||||
padding-left: 4px;
|
||||
`}
|
||||
>
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.lens.xyChart.unsavedChanges', {
|
||||
defaultMessage: 'Unsaved changes',
|
||||
})}
|
||||
type="dot"
|
||||
color={euiThemeVars.euiColorSuccess}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -20070,7 +20070,6 @@
|
|||
"xpack.lens.functions.timeScale.dateColumnMissingMessage": "L'ID de colonne de date spécifié {columnId} n'existe pas.",
|
||||
"xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label} contient des valeurs de tableau. Le rendu de votre visualisation peut ne pas se présenter comme attendu.",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabel": "Ajouter ou faire glisser un champ vers {groupLabel}",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabelClick": "Ajouter une annotation à {groupLabel}",
|
||||
"xpack.lens.indexPattern.annotationsDimensionEditorLabel": "Annotation {groupLabel}",
|
||||
"xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "{name} pour cette visualisation peut être approximatif en raison de la manière dont les données sont indexées. Essayez de trier par rareté plutôt que par nombre ascendant d’enregistrements. Pour en savoir plus sur cette limitation, {link}.",
|
||||
"xpack.lens.indexPattern.autoIntervalLabel": "Auto ({interval})",
|
||||
|
@ -20305,7 +20304,6 @@
|
|||
"xpack.lens.configPanel.selectVisualization": "Sélectionner une visualisation",
|
||||
"xpack.lens.configPanel.visualizationType": "Type de visualisation",
|
||||
"xpack.lens.configure.emptyConfig": "Ajouter ou glisser-déposer un champ",
|
||||
"xpack.lens.configure.emptyConfigClick": "Ajouter une annotation",
|
||||
"xpack.lens.configure.invalidBottomReferenceLineDimension": "La ligne de référence est affectée à un axe qui n’existe plus ou qui n’est plus valide. Vous pouvez déplacer cette ligne de référence vers un autre axe disponible ou la supprimer.",
|
||||
"xpack.lens.configure.invalidConfigTooltip": "Configuration non valide.",
|
||||
"xpack.lens.configure.invalidConfigTooltipClick": "Cliquez pour en savoir plus.",
|
||||
|
|
|
@ -20069,7 +20069,6 @@
|
|||
"xpack.lens.functions.timeScale.dateColumnMissingMessage": "指定したdateColumnId {columnId}は存在しません。",
|
||||
"xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label}には配列値が含まれます。可視化が想定通りに表示されない場合があります。",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabel": "フィールドを追加するか、{groupLabel}にドラッグアンドドロップします",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabelClick": "注釈を{groupLabel}に追加",
|
||||
"xpack.lens.indexPattern.annotationsDimensionEditorLabel": "{groupLabel}注釈",
|
||||
"xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "データのインデックス方法のため、このビジュアライゼーションの{name}は近似される場合があります。レコード数の昇順ではなく希少性で並べ替えてください。この制限の詳細については、{link}。",
|
||||
"xpack.lens.indexPattern.autoIntervalLabel": "自動({interval})",
|
||||
|
@ -20305,7 +20304,6 @@
|
|||
"xpack.lens.configPanel.selectVisualization": "ビジュアライゼーションを選択してください",
|
||||
"xpack.lens.configPanel.visualizationType": "ビジュアライゼーションタイプ",
|
||||
"xpack.lens.configure.emptyConfig": "フィールドを追加するか、ドラッグアンドドロップします",
|
||||
"xpack.lens.configure.emptyConfigClick": "注釈の追加",
|
||||
"xpack.lens.configure.invalidBottomReferenceLineDimension": "この基準線は存在しないか有効ではない軸に割り当てられています。この基準線を別の使用可能な軸に移動するか、削除することができます。",
|
||||
"xpack.lens.configure.invalidConfigTooltip": "無効な構成です。",
|
||||
"xpack.lens.configure.invalidConfigTooltipClick": "詳細はクリックしてください。",
|
||||
|
|
|
@ -20069,7 +20069,6 @@
|
|||
"xpack.lens.functions.timeScale.dateColumnMissingMessage": "指定的 dateColumnId {columnId} 不存在。",
|
||||
"xpack.lens.heatmapVisualization.arrayValuesWarningMessage": "{label} 包含数组值。您的可视化可能无法正常渲染。",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabel": "将字段添加或拖放到 {groupLabel}",
|
||||
"xpack.lens.indexPattern.addColumnAriaLabelClick": "添加标注到 {groupLabel}",
|
||||
"xpack.lens.indexPattern.annotationsDimensionEditorLabel": "{groupLabel} 标注",
|
||||
"xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning": "由于数据的索引方式,此可视化的 {name} 可能为近似值。尝试按稀有度排序,而不是采用升序记录计数。有关此限制的详情,{link}。",
|
||||
"xpack.lens.indexPattern.autoIntervalLabel": "自动 ({interval})",
|
||||
|
@ -20305,7 +20304,6 @@
|
|||
"xpack.lens.configPanel.selectVisualization": "选择可视化",
|
||||
"xpack.lens.configPanel.visualizationType": "可视化类型",
|
||||
"xpack.lens.configure.emptyConfig": "添加或拖放字段",
|
||||
"xpack.lens.configure.emptyConfigClick": "添加标注",
|
||||
"xpack.lens.configure.invalidBottomReferenceLineDimension": "此参考线分配给了不再存在或不再有效的轴。您可以将此参考线移到其他可用的轴,或将其移除。",
|
||||
"xpack.lens.configure.invalidConfigTooltip": "配置无效。",
|
||||
"xpack.lens.configure.invalidConfigTooltipClick": "单击了解更多详情。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue