Changes edit view to json read-only view (#112034)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christiane (Tina) Heiligers 2021-09-21 13:10:53 -07:00 committed by GitHub
parent 25bf795099
commit 8cf0efe2f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1365 additions and 1335 deletions

View file

@ -241,6 +241,7 @@ export class DocLinksService {
kibanaSearchSettings: `${KIBANA_DOCS}advanced-options.html#kibana-search-settings`,
visualizationSettings: `${KIBANA_DOCS}advanced-options.html#kibana-visualization-settings`,
timelionSettings: `${KIBANA_DOCS}advanced-options.html#kibana-timelion-settings`,
savedObjectsApiList: `${KIBANA_DOCS}saved-objects-api.html#saved-objects-api`,
},
ml: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/index.html`,

View file

@ -27,9 +27,6 @@ export const createDashboardSavedObjectType = ({
getTitle(obj) {
return obj.attributes.title;
},
getEditUrl(obj) {
return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`;
},
getInAppUrl(obj) {
return {
path: `/app/dashboards#/view/${encodeURIComponent(obj.id)}`,

View file

@ -20,9 +20,6 @@ export const searchSavedObjectType: SavedObjectsType = {
getTitle(obj) {
return obj.attributes.title;
},
getEditUrl(obj) {
return `/management/kibana/objects/savedSearches/${encodeURIComponent(obj.id)}`;
},
getInAppUrl(obj) {
return {
path: `/app/discover#/view/${encodeURIComponent(obj.id)}`,

View file

@ -62,12 +62,11 @@ export const mountManagementSection = async ({
<I18nProvider>
<Router history={history}>
<Switch>
<Route path={'/:service/:id'} exact={true}>
<Route path={'/:type/:id'} exact={true}>
<RedirectToHomeIfUnauthorized>
<Suspense fallback={<EuiLoadingSpinner />}>
<SavedObjectsEditionPage
coreStart={coreStart}
serviceRegistry={serviceRegistry}
setBreadcrumbs={setBreadcrumbs}
history={history}
/>

View file

@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SavedObjectEdition should render normally 1`] = `
<Provider
services={
Object {
"uiSettings": Object {
"get": [MockFunction],
"get$": [MockFunction],
"getAll": [MockFunction],
"getUpdate$": [MockFunction],
"getUpdateErrors$": [MockFunction],
"isCustom": [MockFunction],
"isDeclared": [MockFunction],
"isDefault": [MockFunction],
"isOverridden": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
},
}
}
>
<EuiFlexGroup
className="savedObjectsManagementObjectView"
data-test-subject="savedObjectsEdit"
direction="column"
>
<EuiFlexItem
grow={false}
>
<Header
canDelete={false}
canViewInApp={true}
onDeleteClick={[Function]}
title="MyDashboard*"
viewUrl="/app/dashboards#/view/1"
/>
</EuiFlexItem>
<EuiFlexItem
grow={true}
>
<Inspect
object={
Object {
"attributes": Object {
"title": "MyDashboard*",
},
"id": "1",
"meta": Object {
"icon": "dashboardApp",
"inAppUrl": Object {
"path": "/app/dashboards#/view/1",
"uiCapabilitiesPath": "management.kibana.dashboard",
},
"title": "MyDashboard*",
},
"type": "dashboard",
}
}
/>
</EuiFlexItem>
</EuiFlexGroup>
</Provider>
`;

View file

@ -11,17 +11,7 @@ exports[`Intro component renders correctly 1`] = `
>
<EuiPageHeader
bottomBorder={true}
pageTitle={
<FormattedMessage
defaultMessage="Edit {title}"
id="savedObjectsManagement.view.editItemTitle"
values={
Object {
"title": "search",
}
}
/>
}
pageTitle="Inspect saved object"
rightSideItems={
Array [
<EuiButton
@ -35,7 +25,7 @@ exports[`Intro component renders correctly 1`] = `
id="savedObjectsManagement.view.viewItemButtonLabel"
values={
Object {
"title": "search",
"title": "saved object",
}
}
/>
@ -48,13 +38,9 @@ exports[`Intro component renders correctly 1`] = `
size="s"
>
<FormattedMessage
defaultMessage="Delete {title}"
defaultMessage="Delete"
id="savedObjectsManagement.view.deleteItemButtonLabel"
values={
Object {
"title": "search",
}
}
values={Object {}}
/>
</EuiButton>,
]
@ -64,17 +50,7 @@ exports[`Intro component renders correctly 1`] = `
className="euiPageHeader euiPageHeader--bottomBorder euiPageHeader--responsive euiPageHeader--center"
>
<EuiPageHeaderContent
pageTitle={
<FormattedMessage
defaultMessage="Edit {title}"
id="savedObjectsManagement.view.editItemTitle"
values={
Object {
"title": "search",
}
}
/>
}
pageTitle="Inspect saved object"
responsive={true}
rightSideItems={
Array [
@ -89,7 +65,7 @@ exports[`Intro component renders correctly 1`] = `
id="savedObjectsManagement.view.viewItemButtonLabel"
values={
Object {
"title": "search",
"title": "saved object",
}
}
/>
@ -102,13 +78,9 @@ exports[`Intro component renders correctly 1`] = `
size="s"
>
<FormattedMessage
defaultMessage="Delete {title}"
defaultMessage="Delete"
id="savedObjectsManagement.view.deleteItemButtonLabel"
values={
Object {
"title": "search",
}
}
values={Object {}}
/>
</EuiButton>,
]
@ -136,17 +108,7 @@ exports[`Intro component renders correctly 1`] = `
<h1
className="euiTitle euiTitle--large"
>
<FormattedMessage
defaultMessage="Edit {title}"
id="savedObjectsManagement.view.editItemTitle"
values={
Object {
"title": "search",
}
}
>
Edit search
</FormattedMessage>
Inspect saved object
</h1>
</EuiTitle>
</div>
@ -234,11 +196,11 @@ exports[`Intro component renders correctly 1`] = `
id="savedObjectsManagement.view.viewItemButtonLabel"
values={
Object {
"title": "search",
"title": "saved object",
}
}
>
View search
View saved object
</FormattedMessage>
</span>
</span>
@ -316,15 +278,11 @@ exports[`Intro component renders correctly 1`] = `
className="euiButton__text"
>
<FormattedMessage
defaultMessage="Delete {title}"
defaultMessage="Delete"
id="savedObjectsManagement.view.deleteItemButtonLabel"
values={
Object {
"title": "search",
}
}
values={Object {}}
>
Delete search
Delete
</FormattedMessage>
</span>
</span>

View file

@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Inspect component renders correctly 1`] = `
<CodeEditor
aria-label="inspect MyIndexPattern*"
languageId="xjson"
options={
Object {
"automaticLayout": false,
"fontSize": 12,
"lineNumbers": "on",
"minimap": Object {
"enabled": false,
},
"overviewRulerBorder": false,
"readOnly": true,
"renderIndentGuides": false,
"scrollBeyondLastLine": false,
"scrollbar": Object {
"alwaysConsumeMouseWheel": false,
},
"wordWrap": "on",
"wrappingIndent": "indent",
}
}
value="{
\\"id\\": \\"1\\",
\\"type\\": \\"index-pattern\\",
\\"attributes\\": {
\\"title\\": \\"MyIndexPattern*\\"
},
\\"references\\": []
}"
/>
`;

View file

@ -1,80 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Intro component renders correctly 1`] = `
<Intro>
<EuiCallOut
color="warning"
iconType="alert"
title={
<FormattedMessage
defaultMessage="Proceed with caution!"
id="savedObjectsManagement.view.howToModifyObjectTitle"
values={Object {}}
/>
}
>
<div
className="euiCallOut euiCallOut--warning"
>
<div
className="euiCallOutHeader"
>
<EuiIcon
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
size="m"
type="alert"
>
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<FormattedMessage
defaultMessage="Proceed with caution!"
id="savedObjectsManagement.view.howToModifyObjectTitle"
values={Object {}}
>
Proceed with caution!
</FormattedMessage>
</span>
</div>
<EuiText
color="default"
size="s"
>
<div
className="euiText euiText--small"
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldnt be."
id="savedObjectsManagement.view.howToModifyObjectDescription"
values={Object {}}
>
Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldnt be.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiCallOut>
</Intro>
`;

View file

@ -1,353 +1,541 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NotFoundErrors component renders correctly for index-pattern type 1`] = `
<NotFoundErrors
type="index-pattern"
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
>
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
<div
className="euiCallOut euiCallOut--danger"
>
<div
className="euiCallOut euiCallOut--danger"
className="euiCallOutHeader"
>
<div
className="euiCallOutHeader"
<EuiIcon
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
size="m"
type="alert"
>
<EuiIcon
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
type="alert"
>
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
<EuiText
color="default"
size="s"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<div
className="euiText euiText--small"
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="The index pattern associated with this object no longer exists."
id="savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage"
values={Object {}}
>
The index pattern associated with this object no longer exists.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={Object {}}
>
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
</EuiCallOut>
</NotFoundErrors>
<EuiText
color="default"
size="s"
>
<div
className="euiText euiText--small"
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="The index pattern associated with this object no longer exists."
id="savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage"
values={Object {}}
>
The index pattern associated with this object no longer exists.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, you can use the {savedObjectsApis} to fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={
Object {
"savedObjectsApis": <EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
Saved objects APIs
</EuiLink>,
}
}
>
If you know what this error means, you can use the
<EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
<a
aria-label="Saved objects APIs"
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
rel="noopener"
target="_blank"
>
Saved objects APIs
<EuiIcon
aria-label="External link"
className="euiLink__externalIcon"
size="s"
type="popout"
>
<span
aria-label="External link"
className="euiLink__externalIcon"
data-euiicon-type="popout"
size="s"
/>
</EuiIcon>
<EuiScreenReaderOnly>
<span
className="euiScreenReaderOnly"
>
<EuiI18n
default="(opens in a new tab or window)"
token="euiLink.newTarget.screenReaderOnlyText"
>
(opens in a new tab or window)
</EuiI18n>
</span>
</EuiScreenReaderOnly>
</a>
</EuiLink>
to fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiCallOut>
`;
exports[`NotFoundErrors component renders correctly for index-pattern-field type 1`] = `
<NotFoundErrors
type="index-pattern-field"
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
>
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
<div
className="euiCallOut euiCallOut--danger"
>
<div
className="euiCallOut euiCallOut--danger"
className="euiCallOutHeader"
>
<div
className="euiCallOutHeader"
<EuiIcon
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
size="m"
type="alert"
>
<EuiIcon
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
type="alert"
>
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
<EuiText
color="default"
size="s"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<div
className="euiText euiText--small"
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="A field associated with this object no longer exists in the index pattern."
id="savedObjectsManagement.view.fieldDoesNotExistErrorMessage"
values={Object {}}
>
A field associated with this object no longer exists in the index pattern.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={Object {}}
>
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
</EuiCallOut>
</NotFoundErrors>
<EuiText
color="default"
size="s"
>
<div
className="euiText euiText--small"
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="A field associated with this object no longer exists in the index pattern."
id="savedObjectsManagement.view.fieldDoesNotExistErrorMessage"
values={Object {}}
>
A field associated with this object no longer exists in the index pattern.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, you can use the {savedObjectsApis} to fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={
Object {
"savedObjectsApis": <EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
Saved objects APIs
</EuiLink>,
}
}
>
If you know what this error means, you can use the
<EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
<a
aria-label="Saved objects APIs"
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
rel="noopener"
target="_blank"
>
Saved objects APIs
<EuiIcon
aria-label="External link"
className="euiLink__externalIcon"
size="s"
type="popout"
>
<span
aria-label="External link"
className="euiLink__externalIcon"
data-euiicon-type="popout"
size="s"
/>
</EuiIcon>
<EuiScreenReaderOnly>
<span
className="euiScreenReaderOnly"
>
<EuiI18n
default="(opens in a new tab or window)"
token="euiLink.newTarget.screenReaderOnlyText"
>
(opens in a new tab or window)
</EuiI18n>
</span>
</EuiScreenReaderOnly>
</a>
</EuiLink>
to fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiCallOut>
`;
exports[`NotFoundErrors component renders correctly for search type 1`] = `
<NotFoundErrors
type="search"
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
>
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
<div
className="euiCallOut euiCallOut--danger"
>
<div
className="euiCallOut euiCallOut--danger"
className="euiCallOutHeader"
>
<div
className="euiCallOutHeader"
<EuiIcon
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
size="m"
type="alert"
>
<EuiIcon
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
type="alert"
>
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
<EuiText
color="default"
size="s"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<div
className="euiText euiText--small"
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="The saved search associated with this object no longer exists."
id="savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage"
values={Object {}}
>
The saved search associated with this object no longer exists.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={Object {}}
>
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
</EuiCallOut>
</NotFoundErrors>
<EuiText
color="default"
size="s"
>
<div
className="euiText euiText--small"
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div>
<FormattedMessage
defaultMessage="The saved search associated with this object no longer exists."
id="savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage"
values={Object {}}
>
The saved search associated with this object no longer exists.
</FormattedMessage>
</div>
<div>
<FormattedMessage
defaultMessage="If you know what this error means, you can use the {savedObjectsApis} to fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={
Object {
"savedObjectsApis": <EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
Saved objects APIs
</EuiLink>,
}
}
>
If you know what this error means, you can use the
<EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
<a
aria-label="Saved objects APIs"
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
rel="noopener"
target="_blank"
>
Saved objects APIs
<EuiIcon
aria-label="External link"
className="euiLink__externalIcon"
size="s"
type="popout"
>
<span
aria-label="External link"
className="euiLink__externalIcon"
data-euiicon-type="popout"
size="s"
/>
</EuiIcon>
<EuiScreenReaderOnly>
<span
className="euiScreenReaderOnly"
>
<EuiI18n
default="(opens in a new tab or window)"
token="euiLink.newTarget.screenReaderOnlyText"
>
(opens in a new tab or window)
</EuiI18n>
</span>
</EuiScreenReaderOnly>
</a>
</EuiLink>
to fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiCallOut>
`;
exports[`NotFoundErrors component renders correctly for unknown type 1`] = `
<NotFoundErrors
type="unknown"
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
>
<EuiCallOut
color="danger"
iconType="alert"
title={
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
/>
}
<div
className="euiCallOut euiCallOut--danger"
>
<div
className="euiCallOut euiCallOut--danger"
className="euiCallOutHeader"
>
<div
className="euiCallOutHeader"
<EuiIcon
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
size="m"
type="alert"
>
<EuiIcon
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
type="alert"
>
<span
aria-hidden="true"
className="euiCallOutHeader__icon"
color="inherit"
data-euiicon-type="alert"
size="m"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
<EuiText
color="default"
size="s"
/>
</EuiIcon>
<span
className="euiCallOutHeader__title"
>
<div
className="euiText euiText--small"
<FormattedMessage
defaultMessage="There is a problem with this saved object"
id="savedObjectsManagement.view.savedObjectProblemErrorMessage"
values={Object {}}
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div />
<div>
<FormattedMessage
defaultMessage="If you know what this error means, go ahead and fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={Object {}}
>
If you know what this error means, go ahead and fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
There is a problem with this saved object
</FormattedMessage>
</span>
</div>
</EuiCallOut>
</NotFoundErrors>
<EuiText
color="default"
size="s"
>
<div
className="euiText euiText--small"
>
<EuiTextColor
color="default"
component="div"
>
<div
className="euiTextColor euiTextColor--default"
style={Object {}}
>
<div />
<div>
<FormattedMessage
defaultMessage="If you know what this error means, you can use the {savedObjectsApis} to fix it — otherwise click the delete button above."
id="savedObjectsManagement.view.howToFixErrorDescription"
values={
Object {
"savedObjectsApis": <EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
Saved objects APIs
</EuiLink>,
}
}
>
If you know what this error means, you can use the
<EuiLink
aria-label="Saved objects APIs"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
target="_blank"
>
<a
aria-label="Saved objects APIs"
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/kibana/mocked-test-branch/saved-objects-api.html#saved-objects-api"
rel="noopener"
target="_blank"
>
Saved objects APIs
<EuiIcon
aria-label="External link"
className="euiLink__externalIcon"
size="s"
type="popout"
>
<span
aria-label="External link"
className="euiLink__externalIcon"
data-euiicon-type="popout"
size="s"
/>
</EuiIcon>
<EuiScreenReaderOnly>
<span
className="euiScreenReaderOnly"
>
<EuiI18n
default="(opens in a new tab or window)"
token="euiLink.newTarget.screenReaderOnlyText"
>
(opens in a new tab or window)
</EuiI18n>
</span>
</EuiScreenReaderOnly>
</a>
</EuiLink>
to fix it — otherwise click the delete button above.
</FormattedMessage>
</div>
</div>
</EuiTextColor>
</div>
</EuiText>
</div>
</EuiCallOut>
`;

View file

@ -1,84 +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 React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { Field } from './field';
import { FieldState, FieldType } from '../../types';
describe('Field component', () => {
const mountField = (props: {
type: FieldType;
name: string;
value: any;
disabled: boolean;
state?: FieldState;
onChange: (name: string, state: FieldState) => void;
}) =>
mount(
<I18nProvider>
<Field {...props} />
</I18nProvider>
).find('Field');
const defaultProps = {
type: 'text' as FieldType,
name: 'field',
value: '',
disabled: false,
state: undefined,
onChange: (name: string, state: FieldState) => undefined,
};
it('uses the field name as the label', () => {
let mounted = mountField({ ...defaultProps, name: 'some.name' });
expect(mounted.find('EuiFormLabel').text()).toMatchInlineSnapshot(`"some.name"`);
mounted = mountField({ ...defaultProps, name: 'someother.name' });
expect(mounted.find('EuiFormLabel').text()).toMatchInlineSnapshot(`"someother.name"`);
});
it('renders a EuiCodeEditor for json type', () => {
const mounted = mountField({ ...defaultProps, type: 'json' });
expect(mounted.exists('EuiCodeEditor')).toEqual(true);
});
it('renders a EuiCodeEditor for array type', () => {
const mounted = mountField({ ...defaultProps, type: 'array' });
expect(mounted.exists('EuiCodeEditor')).toEqual(true);
});
it('renders a EuiSwitch for boolean type', () => {
const mounted = mountField({ ...defaultProps, type: 'boolean' });
expect(mounted.exists('EuiSwitch')).toEqual(true);
});
it('display correct label for boolean type depending on value', () => {
let mounted = mountField({ ...defaultProps, type: 'boolean', value: true });
expect(mounted.find('EuiSwitch').text()).toMatchInlineSnapshot(`"On"`);
mounted = mountField({ ...defaultProps, type: 'boolean', value: false });
expect(mounted.find('EuiSwitch').text()).toMatchInlineSnapshot(`"Off"`);
});
it('renders a EuiFieldNumber for number type', () => {
const mounted = mountField({ ...defaultProps, type: 'number' });
expect(mounted.exists('EuiFieldNumber')).toEqual(true);
});
it('renders a EuiFieldText for text type', () => {
const mounted = mountField({ ...defaultProps, type: 'text' });
expect(mounted.exists('EuiFieldText')).toEqual(true);
});
it('renders a EuiFieldText as fallback', () => {
const mounted = mountField({ ...defaultProps, type: 'unknown-type' as any });
expect(mounted.exists('EuiFieldText')).toEqual(true);
});
});

View file

@ -1,145 +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 React, { PureComponent } from 'react';
import { EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSwitch, EuiCodeEditor } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { FieldState, FieldType } from '../../types';
interface FieldProps {
type: FieldType;
name: string;
value: any;
disabled: boolean;
state?: FieldState;
onChange: (name: string, state: FieldState) => void;
}
export class Field extends PureComponent<FieldProps> {
render() {
const { name } = this.props;
return (
<EuiFormRow fullWidth={true} label={name}>
{this.renderField()}
</EuiFormRow>
);
}
onCodeEditorChange(targetValue: any) {
const { name, onChange } = this.props;
let invalid = false;
try {
JSON.parse(targetValue);
} catch (e) {
invalid = true;
}
onChange(name, {
value: targetValue,
invalid,
});
}
onFieldChange(targetValue: any) {
const { name, type, onChange } = this.props;
let newParsedValue = targetValue;
let invalid = false;
if (type === 'number') {
try {
newParsedValue = Number(newParsedValue);
} catch (e) {
invalid = true;
}
}
onChange(name, {
value: newParsedValue,
invalid,
});
}
renderField() {
const { type, name, state, disabled } = this.props;
const currentValue = state?.value ?? this.props.value;
switch (type) {
case 'number':
return (
<EuiFieldNumber
name={name}
id={this.fieldId}
value={currentValue}
onChange={(e) => this.onFieldChange(e.target.value)}
disabled={disabled}
data-test-subj={`savedObjects-editField-${name}`}
/>
);
case 'boolean':
return (
<EuiSwitch
name={name}
id={this.fieldId}
label={
!!currentValue ? (
<FormattedMessage id="savedObjectsManagement.field.onLabel" defaultMessage="On" />
) : (
<FormattedMessage id="savedObjectsManagement.field.offLabel" defaultMessage="Off" />
)
}
checked={!!currentValue}
onChange={(e) => this.onFieldChange(e.target.checked)}
disabled={disabled}
data-test-subj={`savedObjects-editField-${name}`}
/>
);
case 'json':
case 'array':
return (
<div data-test-subj={`savedObjects-editField-${name}`}>
<EuiCodeEditor
name={`savedObjects-editField-${name}-aceEditor`}
mode="json"
theme="textmate"
value={currentValue}
onChange={(value: any) => this.onCodeEditorChange(value)}
width="100%"
height="auto"
minLines={6}
maxLines={30}
isReadOnly={disabled}
setOptions={{
showLineNumbers: true,
tabSize: 2,
useSoftTabs: true,
}}
editorProps={{
$blockScrolling: Infinity,
}}
showGutter={true}
/>
</div>
);
default:
return (
<EuiFieldText
id={this.fieldId}
name={name}
value={currentValue}
onChange={(e) => this.onFieldChange(e.target.value)}
disabled={disabled}
data-test-subj={`savedObjects-editField-${name}`}
/>
);
}
}
private get fieldId() {
const { name } = this.props;
return `savedObjects-editField-${name}`;
}
}

View file

@ -1,174 +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 React, { Component } from 'react';
import {
EuiForm,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiButtonEmpty,
EuiSpacer,
} from '@elastic/eui';
import { set } from '@elastic/safer-lodash-set';
import { cloneDeep } from 'lodash';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { SavedObjectsClientContract } from '../../../../../../core/public';
import { SavedObjectLoader } from '../../../../../saved_objects/public';
import { Field } from './field';
import { ObjectField, FieldState, SubmittedFormData } from '../../types';
import { createFieldList } from '../../../lib';
import { SavedObjectWithMetadata } from '../../../types';
interface FormProps {
object: SavedObjectWithMetadata;
service: SavedObjectLoader;
savedObjectsClient: SavedObjectsClientContract;
editionEnabled: boolean;
onSave: (form: SubmittedFormData) => Promise<void>;
}
interface FormState {
fields: ObjectField[];
fieldStates: Record<string, FieldState>;
submitting: boolean;
}
export class Form extends Component<FormProps, FormState> {
constructor(props: FormProps) {
super(props);
this.state = {
fields: [],
fieldStates: {},
submitting: false,
};
}
componentDidMount() {
const { object, service } = this.props;
const fields = createFieldList(object, service);
this.setState({
fields,
});
}
render() {
const { editionEnabled, service } = this.props;
const { fields, fieldStates, submitting } = this.state;
const isValid = this.isFormValid();
return (
<EuiForm data-test-subj="savedObjectEditForm" role="form">
{fields.map((field) => (
<Field
key={`${field.type}-${field.name}`}
type={field.type}
name={field.name}
value={field.value}
state={fieldStates[field.name]}
disabled={!editionEnabled}
onChange={this.handleFieldChange}
/>
))}
<EuiSpacer size={'l'} />
<EuiFlexGroup responsive={false} gutterSize={'m'}>
{editionEnabled && (
<EuiFlexItem grow={false}>
<EuiButton
fill={true}
aria-label={i18n.translate('savedObjectsManagement.view.saveButtonAriaLabel', {
defaultMessage: 'Save { title } object',
values: {
title: service.type,
},
})}
onClick={this.onSubmit}
disabled={!isValid || submitting}
data-test-subj="savedObjectEditSave"
>
<FormattedMessage
id="savedObjectsManagement.view.saveButtonLabel"
defaultMessage="Save { title } object"
values={{ title: service.type }}
/>
</EuiButton>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButtonEmpty
aria-label={i18n.translate('savedObjectsManagement.view.cancelButtonAriaLabel', {
defaultMessage: 'Cancel',
})}
onClick={this.onCancel}
data-test-subj="savedObjectEditCancel"
>
<FormattedMessage
id="savedObjectsManagement.view.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
);
}
handleFieldChange = (name: string, newState: FieldState) => {
this.setState({
fieldStates: {
...this.state.fieldStates,
[name]: newState,
},
});
};
isFormValid() {
const { fieldStates } = this.state;
return !Object.values(fieldStates).some((state) => state.invalid === true);
}
onCancel = () => {
window.history.back();
};
onSubmit = async () => {
const { object, onSave } = this.props;
const { fields, fieldStates } = this.state;
if (!this.isFormValid()) {
return;
}
this.setState({
submitting: true,
});
const source = cloneDeep(object.attributes as any);
fields.forEach((field) => {
let value = fieldStates[field.name]?.value ?? field.value;
if (field.type === 'array' && typeof value === 'string') {
value = JSON.parse(value);
}
set(source, field.name, value);
});
// we extract the `references` field that does not belong to attributes
const { references, ...attributes } = source;
await onSave({ attributes, references });
this.setState({
submitting: false,
});
};
}

View file

@ -19,6 +19,7 @@ describe('Intro component', () => {
type: string;
viewUrl: string;
onDeleteClick: () => void;
title?: string;
}) =>
mount(
<I18nProvider>
@ -42,32 +43,11 @@ describe('Intro component', () => {
expect(mounted).toMatchSnapshot();
});
it('displays correct title depending on canEdit', () => {
let mounted = mountHeader({
...defaultProps,
canEdit: true,
});
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit search"`);
mounted = mountHeader({
...defaultProps,
canEdit: false,
});
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"View search"`);
});
it('displays correct title depending on type', () => {
let mounted = mountHeader({
...defaultProps,
type: 'some-type',
});
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit some-type"`);
mounted = mountHeader({
...defaultProps,
type: 'another-type',
});
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Edit another-type"`);
it('displays correct title if one is provided', () => {
let mounted = mountHeader({ ...defaultProps, title: 'my saved search' });
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect my saved search"`);
mounted = mountHeader({ ...defaultProps, title: 'my other saved search' });
expect(mounted.find('h1').text()).toMatchInlineSnapshot(`"Inspect my other saved search"`);
});
it('only displays delete button if canDelete is true', () => {

View file

@ -9,43 +9,24 @@
import React from 'react';
import { EuiButton, EuiPageHeader } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
interface HeaderProps {
canEdit: boolean;
canDelete: boolean;
canViewInApp: boolean;
type: string;
viewUrl: string;
onDeleteClick: () => void;
title?: string;
}
const renderConditionalTitle = (canEdit: boolean, type: string) =>
canEdit ? (
<FormattedMessage
id="savedObjectsManagement.view.editItemTitle"
defaultMessage="Edit {title}"
values={{ title: type }}
/>
) : (
<FormattedMessage
id="savedObjectsManagement.view.viewItemTitle"
defaultMessage="View {title}"
values={{ title: type }}
/>
);
export const Header = ({
canEdit,
canDelete,
canViewInApp,
type,
viewUrl,
onDeleteClick,
}: HeaderProps) => {
export const Header = ({ canDelete, canViewInApp, viewUrl, onDeleteClick, title }: HeaderProps) => {
return (
<EuiPageHeader
bottomBorder
pageTitle={renderConditionalTitle(canEdit, type)}
pageTitle={i18n.translate('savedObjectsManagement.view.inspectItemTitle', {
defaultMessage: 'Inspect {title}',
values: { title: title || 'saved object' },
})}
rightSideItems={[
canViewInApp && (
<EuiButton
@ -57,7 +38,7 @@ export const Header = ({
<FormattedMessage
id="savedObjectsManagement.view.viewItemButtonLabel"
defaultMessage="View {title}"
values={{ title: type }}
values={{ title: title || 'saved object' }}
/>
</EuiButton>
),
@ -71,8 +52,7 @@ export const Header = ({
>
<FormattedMessage
id="savedObjectsManagement.view.deleteItemButtonLabel"
defaultMessage="Delete {title}"
values={{ title: type }}
defaultMessage="Delete"
/>
</EuiButton>
),

View file

@ -7,6 +7,5 @@
*/
export { Header } from './header';
export { Inspect } from './inspect';
export { NotFoundErrors } from './not_found_errors';
export { Intro } from './intro';
export { Form } from './form';

View file

@ -0,0 +1,69 @@
/*
* 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 React from 'react';
import { ShallowWrapper } from 'enzyme';
import { shallowWithI18nProvider } from '@kbn/test/jest';
import { Inspect, InspectProps } from './inspect';
import { SavedObjectWithMetadata } from '../../../../common';
describe('Inspect component', () => {
let defaultProps: { object: SavedObjectWithMetadata };
const shallowRender = (overrides: Partial<SavedObjectWithMetadata> = {}) => {
return shallowWithI18nProvider(
<Inspect {...defaultProps} {...overrides} />
) as unknown as ShallowWrapper<InspectProps>;
};
beforeEach(() => {
defaultProps = {
object: {
id: '1',
type: 'index-pattern',
attributes: {
title: `MyIndexPattern*`,
},
meta: {
title: `MyIndexPattern*`,
icon: 'indexPatternApp',
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
references: [],
},
};
});
it('renders correctly', async () => {
const component = shallowRender();
await new Promise((resolve) => process.nextTick(resolve));
component.update();
const codeEditorComponent = component.find('CodeEditor');
expect(codeEditorComponent).toMatchSnapshot();
});
it("does not include `meta` in the value that's rendered", async () => {
const component = shallowRender();
await new Promise((resolve) => process.nextTick(resolve));
component.update();
const codeEditorComponent = component.find('CodeEditor');
// find could return nothing
const editorValue = codeEditorComponent
? (codeEditorComponent.prop('value') as unknown as string)
: '';
// we assert against the expected object props rather than asserting that 'meta' is removed
expect(Object.keys(JSON.parse(editorValue))).toEqual([
'id',
'type',
'attributes',
'references',
]);
});
});

View file

@ -0,0 +1,79 @@
/*
* 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 React, { FC, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { XJsonLang } from '@kbn/monaco';
import { omit } from 'lodash';
import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { CodeEditor } from '../../../../../kibana_react/public';
import { SavedObjectWithMetadata } from '../../../../common';
export interface InspectProps {
object: SavedObjectWithMetadata<any>;
}
const codeEditorAriaLabel = (title: string) =>
i18n.translate('savedObjectsManagement.view.inspectCodeEditorAriaLabel', {
defaultMessage: 'inspect { title }',
values: {
title,
},
});
const copyToClipboardLabel = i18n.translate('savedObjectsManagement.view.copyToClipboardLabel', {
defaultMessage: 'Copy to clipboard',
});
export const Inspect: FC<InspectProps> = ({ object }) => {
const title = object.meta.title || 'saved object';
const objectAsJsonString = useMemo(() => JSON.stringify(omit(object, 'meta'), null, 2), [object]);
return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<div className="eui-textRight">
<EuiCopy textToCopy={objectAsJsonString}>
{(copy) => (
<EuiButtonEmpty
aria-label={copyToClipboardLabel}
size="s"
flush="right"
iconType="copyClipboard"
onClick={copy}
>
{copyToClipboardLabel}
</EuiButtonEmpty>
)}
</EuiCopy>
<EuiSpacer size="s" />
</div>
<CodeEditor
languageId={XJsonLang.ID}
value={objectAsJsonString}
aria-label={codeEditorAriaLabel(title)}
options={{
automaticLayout: false,
fontSize: 12,
lineNumbers: 'on',
minimap: {
enabled: false,
},
overviewRulerBorder: false,
readOnly: true,
scrollbar: {
alwaysConsumeMouseWheel: false,
},
scrollBeyondLastLine: false,
wordWrap: 'on',
wrappingIndent: 'indent',
renderIndentGuides: false,
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -1,23 +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 React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { Intro } from './intro';
describe('Intro component', () => {
it('renders correctly', () => {
const mounted = mount(
<I18nProvider>
<Intro />
</I18nProvider>
);
expect(mounted.find('Intro')).toMatchSnapshot();
});
});

View file

@ -1,33 +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 React from 'react';
import { EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export const Intro = () => {
return (
<EuiCallOut
title={
<FormattedMessage
id="savedObjectsManagement.view.howToModifyObjectTitle"
defaultMessage="Proceed with caution!"
/>
}
iconType="alert"
color="warning"
>
<div>
<FormattedMessage
id="savedObjectsManagement.view.howToModifyObjectDescription"
defaultMessage="Modifying objects is for advanced users only. Object properties are not validated and invalid objects could cause errors, data loss, or worse. Unless someone with intimate knowledge of the code told you to be in here, you probably shouldn&rsquo;t be."
/>
</div>
</EuiCallOut>
);
};

View file

@ -10,44 +10,49 @@ import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';
import { NotFoundErrors } from './not_found_errors';
import { docLinksServiceMock } from '../../../../../../core/public/mocks';
describe('NotFoundErrors component', () => {
const mountError = (type: string) =>
mount(
<I18nProvider>
<NotFoundErrors type={type} />
<NotFoundErrors type={type} docLinks={docLinksServiceMock.createStartContract().links} />
</I18nProvider>
).find('NotFoundErrors');
it('renders correctly for search type', () => {
const mounted = mountError('search');
expect(mounted).toMatchSnapshot();
const callOut = mounted.find('EuiCallOut');
expect(callOut).toMatchSnapshot();
expect(mounted.text()).toMatchInlineSnapshot(
`"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, go ahead and fix it — otherwise click the delete button above."`
`"There is a problem with this saved objectThe saved search associated with this object no longer exists.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."`
);
});
it('renders correctly for index-pattern type', () => {
const mounted = mountError('index-pattern');
expect(mounted).toMatchSnapshot();
const callOut = mounted.find('EuiCallOut');
expect(callOut).toMatchSnapshot();
expect(mounted.text()).toMatchInlineSnapshot(
`"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, go ahead and fix it — otherwise click the delete button above."`
`"There is a problem with this saved objectThe index pattern associated with this object no longer exists.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."`
);
});
it('renders correctly for index-pattern-field type', () => {
const mounted = mountError('index-pattern-field');
expect(mounted).toMatchSnapshot();
const callOut = mounted.find('EuiCallOut');
expect(callOut).toMatchSnapshot();
expect(mounted.text()).toMatchInlineSnapshot(
`"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, go ahead and fix it — otherwise click the delete button above."`
`"There is a problem with this saved objectA field associated with this object no longer exists in the index pattern.If you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."`
);
});
it('renders correctly for unknown type', () => {
const mounted = mountError('unknown');
expect(mounted).toMatchSnapshot();
const callOut = mounted.find('EuiCallOut');
expect(callOut).toMatchSnapshot();
expect(mounted.text()).toMatchInlineSnapshot(
`"There is a problem with this saved objectIf you know what this error means, go ahead and fix it — otherwise click the delete button above."`
`"There is a problem with this saved objectIf you know what this error means, you can use the Saved objects APIs(opens in a new tab or window) to fix it — otherwise click the delete button above."`
);
});
});

View file

@ -8,13 +8,23 @@
import React from 'react';
import { EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink } from '@elastic/eui';
import { DocLinksStart } from '../../../../../../core/public';
interface NotFoundErrors {
type: string;
docLinks: DocLinksStart['links'];
}
const savedObjectsApisLinkText = i18n.translate(
'savedObjectsManagement.view.howToFixErrorDescriptionLinkText',
{
defaultMessage: 'Saved objects APIs',
}
);
export const NotFoundErrors = ({ type }: NotFoundErrors) => {
export const NotFoundErrors = ({ type, docLinks }: NotFoundErrors) => {
const getMessage = () => {
switch (type) {
case 'search':
@ -58,7 +68,18 @@ export const NotFoundErrors = ({ type }: NotFoundErrors) => {
<div>
<FormattedMessage
id="savedObjectsManagement.view.howToFixErrorDescription"
defaultMessage="If you know what this error means, go ahead and fix it &mdash; otherwise click the delete button above."
defaultMessage="If you know what this error means, you can use the {savedObjectsApis} to fix it &mdash; otherwise click the delete button above."
values={{
savedObjectsApis: (
<EuiLink
aria-label={savedObjectsApisLinkText}
href={`${docLinks.management.savedObjectsApiList}`}
target="_blank"
>
{savedObjectsApisLinkText}
</EuiLink>
),
}}
/>
</div>
</EuiCallOut>

View file

@ -0,0 +1,3 @@
.savedObjectsManagementObjectView {
height: 100%;
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
jest.doMock('lodash', () => {
const original = jest.requireActual('lodash');
return {
...original,
get: (func: Function) => {
function get(this: any, args: any[]) {
return func.apply(this, args);
}
return get;
},
};
});
export const bulkGetObjectsMock = jest.fn();
jest.doMock('../../lib/bulk_get_objects', () => ({
bulkGetObjects: bulkGetObjectsMock,
}));

View file

@ -0,0 +1,329 @@
/*
* 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 { bulkGetObjectsMock } from './saved_object_view.test.mocks';
import React from 'react';
import { ShallowWrapper } from 'enzyme';
import { shallowWithI18nProvider } from '@kbn/test/jest';
import {
httpServiceMock,
overlayServiceMock,
notificationServiceMock,
savedObjectsServiceMock,
applicationServiceMock,
uiSettingsServiceMock,
scopedHistoryMock,
docLinksServiceMock,
} from '../../../../../core/public/mocks';
import {
SavedObjectEdition,
SavedObjectEditionProps,
SavedObjectEditionState,
} from './saved_object_view';
const resolvePromises = () => new Promise((resolve) => process.nextTick(resolve));
describe('SavedObjectEdition', () => {
let defaultProps: SavedObjectEditionProps;
let http: ReturnType<typeof httpServiceMock.createStartContract>;
let overlays: ReturnType<typeof overlayServiceMock.createStartContract>;
let notifications: ReturnType<typeof notificationServiceMock.createStartContract>;
let savedObjects: ReturnType<typeof savedObjectsServiceMock.createStartContract>;
let uiSettings: ReturnType<typeof uiSettingsServiceMock.createStartContract>;
let history: ReturnType<typeof scopedHistoryMock.create>;
let applications: ReturnType<typeof applicationServiceMock.createStartContract>;
let docLinks: ReturnType<typeof docLinksServiceMock.createStartContract>;
const shallowRender = (overrides: Partial<SavedObjectEditionProps> = {}) => {
return shallowWithI18nProvider(
<SavedObjectEdition {...defaultProps} {...overrides} />
) as unknown as ShallowWrapper<
SavedObjectEditionProps,
SavedObjectEditionState,
SavedObjectEdition
>;
};
beforeEach(() => {
http = httpServiceMock.createStartContract();
overlays = overlayServiceMock.createStartContract();
notifications = notificationServiceMock.createStartContract();
savedObjects = savedObjectsServiceMock.createStartContract();
uiSettings = uiSettingsServiceMock.createStartContract();
history = scopedHistoryMock.create();
docLinks = docLinksServiceMock.createStartContract();
applications = applicationServiceMock.createStartContract();
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: false,
},
};
http.post.mockResolvedValue([]);
defaultProps = {
id: '1',
savedObjectType: 'dashboard',
http,
capabilities: applications.capabilities,
overlays,
notifications,
savedObjectsClient: savedObjects.client,
history,
uiSettings,
docLinks: docLinks.links,
};
bulkGetObjectsMock.mockImplementation(() => [{}]);
});
it('should render normally', async () => {
bulkGetObjectsMock.mockImplementation(() =>
Promise.resolve([
{
id: '1',
type: 'dashboard',
attributes: {
title: `MyDashboard*`,
},
meta: {
title: `MyDashboard*`,
icon: 'dashboardApp',
inAppUrl: {
path: '/app/dashboards#/view/1',
uiCapabilitiesPath: 'management.kibana.dashboard',
},
},
},
])
);
const component = shallowRender();
// Ensure all promises resolve
await resolvePromises();
// Ensure the state changes are reflected
component.update();
expect(component).toMatchSnapshot();
});
it('should add danger toast when bulk get fails', async () => {
bulkGetObjectsMock.mockImplementation(() =>
Promise.resolve([
{
error: {
message: 'Not found',
},
},
])
);
const component = shallowRender({ notFoundType: 'does_not_exist' });
await resolvePromises();
component.update();
expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
});
it('should add danger toast when bulk get throws', async () => {
bulkGetObjectsMock.mockImplementation(() => Promise.reject(new Error('fail')));
const component = shallowRender({ notFoundType: 'does_not_exist' });
await resolvePromises();
component.update();
expect(notifications.toasts.addDanger).toHaveBeenCalledTimes(1);
});
it('should pass the correct props to the child components', async () => {
const savedObjectItem = {
id: '1',
type: 'index-pattern',
attributes: {
title: `MyIndexPattern*`,
},
meta: {
title: `MyIndexPattern*`,
icon: 'indexPatternApp',
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
hiddenType: false,
},
};
bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem]));
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: true,
},
};
const component = shallowRender({
capabilities: applications.capabilities,
});
await resolvePromises();
component.update();
const headerComponent = component.find('Header');
expect(headerComponent.prop('canViewInApp')).toBe(true);
expect(headerComponent.prop('canDelete')).toBe(true);
expect(headerComponent.prop('viewUrl')).toEqual('/management/kibana/indexPatterns/patterns/1');
const inspectComponent = component.find('Inspect');
expect(inspectComponent.prop('object')).toEqual(savedObjectItem);
});
it("does not render Inspect if there isn't an object", async () => {
bulkGetObjectsMock.mockImplementation(() => Promise.resolve([]));
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: true,
},
};
const component = shallowRender({
capabilities: applications.capabilities,
});
await resolvePromises();
component.update();
const inspectComponent = component.find('Inspect');
expect(inspectComponent).toEqual({});
});
describe('delete', () => {
const savedObjectItem = {
id: '1',
type: 'index-pattern',
attributes: {
title: `MyIndexPattern*`,
},
meta: {
title: `MyIndexPattern*`,
icon: 'indexPatternApp',
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
hiddenType: false,
},
};
it('should display a confirmation message on deleting the saved object', async () => {
bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem]));
const mockSavedObjectsClient = {
...defaultProps.savedObjectsClient,
delete: jest.fn().mockImplementation(() => ({})),
};
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: true,
},
};
overlays.openConfirm.mockResolvedValue(false);
const component = shallowRender({
capabilities: applications.capabilities,
savedObjectsClient: mockSavedObjectsClient,
overlays,
});
await resolvePromises();
component.update();
component.instance().delete();
expect(overlays.openConfirm).toHaveBeenCalledWith(
'This action permanently removes the object from Kibana.',
{
buttonColor: 'danger',
confirmButtonText: 'Delete',
title: `Delete '${savedObjectItem.meta.title}'?`,
}
);
});
it('should route back if action is confirm and user accepted', async () => {
bulkGetObjectsMock.mockImplementation(() => Promise.resolve([savedObjectItem]));
const mockSavedObjectsClient = {
...defaultProps.savedObjectsClient,
delete: jest.fn().mockImplementation(() => ({})),
};
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: true,
},
};
overlays.openConfirm.mockResolvedValue(true);
const component = shallowRender({
capabilities: applications.capabilities,
savedObjectsClient: mockSavedObjectsClient,
overlays,
});
await resolvePromises();
component.update();
component.instance().delete();
expect(overlays.openConfirm).toHaveBeenCalledTimes(1);
expect(history.location.pathname).toEqual('/');
});
it('should not enable delete if the saved object is hidden', async () => {
bulkGetObjectsMock.mockImplementation(() =>
Promise.resolve([{ ...savedObjectItem, meta: { hiddenType: true } }])
);
applications.capabilities = {
navLinks: {},
management: {},
catalogue: {},
savedObjectsManagement: {
read: true,
edit: false,
delete: true,
},
};
const component = shallowRender({
capabilities: applications.capabilities,
});
await resolvePromises();
component.update();
expect(component.find('Header').prop('canDelete')).toBe(false);
});
});
});

View file

@ -8,7 +8,9 @@
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { get } from 'lodash';
import { KibanaContextProvider } from '../../../../kibana_react/public';
import {
Capabilities,
SavedObjectsClientContract,
@ -16,27 +18,27 @@ import {
NotificationsStart,
ScopedHistory,
HttpSetup,
IUiSettingsClient,
DocLinksStart,
} from '../../../../../core/public';
import { ISavedObjectsManagementServiceRegistry } from '../../services';
import { Header, NotFoundErrors, Intro, Form } from './components';
import { canViewInApp, bulkGetObjects } from '../../lib';
import { SubmittedFormData } from '../types';
import { Header, Inspect, NotFoundErrors } from './components';
import { bulkGetObjects } from '../../lib/bulk_get_objects';
import { SavedObjectWithMetadata } from '../../types';
interface SavedObjectEditionProps {
import './saved_object_view.scss';
export interface SavedObjectEditionProps {
id: string;
savedObjectType: string;
http: HttpSetup;
serviceName: string;
serviceRegistry: ISavedObjectsManagementServiceRegistry;
capabilities: Capabilities;
overlays: OverlayStart;
notifications: NotificationsStart;
notFoundType?: string;
savedObjectsClient: SavedObjectsClientContract;
history: ScopedHistory;
uiSettings: IUiSettingsClient;
docLinks: DocLinksStart['links'];
}
interface SavedObjectEditionState {
export interface SavedObjectEditionState {
type: string;
object?: SavedObjectWithMetadata<any>;
}
@ -45,7 +47,6 @@ const unableFindSavedObjectNotificationMessage = i18n.translate(
'savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage',
{ defaultMessage: 'Unable to find saved object' }
);
export class SavedObjectEdition extends Component<
SavedObjectEditionProps,
SavedObjectEditionState
@ -53,8 +54,7 @@ export class SavedObjectEdition extends Component<
constructor(props: SavedObjectEditionProps) {
super(props);
const { serviceRegistry, serviceName } = props;
const type = serviceRegistry.get(serviceName)!.service.type;
const { savedObjectType: type } = props;
this.state = {
object: undefined,
@ -85,54 +85,46 @@ export class SavedObjectEdition extends Component<
});
}
render() {
const { capabilities, notFoundType, serviceRegistry, http, serviceName, savedObjectsClient } =
this.props;
const { type } = this.state;
const { object } = this.state;
const { edit: canEdit, delete: canDelete } = capabilities.savedObjectsManagement as Record<
string,
boolean
>;
const canView = canViewInApp(capabilities, type) && Boolean(object?.meta.inAppUrl?.path);
const service = serviceRegistry.get(serviceName)!.service;
canViewInApp(capabilities: Capabilities, obj?: SavedObjectWithMetadata<any>) {
return obj && obj.meta.inAppUrl
? get(capabilities, obj?.meta.inAppUrl?.uiCapabilitiesPath, false) &&
Boolean(obj?.meta.inAppUrl?.path)
: false;
}
render() {
const { capabilities, notFoundType, http, uiSettings, docLinks } = this.props;
const { object } = this.state;
const { delete: canDelete } = capabilities.savedObjectsManagement as Record<string, boolean>;
const canView = this.canViewInApp(capabilities, object);
return (
<div data-test-subj="savedObjectsEdit">
<Header
canEdit={canEdit}
canDelete={canDelete && !object?.meta.hiddenType}
canViewInApp={canView}
type={type}
onDeleteClick={() => this.delete()}
viewUrl={http.basePath.prepend(object?.meta.inAppUrl?.path || '')}
/>
<EuiSpacer size="l" />
{notFoundType && (
<>
<EuiSpacer size="s" />
<NotFoundErrors type={notFoundType} />
</>
)}
{canEdit && (
<>
<EuiSpacer size="s" />
<Intro />
</>
)}
{object && (
<>
<EuiSpacer size="m" />
<Form
object={object}
savedObjectsClient={savedObjectsClient}
service={service}
editionEnabled={canEdit}
onSave={this.saveChanges}
<KibanaContextProvider services={{ uiSettings }}>
<EuiFlexGroup
direction="column"
data-test-subject="savedObjectsEdit"
className="savedObjectsManagementObjectView"
>
<EuiFlexItem grow={false}>
<Header
canDelete={canDelete && !object?.meta.hiddenType}
canViewInApp={canView}
onDeleteClick={() => this.delete()}
viewUrl={http.basePath.prepend(object?.meta.inAppUrl?.path || '')}
title={object?.meta.title}
/>
</>
)}
</div>
</EuiFlexItem>
{notFoundType && (
<EuiFlexItem grow={false}>
<NotFoundErrors type={notFoundType} docLinks={docLinks} />
</EuiFlexItem>
)}
{object && (
<EuiFlexItem grow={true}>
<Inspect object={object} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</KibanaContextProvider>
);
}
@ -167,15 +159,6 @@ export class SavedObjectEdition extends Component<
}
}
saveChanges = async ({ attributes, references }: SubmittedFormData) => {
const { savedObjectsClient, notifications } = this.props;
const { object, type } = this.state;
await savedObjectsClient.update(object!.type, object!.id, attributes, { references });
notifications.toasts.addSuccess(`Updated '${attributes.title}' ${type} object`);
this.redirectToListing();
};
redirectToListing() {
this.props.history.push('/');
}

View file

@ -192,7 +192,6 @@ exports[`SavedObjectsTable should render normally 1`] = `
Object {
"id": "2",
"meta": Object {
"editUrl": "/management/kibana/objects/savedSearches/2",
"icon": "search",
"inAppUrl": Object {
"path": "/discover/2",
@ -205,7 +204,6 @@ exports[`SavedObjectsTable should render normally 1`] = `
Object {
"id": "3",
"meta": Object {
"editUrl": "/management/kibana/objects/savedDashboards/3",
"icon": "dashboardApp",
"inAppUrl": Object {
"path": "/dashboard/3",
@ -218,7 +216,6 @@ exports[`SavedObjectsTable should render normally 1`] = `
Object {
"id": "4",
"meta": Object {
"editUrl": "/management/kibana/objects/savedVisualizations/4",
"icon": "visualizeApp",
"inAppUrl": Object {
"path": "/edit/4",

View file

@ -146,7 +146,6 @@ exports[`Table prevents saved objects from being deleted 1`] = `
Object {
"actions": Array [
Object {
"available": [Function],
"data-test-subj": "savedObjectsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",
@ -362,7 +361,6 @@ exports[`Table should render normally 1`] = `
Object {
"actions": Array [
Object {
"available": [Function],
"data-test-subj": "savedObjectsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",

View file

@ -11,7 +11,6 @@ import { importFileMock, resolveImportErrorsMock } from './flyout.test.mocks';
import React from 'react';
import { shallowWithI18nProvider } from '@kbn/test/jest';
import { coreMock, httpServiceMock } from '../../../../../../core/public/mocks';
import { serviceRegistryMock } from '../../../services/service_registry.mock';
import { Flyout, FlyoutProps, FlyoutState } from './flyout';
import { ShallowWrapper } from 'enzyme';
import { dataPluginMock } from '../../../../../data/public/mocks';
@ -49,7 +48,6 @@ describe('Flyout', () => {
} as any,
http,
allowedTypes: ['search', 'index-pattern', 'visualization'],
serviceRegistry: serviceRegistryMock.create(),
search,
basePath,
};

View file

@ -43,7 +43,6 @@ import {
processImportResponse,
ProcessedImportResponse,
} from '../../../lib';
import { ISavedObjectsManagementServiceRegistry } from '../../../services';
import { FailedImportConflict, RetryDecision } from '../../../lib/resolve_import_errors';
import { OverwriteModal } from './overwrite_modal';
import { ImportModeControl, ImportMode } from './import_mode_control';
@ -53,7 +52,6 @@ const CREATE_NEW_COPIES_DEFAULT = false;
const OVERWRITE_ALL_DEFAULT = true;
export interface FlyoutProps {
serviceRegistry: ISavedObjectsManagementServiceRegistry;
allowedTypes: string[];
close: () => void;
done: () => void;

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import './import_summary.scss';
import _ from 'lodash';
import React, { Fragment, FC, useMemo } from 'react';
import {
@ -30,6 +29,7 @@ import type {
IBasePath,
} from 'kibana/public';
import { getDefaultTitle, getSavedObjectLabel, FailedImport } from '../../../lib';
import './import_summary.scss';
const DEFAULT_ICON = 'apps';

View file

@ -298,7 +298,7 @@ export class Relationships extends Component<RelationshipsProps, RelationshipsSt
icon: 'inspect',
'data-test-subj': 'relationshipsTableAction-inspect',
onClick: (object: SavedObjectWithMetadata) => goInspectObject(object),
available: (object: SavedObjectWithMetadata) => !!object.meta.editUrl,
available: (object: SavedObjectWithMetadata) => !!(object.type && object.id),
},
],
},

View file

@ -243,7 +243,6 @@ export class Table extends PureComponent<TableProps, TableState> {
type: 'icon',
icon: 'inspect',
onClick: (object) => goInspectObject(object),
available: (object) => !!object.meta.editUrl,
'data-test-subj': 'savedObjectsTableAction-inspect',
},
{

View file

@ -28,7 +28,6 @@ import {
applicationServiceMock,
} from '../../../../../core/public/mocks';
import { dataPluginMock } from '../../../../data/public/mocks';
import { serviceRegistryMock } from '../../services/service_registry.mock';
import { actionServiceMock } from '../../services/action_service.mock';
import { columnServiceMock } from '../../services/column_service.mock';
import {
@ -122,7 +121,6 @@ describe('SavedObjectsTable', () => {
defaultProps = {
allowedTypes,
serviceRegistry: serviceRegistryMock.create(),
actionRegistry: actionServiceMock.createStart(),
columnRegistry: columnServiceMock.createStart(),
savedObjectsClient: savedObjects.client,
@ -159,7 +157,6 @@ describe('SavedObjectsTable', () => {
meta: {
title: `MySearch`,
icon: 'search',
editUrl: '/management/kibana/objects/savedSearches/2',
inAppUrl: {
path: '/discover/2',
uiCapabilitiesPath: 'discover.show',
@ -172,7 +169,6 @@ describe('SavedObjectsTable', () => {
meta: {
title: `MyDashboard`,
icon: 'dashboardApp',
editUrl: '/management/kibana/objects/savedDashboards/3',
inAppUrl: {
path: '/dashboard/3',
uiCapabilitiesPath: 'dashboard.show',
@ -185,7 +181,6 @@ describe('SavedObjectsTable', () => {
meta: {
title: `MyViz`,
icon: 'visualizeApp',
editUrl: '/management/kibana/objects/savedVisualizations/4',
inAppUrl: {
path: '/edit/4',
uiCapabilitiesPath: 'visualize.show',

View file

@ -37,7 +37,6 @@ import {
} from '../../lib';
import { SavedObjectWithMetadata } from '../../types';
import {
ISavedObjectsManagementServiceRegistry,
SavedObjectsManagementActionServiceStart,
SavedObjectsManagementColumnServiceStart,
} from '../../services';
@ -58,7 +57,6 @@ interface ExportAllOption {
export interface SavedObjectsTableProps {
allowedTypes: string[];
serviceRegistry: ISavedObjectsManagementServiceRegistry;
actionRegistry: SavedObjectsManagementActionServiceStart;
columnRegistry: SavedObjectsManagementColumnServiceStart;
savedObjectsClient: SavedObjectsClientContract;
@ -540,7 +538,6 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
close={this.hideImportFlyout}
done={this.finishImport}
http={this.props.http}
serviceRegistry={this.props.serviceRegistry}
indexPatterns={this.props.indexPatterns}
newIndexPatternUrl={newIndexPatternUrl}
allowedTypes={this.props.allowedTypes}

View file

@ -0,0 +1,3 @@
.savedObjectsManagementEditionPage {
height: 100%
}

View file

@ -12,26 +12,24 @@ import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { CoreStart, ChromeBreadcrumb, ScopedHistory } from 'src/core/public';
import { RedirectAppLinks } from '../../../kibana_react/public';
import { ISavedObjectsManagementServiceRegistry } from '../services';
import { SavedObjectEdition } from './object_view';
import './saved_objects_edition_page.scss';
const SavedObjectsEditionPage = ({
coreStart,
serviceRegistry,
setBreadcrumbs,
history,
}: {
coreStart: CoreStart;
serviceRegistry: ISavedObjectsManagementServiceRegistry;
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
history: ScopedHistory;
}) => {
const { service: serviceName, id } = useParams<{ service: string; id: string }>();
const { type, id } = useParams<{ type: string; id: string }>();
const capabilities = coreStart.application.capabilities;
const dockLinks = coreStart.docLinks.links;
const { search } = useLocation();
const query = parse(search);
const service = serviceRegistry.get(serviceName);
useEffect(() => {
setBreadcrumbs([
@ -42,27 +40,31 @@ const SavedObjectsEditionPage = ({
href: '/',
},
{
text: i18n.translate('savedObjectsManagement.breadcrumb.edit', {
defaultMessage: 'Edit {savedObjectType}',
values: { savedObjectType: service?.service.type ?? 'object' },
text: i18n.translate('savedObjectsManagement.breadcrumb.inspect', {
defaultMessage: 'Inspect {savedObjectType}',
values: { savedObjectType: type },
}),
},
]);
}, [setBreadcrumbs, service]);
}, [setBreadcrumbs, type]);
return (
<RedirectAppLinks application={coreStart.application}>
<RedirectAppLinks
application={coreStart.application}
className="savedObjectsManagementEditionPage"
>
<SavedObjectEdition
id={id}
savedObjectType={type}
http={coreStart.http}
serviceName={serviceName}
serviceRegistry={serviceRegistry}
savedObjectsClient={coreStart.savedObjects.client}
overlays={coreStart.overlays}
notifications={coreStart.notifications}
capabilities={capabilities}
notFoundType={query.notFound as string}
uiSettings={coreStart.uiSettings}
history={history}
docLinks={dockLinks}
/>
</RedirectAppLinks>
);

View file

@ -75,13 +75,11 @@ const SavedObjectsTablePage = ({
spacesApi ? spacesApi.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
[spacesApi]
);
return (
<ContextWrapper>
<SavedObjectsTable
initialQuery={initialQuery}
allowedTypes={allowedTypes}
serviceRegistry={serviceRegistry}
actionRegistry={actionRegistry}
columnRegistry={columnRegistry}
taggingApi={taggingApi}
@ -94,12 +92,10 @@ const SavedObjectsTablePage = ({
applications={coreStart.application}
perPageConfig={itemsPerPage}
goInspectObject={(savedObject) => {
const { editUrl } = savedObject.meta;
if (editUrl) {
return coreStart.application.navigateToUrl(
coreStart.http.basePath.prepend(`/app${editUrl}`)
);
}
const savedObjectEditUrl = savedObject.meta.editUrl
? `/app${savedObject.meta.editUrl}`
: `/app/management/kibana/objects/${savedObject.type}/${savedObject.id}`;
coreStart.application.navigateToUrl(coreStart.http.basePath.prepend(savedObjectEditUrl));
}}
canGoInApp={(savedObject) => {
const { inAppUrl } = savedObject.meta;

View file

@ -20,9 +20,6 @@ export const visualizationSavedObjectType: SavedObjectsType = {
getTitle(obj) {
return obj.attributes.title;
},
getEditUrl(obj) {
return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`;
},
getInAppUrl(obj) {
return {
path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`,

View file

@ -180,8 +180,6 @@ export default function ({ getService }: FtrProviderContext) {
icon: 'discoverApp',
title: 'OneRecord',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'discover.show',
@ -200,8 +198,6 @@ export default function ({ getService }: FtrProviderContext) {
icon: 'dashboardApp',
title: 'Dashboard',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedDashboards/b70c7ae0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'dashboard.show',
@ -220,8 +216,6 @@ export default function ({ getService }: FtrProviderContext) {
icon: 'visualizeApp',
title: 'VisualizationFromSavedSearch',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -232,8 +226,6 @@ export default function ({ getService }: FtrProviderContext) {
icon: 'visualizeApp',
title: 'Visualization',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',

View file

@ -21,7 +21,7 @@ export default function ({ getService }: FtrProviderContext) {
meta: schema.object({
title: schema.string(),
icon: schema.string(),
editUrl: schema.string(),
editUrl: schema.maybe(schema.string()),
inAppUrl: schema.object({
path: schema.string(),
uiCapabilitiesPath: schema.string(),
@ -103,8 +103,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
title: 'VisualizationFromSavedSearch',
icon: 'visualizeApp',
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -147,8 +145,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'VisualizationFromSavedSearch',
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -192,8 +188,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'Visualization',
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -209,8 +203,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'VisualizationFromSavedSearch',
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -234,8 +226,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'Visualization',
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -251,8 +241,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'VisualizationFromSavedSearch',
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/a42c0580-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -296,8 +284,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'discoverApp',
title: 'OneRecord',
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'discover.show',
@ -313,8 +299,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'dashboardApp',
title: 'Dashboard',
editUrl:
'/management/kibana/objects/savedDashboards/b70c7ae0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'dashboard.show',
@ -340,8 +324,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'discoverApp',
title: 'OneRecord',
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'discover.show',
@ -385,8 +367,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'discoverApp',
title: 'OneRecord',
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'discover.show',
@ -402,8 +382,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'visualizeApp',
title: 'Visualization',
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'visualize.show',
@ -429,8 +407,6 @@ export default function ({ getService }: FtrProviderContext) {
meta: {
icon: 'discoverApp',
title: 'OneRecord',
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'discover.show',
@ -475,8 +451,6 @@ export default function ({ getService }: FtrProviderContext) {
{
id: 'add810b0-3224-11e8-a572-ffca06da1357',
meta: {
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
icon: 'visualizeApp',
inAppUrl: {
path: '/app/visualize#/edit/add810b0-3224-11e8-a572-ffca06da1357',

View file

@ -1,182 +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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'settings', 'savedObjects']);
const browser = getService('browser');
const find = getService('find');
const setFieldValue = async (fieldName: string, value: string) => {
return testSubjects.setValue(`savedObjects-editField-${fieldName}`, value);
};
const getFieldValue = async (fieldName: string) => {
return testSubjects.getAttribute(`savedObjects-editField-${fieldName}`, 'value');
};
const setAceEditorFieldValue = async (fieldName: string, fieldValue: string) => {
const editorId = `savedObjects-editField-${fieldName}-aceEditor`;
await find.clickByCssSelector(`#${editorId}`);
return browser.execute(
(editor: string, value: string) => {
return (window as any).ace.edit(editor).setValue(value);
},
editorId,
fieldValue
);
};
const getAceEditorFieldValue = async (fieldName: string) => {
const editorId = `savedObjects-editField-${fieldName}-aceEditor`;
await find.clickByCssSelector(`#${editorId}`);
return browser.execute((editor: string) => {
return (window as any).ace.edit(editor).getValue() as string;
}, editorId);
};
const focusAndClickButton = async (buttonSubject: string) => {
const button = await testSubjects.find(buttonSubject);
await button.scrollIntoViewIfNecessary();
await delay(10);
await button.focus();
await delay(10);
await button.click();
// Allow some time for the transition/animations to occur before assuming the click is done
await delay(10);
};
describe('saved objects edition page', () => {
beforeEach(async () => {
await esArchiver.load(
'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object'
);
});
afterEach(async () => {
await esArchiver.unload(
'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object'
);
});
it('allows to update the saved object when submitting', async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
let objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(true);
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedDashboards/i-exist',
{
shouldUseHashForSubUrl: false,
}
);
await testSubjects.existOrFail('savedObjectEditSave');
expect(await getFieldValue('title')).to.eql('A Dashboard');
await setFieldValue('title', 'Edited Dashboard');
await setFieldValue('description', 'Some description');
await focusAndClickButton('savedObjectEditSave');
objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(false);
expect(objects.includes('Edited Dashboard')).to.be(true);
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedDashboards/i-exist',
{
shouldUseHashForSubUrl: false,
}
);
expect(await getFieldValue('title')).to.eql('Edited Dashboard');
expect(await getFieldValue('description')).to.eql('Some description');
});
it('allows to delete a saved object', async () => {
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedDashboards/i-exist',
{
shouldUseHashForSubUrl: false,
}
);
await focusAndClickButton('savedObjectEditDelete');
await PageObjects.common.clickConfirmOnModal();
const objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(false);
});
it('preserves the object references when saving', async () => {
const testVisualizationUrl =
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed';
const visualizationRefs = [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: 'logstash-*',
},
];
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
const objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Pie')).to.be(true);
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
shouldUseHashForSubUrl: false,
});
await testSubjects.existOrFail('savedObjectEditSave');
let displayedReferencesValue = await getAceEditorFieldValue('references');
expect(JSON.parse(displayedReferencesValue)).to.eql(visualizationRefs);
await focusAndClickButton('savedObjectEditSave');
await PageObjects.savedObjects.getRowTitles();
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
shouldUseHashForSubUrl: false,
});
// Parsing to avoid random keys ordering issues in raw string comparison
expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs);
await setAceEditorFieldValue('references', JSON.stringify([], undefined, 2));
await focusAndClickButton('savedObjectEditSave');
await PageObjects.savedObjects.getRowTitles();
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
shouldUseHashForSubUrl: false,
});
displayedReferencesValue = await getAceEditorFieldValue('references');
expect(JSON.parse(displayedReferencesValue)).to.eql([]);
});
});
}

View file

@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default function savedObjectsManagementApp({ loadTestFile }: FtrProviderContext) {
describe('saved objects management', function savedObjectsManagementAppTestSuite() {
this.tags('ciGroup7');
loadTestFile(require.resolve('./edit_saved_object'));
loadTestFile(require.resolve('./inspect_saved_objects'));
loadTestFile(require.resolve('./show_relationships'));
});
}

View file

@ -0,0 +1,87 @@
/*
* 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';
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'settings', 'savedObjects']);
const find = getService('find');
const focusAndClickButton = async (buttonSubject: string) => {
const button = await testSubjects.find(buttonSubject);
await button.scrollIntoViewIfNecessary();
await delay(10);
await button.focus();
await delay(10);
await button.click();
// Allow some time for the transition/animations to occur before assuming the click is done
await delay(10);
};
const textIncludesAll = (text: string, items: string[]) => {
const bools = items.map((item) => !!text.includes(item));
return bools.every((currBool) => currBool === true);
};
describe('saved objects edition page', () => {
beforeEach(async () => {
await esArchiver.load(
'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object'
);
});
afterEach(async () => {
await esArchiver.unload(
'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object'
);
});
it('allows to view the saved object', async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
const objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(true);
await PageObjects.common.navigateToUrl('management', 'kibana/objects/dashboard/i-exist', {
shouldUseHashForSubUrl: false,
});
const inspectContainer = await find.byClassName('kibanaCodeEditor');
const visibleContainerText = await inspectContainer.getVisibleText();
// ensure that something renders visibly
expect(
textIncludesAll(visibleContainerText, [
'A Dashboard',
'title',
'id',
'type',
'attributes',
'references',
])
).to.be(true);
});
it('allows to delete a saved object', async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
let objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(true);
await PageObjects.savedObjects.clickInspectByTitle('A Dashboard');
await PageObjects.common.navigateToUrl('management', 'kibana/objects/dashboard/i-exist', {
shouldUseHashForSubUrl: false,
});
await focusAndClickButton('savedObjectEditDelete');
await PageObjects.common.clickConfirmOnModal();
objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Dashboard')).to.be(false);
});
});
}

View file

@ -4369,14 +4369,11 @@
"savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存後に{originVerb}から{origin}",
"savedObjects.saveModalOrigin.returnToOriginLabel": "戻る",
"savedObjects.saveModalOrigin.saveAndReturnLabel": "保存して戻る",
"savedObjectsManagement.breadcrumb.edit": "{savedObjectType}を編集",
"savedObjectsManagement.breadcrumb.index": "保存されたオブジェクト",
"savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "削除",
"savedObjectsManagement.deleteConfirm.modalDescription": "このアクションはオブジェクトをKibanaから永久に削除します。",
"savedObjectsManagement.deleteConfirm.modalTitle": "「{title}」を削除しますか?",
"savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:",
"savedObjectsManagement.field.offLabel": "オフ",
"savedObjectsManagement.field.onLabel": "オン",
"savedObjectsManagement.importSummary.createdCountHeader": "{createdCount}件の新規項目",
"savedObjectsManagement.importSummary.createdOutcomeLabel": "作成済み",
"savedObjectsManagement.importSummary.errorCountHeader": "{errorCount}件のエラー",
@ -4478,21 +4475,10 @@
"savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません",
"savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません",
"savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません",
"savedObjectsManagement.view.cancelButtonAriaLabel": "キャンセル",
"savedObjectsManagement.view.cancelButtonLabel": "キャンセル",
"savedObjectsManagement.view.deleteItemButtonLabel": "{title}を削除",
"savedObjectsManagement.view.editItemTitle": "{title}の編集",
"savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "このオブジェクトに関連付けられたフィールドは、現在このインデックスパターンに存在しません。",
"savedObjectsManagement.view.howToFixErrorDescription": "このエラーの原因がわかる場合は修正してください。わからない場合は上の削除ボタンをクリックしてください。",
"savedObjectsManagement.view.howToModifyObjectDescription": "オブジェクトの編集は上級ユーザー向けです。オブジェクトのプロパティが検証されておらず、無効なオブジェクトはエラー、データ損失、またはそれ以上の問題の原因となります。コードを熟知した人に指示されていない限り、この設定は変更しない方が無難です。",
"savedObjectsManagement.view.howToModifyObjectTitle": "十分ご注意ください!",
"savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。",
"savedObjectsManagement.view.saveButtonAriaLabel": "{ title }オブジェクトを保存",
"savedObjectsManagement.view.saveButtonLabel": "{ title }オブジェクトを保存",
"savedObjectsManagement.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります",
"savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。",
"savedObjectsManagement.view.viewItemButtonLabel": "{title}を表示",
"savedObjectsManagement.view.viewItemTitle": "{title}を表示",
"security.checkup.dismissButtonText": "閉じる",
"security.checkup.dontShowAgain": "今後表示しない",
"security.checkup.insecureClusterMessage": "1 ビットを失わないでください。Elastic では無料でデータを保護できます。",

View file

@ -4409,14 +4409,11 @@
"savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存后{originVerb}至{origin}",
"savedObjects.saveModalOrigin.returnToOriginLabel": "返回",
"savedObjects.saveModalOrigin.saveAndReturnLabel": "保存并返回",
"savedObjectsManagement.breadcrumb.edit": "编辑 {savedObjectType}",
"savedObjectsManagement.breadcrumb.index": "已保存对象",
"savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "删除",
"savedObjectsManagement.deleteConfirm.modalDescription": "此操作会将对象从 Kibana 永久移除。",
"savedObjectsManagement.deleteConfirm.modalTitle": "删除“{title}”?",
"savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "此操作将删除以下已保存对象:",
"savedObjectsManagement.field.offLabel": "关闭",
"savedObjectsManagement.field.onLabel": "开启",
"savedObjectsManagement.importSummary.createdCountHeader": "{createdCount} 个新",
"savedObjectsManagement.importSummary.createdOutcomeLabel": "已创建",
"savedObjectsManagement.importSummary.errorCountHeader": "{errorCount} 个错误",
@ -4523,21 +4520,10 @@
"savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "找不到已保存对象",
"savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "找不到已保存对象",
"savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "找不到已保存对象",
"savedObjectsManagement.view.cancelButtonAriaLabel": "取消",
"savedObjectsManagement.view.cancelButtonLabel": "取消",
"savedObjectsManagement.view.deleteItemButtonLabel": "删除“{title}”",
"savedObjectsManagement.view.editItemTitle": "编辑“{title}”",
"savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "与此对象关联的字段在该索引模式中已不存在。",
"savedObjectsManagement.view.howToFixErrorDescription": "如果您清楚此错误的含义,请修复该错误 — 否则单击上面的删除按钮。",
"savedObjectsManagement.view.howToModifyObjectDescription": "修改对象仅适用于高级用户。对象属性未得到验证,无效的对象可能会导致错误、数据丢失或更坏的情况发生。除非熟悉该代码的人让您来这里,否则您可能不应到访此处。",
"savedObjectsManagement.view.howToModifyObjectTitle": "谨慎操作!",
"savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "与此对象关联的索引模式已不存在。",
"savedObjectsManagement.view.saveButtonAriaLabel": "保存 { title } 对象",
"savedObjectsManagement.view.saveButtonLabel": "保存 { title } 对象",
"savedObjectsManagement.view.savedObjectProblemErrorMessage": "此已保存对象有问题",
"savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "与此对象关联的已保存搜索已不存在。",
"savedObjectsManagement.view.viewItemButtonLabel": "查看“{title}”",
"savedObjectsManagement.view.viewItemTitle": "查看“{title}”",
"security.checkup.dismissButtonText": "关闭",
"security.checkup.dontShowAgain": "不再显示",
"security.checkup.insecureClusterMessage": "不要丢失一位。使用 Elastic免费保护您的数据。",

View file

@ -14,6 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'settings', 'security', 'error', 'savedObjects']);
const kibanaServer = getService('kibanaServer');
let version: string = '';
const find = getService('find');
describe('feature controls saved objects management', () => {
before(async () => {
@ -108,12 +109,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(actual).to.be(true);
});
});
describe('edit visualization', () => {
// From https://github.com/elastic/kibana/issues/59588 edit view became read-only json view
// test description changed from "edit" to "inspect"
// Skipping the test to allow code owners to delete or modify the test.
describe('inspect visualization', () => {
before(async () => {
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed',
{
shouldLoginIfPrompted: false,
shouldUseHashForSubUrl: false,
@ -125,11 +128,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await testSubjects.existOrFail('savedObjectEditDelete');
});
it('shows save button', async () => {
// no longer a feature
it.skip('shows save button', async () => {
await testSubjects.existOrFail('savedObjectEditSave');
});
it('has inputs without readonly attributes', async () => {
// no longer a feature
it.skip('has inputs without readonly attributes', async () => {
const form = await testSubjects.find('savedObjectEditForm');
const inputs = await form.findAllByCssSelector('input');
expect(inputs.length).to.be.greaterThan(0);
@ -223,17 +228,30 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
describe('edit visualization', () => {
// From https://github.com/elastic/kibana/issues/59588 edit view became read-only json view
// test description changed from "edit" to "inspect"
// Skipping the test to allow code owners to delete or modify the test.
describe('inspect visualization', () => {
before(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
const objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('A Pie')).to.be(true);
await PageObjects.savedObjects.clickInspectByTitle('A Pie');
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed',
{
shouldLoginIfPrompted: false,
shouldUseHashForSubUrl: false,
}
);
await testSubjects.existOrFail('savedObjectsEdit');
});
it('allows viewing the object', async () => {
const inspectContainer = await find.byClassName('kibanaCodeEditor');
const visibleContainerText = await inspectContainer.getVisibleText();
expect(visibleContainerText.includes('A Pie'));
});
it('does not show delete button', async () => {
@ -244,7 +262,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await testSubjects.missingOrFail('savedObjectEditSave');
});
it('has inputs with only readonly attributes', async () => {
// No longer a feature
it.skip('has inputs with only readonly attributes', async () => {
const form = await testSubjects.find('savedObjectEditForm');
const inputs = await form.findAllByCssSelector('input');
expect(inputs.length).to.be.greaterThan(0);
@ -309,11 +328,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
describe('edit visualization', () => {
describe('inspect visualization', () => {
it('redirects to management home', async () => {
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
'kibana/objects/visualization/75c3e060-1e7c-11e9-8488-65449e65d0ed',
{
shouldLoginIfPrompted: false,
ensureCurrentUrl: false,

View file

@ -14,7 +14,6 @@ const getSpacePrefix = (spaceId: string) => {
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects([
'common',
'security',
@ -22,9 +21,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'spaceSelector',
'settings',
]);
const find = getService('find');
const spaceId = 'space_1';
const textIncludesAll = (text: string, items: string[]) => {
const bools = items.map((item) => !!text.includes(item));
return bools.every((currBool) => currBool === true);
};
describe('spaces integration', () => {
before(async () => {
await esArchiver.load(
@ -54,9 +59,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await PageObjects.common.waitUntilUrlIncludes(getSpacePrefix(spaceId));
expect(await testSubjects.getAttribute(`savedObjects-editField-title`, 'value')).to.eql(
'A Pie'
);
const inspectContainer = await find.byClassName('kibanaCodeEditor');
const visibleContainerText = await inspectContainer.getVisibleText();
expect(
textIncludesAll(visibleContainerText, [
'A Pie',
'title',
'id',
'type',
'attributes',
'references',
])
).to.be(true);
expect(visibleContainerText.includes('A Pie'));
});
});
}