diff --git a/.i18nrc.json b/.i18nrc.json index 2b15e1645865..5760f0b36bb0 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -4,6 +4,7 @@ "inputControl":"src/core_plugins/input_control_vis", "kbn": "src/core_plugins/kibana", "kbnVislibVisTypes": "src/core_plugins/kbn_vislib_vis_types", + "kbn.management.objects": "src/core_plugins/kibana/public/management", "markdownVis": "src/core_plugins/markdown_vis", "metricVis": "src/core_plugins/metric_vis", "statusPage": "src/core_plugins/status_page", diff --git a/src/core_plugins/kibana/public/management/sections/objects/_objects.js b/src/core_plugins/kibana/public/management/sections/objects/_objects.js index 06110912601d..f622a8cfd2e0 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/_objects.js +++ b/src/core_plugins/kibana/public/management/sections/objects/_objects.js @@ -28,6 +28,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { ObjectsTable } from './components/objects_table'; import { getInAppUrl } from './lib/get_in_app_url'; +import { I18nProvider } from '@kbn/i18n/react'; const REACT_OBJECTS_TABLE_DOM_ELEMENT_ID = 'reactSavedObjectsTable'; @@ -52,31 +53,33 @@ function updateObjectsTable($scope, $injector) { } render( - { - if (type === 'index-pattern' || type === 'indexPatterns') { - return kbnUrl.eval(`#/management/kibana/indices/${id}`); - } - const serviceName = typeToServiceName(type); - if (!serviceName) { - toastNotifications.addWarning(`Unknown saved object type: ${type}`); - return null; - } + + { + if (type === 'index-pattern' || type === 'indexPatterns') { + return kbnUrl.eval(`#/management/kibana/indices/${id}`); + } + const serviceName = typeToServiceName(type); + if (!serviceName) { + toastNotifications.addWarning(`Unknown saved object type: ${type}`); + return null; + } - return kbnUrl.eval(`#/management/kibana/objects/${serviceName}/${id}`); - }} - goInApp={(id, type) => { - kbnUrl.change(getInAppUrl(id, type)); - $scope.$apply(); - }} - />, + return kbnUrl.eval(`#/management/kibana/objects/${serviceName}/${id}`); + }} + goInApp={(id, type) => { + kbnUrl.change(getInAppUrl(id, type)); + $scope.$apply(); + }} + /> + , node, ); }); diff --git a/src/core_plugins/kibana/public/management/sections/objects/_view.html b/src/core_plugins/kibana/public/management/sections/objects/_view.html index 50c31e758691..11d8ebb2eaa8 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/_view.html +++ b/src/core_plugins/kibana/public/management/sections/objects/_view.html @@ -3,9 +3,12 @@
-

- Edit {{ title }} -

+

@@ -15,7 +18,11 @@ > - View {{ title }} + @@ -25,7 +32,11 @@ > - Delete {{ title }} +
@@ -39,36 +50,40 @@
- - There is a problem with this saved object - +
- The saved search associated with this object no longer exists. -
+ i18n-id="kbn.management.objects.view.savedSearchDoesNotExistErrorMessage" + i18n-default-message="The saved search associated with this object no longer exists." + >
- The index pattern associated with this object no longer exists. -
+ i18n-id="kbn.management.objects.view.indexPatternDoesNotExistErrorMessage" + i18n-default-message="The index pattern associated with this object no longer exists." + >
- A field associated with this object no longer exists in the index pattern. -
+ i18n-id="kbn.management.objects.view.fieldDoesNotExistErrorMessage" + i18n-default-message="A 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. -
+
@@ -78,14 +93,19 @@
- - Proceed with caution! - +
-
- 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’t be. +
@@ -143,20 +163,21 @@
+ i18n-id="kbn.management.objects.view.saveButtonLabel" + i18n-default-message="Save { title } Object" + i18n-values="{ title }" + > + i18n-id="kbn.management.objects.view.cancelButtonLabel" + i18n-default-message="Cancel" + >
diff --git a/src/core_plugins/kibana/public/management/sections/objects/_view.js b/src/core_plugins/kibana/public/management/sections/objects/_view.js index 9c4207398352..86b2e0d5599c 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/_view.js +++ b/src/core_plugins/kibana/public/management/sections/objects/_view.js @@ -38,7 +38,7 @@ uiRoutes }); uiModules.get('apps/management') - .directive('kbnManagementObjectsView', function (kbnIndex, confirmModal) { + .directive('kbnManagementObjectsView', function (kbnIndex, confirmModal, i18n) { return { restrict: 'E', controller: function ($scope, $injector, $routeParams, $location, $window, $rootScope, Private) { @@ -199,11 +199,17 @@ uiModules.get('apps/management') } const confirmModalOptions = { onConfirm: doDelete, - confirmButtonText: 'Delete', - title: 'Delete saved Kibana object?' + confirmButtonText: i18n('kbn.management.objects.confirmModalOptions.deleteButtonLabel', { + defaultMessage: 'Delete', + }), + title: i18n('kbn.management.objects.confirmModalOptions.modalTitle', { + defaultMessage: 'Delete saved Kibana object?' + }), }; confirmModal( - `You can't recover deleted objects`, + i18n('kbn.management.objects.confirmModalOptions.modalDescription', { + defaultMessage: 'You can\'t recover deleted objects', + }), confirmModalOptions ); }; diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/__tests__/__snapshots__/objects_table.test.js.snap b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/__tests__/__snapshots__/objects_table.test.js.snap index 36cfb413e146..d714e7506990 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/__tests__/__snapshots__/objects_table.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/__tests__/__snapshots__/objects_table.test.js.snap @@ -3,15 +3,37 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` + } + confirmButtonText={ + + } defaultFocusedButton="confirm" onCancel={[Function]} onConfirm={[Function]} - title="Delete saved objects" + title={ + + } >

- This action will delete the following saved objects: +

+ } + confirmButtonText={ + + } defaultFocusedButton="confirm" onCancel={[Function]} onConfirm={[Function]} - title="Export 4 objects" + title={ + + } >

- Select which types to export. The number in parentheses indicates how many of this type are available to export. +

- ({ Header: () => 'Header', @@ -157,8 +159,8 @@ describe('ObjectsTable', () => { }); it('should render normally', async () => { - const component = shallow( - @@ -194,8 +196,8 @@ describe('ObjectsTable', () => { const { retrieveAndExportDocs } = require('../../../lib/retrieve_and_export_docs'); - const component = shallow( - @@ -216,8 +218,8 @@ describe('ObjectsTable', () => { }); it('should allow the user to choose when exporting all', async () => { - const component = shallow( - ); @@ -236,8 +238,8 @@ describe('ObjectsTable', () => { it('should export all', async () => { const { scanAllTypes } = require('../../../lib/scan_all_types'); const { saveToFile } = require('../../../lib/save_to_file'); - const component = shallow( - ); @@ -259,8 +261,8 @@ describe('ObjectsTable', () => { describe('import', () => { it('should show the flyout', async () => { - const component = shallow( - ); @@ -273,12 +275,12 @@ describe('ObjectsTable', () => { component.instance().showImportFlyout(); component.update(); - expect(component.find('Flyout')).toMatchSnapshot(); + expect(component.find(Flyout)).toMatchSnapshot(); }); it('should hide the flyout', async () => { - const component = shallow( - ); @@ -291,7 +293,7 @@ describe('ObjectsTable', () => { component.instance().hideImportFlyout(); component.update(); - expect(component.find('Flyout').length).toBe(0); + expect(component.find(Flyout).length).toBe(0); }); }); @@ -299,8 +301,8 @@ describe('ObjectsTable', () => { it('should fetch relationships', async () => { const { getRelationships } = require('../../../lib/get_relationships'); - const component = shallow( - ); @@ -315,8 +317,8 @@ describe('ObjectsTable', () => { }); it('should show the flyout', async () => { - const component = shallow( - ); @@ -329,15 +331,15 @@ describe('ObjectsTable', () => { component.instance().onShowRelationships('1', 'search', 'MySearch'); component.update(); - expect(component.find('Relationships')).toMatchSnapshot(); + expect(component.find(Relationships)).toMatchSnapshot(); expect(component.state('relationshipId')).toBe('1'); expect(component.state('relationshipType')).toBe('search'); expect(component.state('relationshipTitle')).toBe('MySearch'); }); it('should hide the flyout', async () => { - const component = shallow( - ); @@ -350,7 +352,7 @@ describe('ObjectsTable', () => { component.instance().onHideRelationships(); component.update(); - expect(component.find('Relationships').length).toBe(0); + expect(component.find(Relationships).length).toBe(0); expect(component.state('relationshipId')).toBe(undefined); expect(component.state('relationshipType')).toBe(undefined); expect(component.state('relationshipTitle')).toBe(undefined); @@ -359,8 +361,8 @@ describe('ObjectsTable', () => { describe('delete', () => { it('should show a confirm modal', async () => { - const component = shallow( - ); @@ -403,8 +405,8 @@ describe('ObjectsTable', () => { delete: jest.fn(), }; - const component = shallow( - diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/__snapshots__/flyout.test.js.snap b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/__snapshots__/flyout.test.js.snap index 3464cbb2acba..7446150cb835 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/__snapshots__/flyout.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/__snapshots__/flyout.test.js.snap @@ -16,7 +16,11 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` size="m" >

- Import saved objects +

@@ -27,20 +31,34 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` color="warning" iconType="help" size="m" - title="Index Pattern Conflicts" + title={ + + } >

- The following saved objects use index patterns that do not exist. Please select the index patterns you'd like re-associated with them. You can - - - create a new index pattern - - - if necessary. + + + , + } + } + />

@@ -123,7 +141,11 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` size="s" type="button" > - Cancel + - Confirm all changes + @@ -153,7 +179,13 @@ exports[`Flyout conflicts should handle errors 1`] = ` color="danger" iconType="cross" size="m" - title="Sorry, there was an error" + title={ + + } >

foobar @@ -177,7 +209,11 @@ exports[`Flyout should render import step 1`] = ` size="m" >

- Import saved objects +

@@ -187,11 +223,23 @@ exports[`Flyout should render import step 1`] = ` describedByIds={Array []} fullWidth={false} hasEmptyLabelSpace={false} - label="Please select a JSON file to import" + label={ + + } > + } onChange={[Function]} /> @@ -203,7 +251,13 @@ exports[`Flyout should render import step 1`] = ` + } name="overwriteAll" onChange={[Function]} /> @@ -231,7 +285,11 @@ exports[`Flyout should render import step 1`] = ` size="s" type="button" > - Cancel + - Import + diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/flyout.test.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/flyout.test.js index 68b7d46b1816..1866b499e712 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/flyout.test.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__tests__/flyout.test.js @@ -18,7 +18,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Flyout } from '../flyout'; @@ -66,7 +66,7 @@ const mockFile = { describe('Flyout', () => { it('should render import step', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -77,7 +77,7 @@ describe('Flyout', () => { }); it('should toggle the overwrite all control', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -90,7 +90,7 @@ describe('Flyout', () => { }); it('should allow picking a file', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -104,7 +104,7 @@ describe('Flyout', () => { it('should handle invalid files', async () => { const { importFile } = require('../../../../../lib/import_file'); - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -183,7 +183,7 @@ describe('Flyout', () => { }); it('should figure out conflicts', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -226,7 +226,7 @@ describe('Flyout', () => { }); it('should allow conflict resolution', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); @@ -270,7 +270,7 @@ describe('Flyout', () => { }); it('should handle errors', async () => { - const component = shallow(); + const component = shallowWithIntl(); // Ensure all promises resolve await new Promise(resolve => process.nextTick(resolve)); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js index f70177c34103..6610b764589c 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js @@ -50,8 +50,9 @@ import { saveObjects, } from '../../../../lib/resolve_saved_objects'; import { INCLUDED_TYPES } from '../../objects_table'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -export class Flyout extends Component { +class FlyoutUI extends Component { static propTypes = { close: PropTypes.func.isRequired, done: PropTypes.func.isRequired, @@ -102,7 +103,7 @@ export class Flyout extends Component { }; import = async () => { - const { services, indexPatterns } = this.props; + const { services, indexPatterns, intl } = this.props; const { file, isOverwriteAllChecked } = this.state; this.setState({ isLoading: true, error: undefined }); @@ -114,7 +115,10 @@ export class Flyout extends Component { } catch (e) { this.setState({ isLoading: false, - error: 'The file could not be processed.', + error: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.importFileErrorMessage', + defaultMessage: 'The file could not be processed.', + }), }); return; } @@ -122,7 +126,10 @@ export class Flyout extends Component { if (!Array.isArray(contents)) { this.setState({ isLoading: false, - error: 'Saved objects file format is invalid and cannot be imported.', + error: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage', + defaultMessage: 'Saved objects file format is invalid and cannot be imported.', + }), }); return; } @@ -211,7 +218,7 @@ export class Flyout extends Component { failedImports } = this.state; - const { services, indexPatterns } = this.props; + const { services, indexPatterns, intl } = this.props; this.setState({ error: undefined, @@ -226,7 +233,12 @@ export class Flyout extends Component { const resolutions = this.resolutions; // Do not Promise.all these calls as the order matters - this.setState({ loadingMessage: 'Resolving conflicts...' }); + this.setState({ + loadingMessage: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.confirmImport.resolvingConflictsLoadingMessage', + defaultMessage: 'Resolving conflicts…', + }), + }); if (resolutions.length) { importCount += await resolveIndexPatternConflicts( resolutions, @@ -234,13 +246,21 @@ export class Flyout extends Component { isOverwriteAllChecked ); } - this.setState({ loadingMessage: 'Saving conflicts...' }); + this.setState({ + loadingMessage: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.confirmImport.savingConflictsLoadingMessage', + defaultMessage: 'Saving conflicts…', + }), + }); importCount += await saveObjects( conflictedSavedObjectsLinkedToSavedSearches, isOverwriteAllChecked ); this.setState({ - loadingMessage: 'Ensure saved searches are linked properly...', + loadingMessage: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.confirmImport.savedSearchAreLinkedProperlyLoadingMessage', + defaultMessage: 'Ensure saved searches are linked properly…', + }), }); importCount += await resolveSavedSearches( conflictedSearchDocs, @@ -249,7 +269,10 @@ export class Flyout extends Component { isOverwriteAllChecked ); this.setState({ - loadingMessage: 'Retrying failed objects...', + loadingMessage: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.confirmImport.retryingFailedObjectsLoadingMessage', + defaultMessage: 'Retrying failed objects…', + }), }); importCount += await saveObjects( failedImports.map(({ obj }) => obj), @@ -293,6 +316,7 @@ export class Flyout extends Component { renderConflicts() { const { conflicts } = this.state; + const { intl } = this.props; if (!conflicts) { return null; @@ -301,22 +325,40 @@ export class Flyout extends Component { const columns = [ { field: 'existingIndexPatternId', - name: 'ID', - description: `ID of the index pattern`, + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdName', + defaultMessage: 'ID', + }), + description: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdDescription', + defaultMessage: 'ID of the index pattern', + }), sortable: true, }, { field: 'list', - name: 'Count', - description: `How many affected objects`, + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountName', + defaultMessage: 'Count', + }), + description: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountDescription', + defaultMessage: 'How many affected objects', + }), render: list => { return {list.length}; }, }, { field: 'list', - name: 'Sample of affected objects', - description: `Sample of affected objects`, + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName', + defaultMessage: 'Sample of affected objects', + }), + description: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription', + defaultMessage: 'Sample of affected objects', + }), render: list => { return (
    @@ -327,7 +369,10 @@ export class Flyout extends Component { }, { field: 'existingIndexPatternId', - name: 'New index pattern', + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.flyout.renderConflicts.columnNewIndexPatternName', + defaultMessage: 'New index pattern', + }), render: id => { const options = this.state.indexPatterns.map(indexPattern => ({ text: indexPattern.get('title'), @@ -374,7 +419,9 @@ export class Flyout extends Component { return ( + )} color="danger" iconType="cross" > @@ -412,12 +459,18 @@ export class Flyout extends Component { if (failedImports.length && !this.hasConflicts) { return ( + )} color="warning" iconType="help" >

    - Failed to import {failedImports.length} of {importCount + failedImports.length} objects. +

    {failedImports.map(({ error }) => getField(error, 'body.message', error.message || '')).join(' ')} @@ -431,7 +484,12 @@ export class Flyout extends Component { return ( + )} color="primary" /> ); @@ -440,11 +498,22 @@ export class Flyout extends Component { return ( + )} color="success" iconType="check" > -

    Successfully imported {importCount} objects.

    +

    + +

    ); } @@ -455,16 +524,33 @@ export class Flyout extends Component { return ( - + + )} + > + )} onChange={this.setImportFile} /> + )} data-test-subj="importSavedObjectsOverwriteToggle" checked={isOverwriteAllChecked} onChange={this.changeOverwriteAll} @@ -488,7 +574,10 @@ export class Flyout extends Component { fill data-test-subj="importSavedObjectsDoneBtn" > - Done + ); } else if (this.hasConflicts) { @@ -500,7 +589,10 @@ export class Flyout extends Component { isLoading={isLoading} data-test-subj="importSavedObjectsConfirmBtn" > - Confirm all changes + ); } else { @@ -512,7 +604,10 @@ export class Flyout extends Component { isLoading={isLoading} data-test-subj="importSavedObjectsImportBtn" > - Import + ); } @@ -521,7 +616,10 @@ export class Flyout extends Component { - Cancel + {confirmButton} @@ -542,20 +640,32 @@ export class Flyout extends Component { + )} color="warning" iconType="help" >

    - The following saved objects use index patterns that do not exist. - Please select the index patterns you'd like re-associated with - them. You can{' '} - { - - create a new index pattern - - }{' '} - if necessary. + + + + ) + }} + />

    @@ -569,7 +679,12 @@ export class Flyout extends Component { -

    Import saved objects

    +

    + +

    {this.renderSubheader()}
    @@ -584,3 +699,5 @@ export class Flyout extends Component { ); } } + +export const Flyout = injectI18n(FlyoutUI); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__tests__/__snapshots__/header.test.js.snap b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__tests__/__snapshots__/header.test.js.snap index 8404ebc156f4..2bdebf8f047c 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__tests__/__snapshots__/header.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/__tests__/__snapshots__/header.test.js.snap @@ -19,7 +19,11 @@ exports[`Header should render normally 1`] = ` size="m" >

    - Saved Objects +

    @@ -49,10 +53,15 @@ exports[`Header should render normally 1`] = ` size="s" type="button" > - Export - 2 - - objects + - Import + - Refresh +
    @@ -100,7 +117,11 @@ exports[`Header should render normally 1`] = ` color="subdued" component="span" > - From here you can delete saved objects, such as saved searches. You can also edit the raw data of saved objects. Typically objects are only modified via their associated application, which is probably what you should use instead of this screen. +

    diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js index 2ac10addbd4a..a901af6413fe 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/header/header.js @@ -29,6 +29,7 @@ import { EuiTextColor, EuiButtonEmpty, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export const Header = ({ onExportAll, @@ -40,7 +41,12 @@ export const Header = ({ -

    Saved Objects

    +

    + +

    @@ -53,7 +59,13 @@ export const Header = ({ data-test-subj="exportAllObjects" onClick={onExportAll} > - Export {filteredCount} {filteredCount === 1 ? 'object' : 'objects'} + @@ -63,7 +75,10 @@ export const Header = ({ data-test-subj="importObjects" onClick={onImport} > - Import + @@ -72,7 +87,10 @@ export const Header = ({ iconType="refresh" onClick={onRefresh} > - Refresh +
    @@ -82,10 +100,13 @@ export const Header = ({

    - From here you can delete saved objects, such as saved searches. - You can also edit the raw data of saved objects. - Typically objects are only modified via their associated application, - which is probably what you should use instead of this screen. +

    diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/__snapshots__/relationships.test.js.snap b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/__snapshots__/relationships.test.js.snap index 961d5480a2e2..8e01148b7051 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/__snapshots__/relationships.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/__snapshots__/relationships.test.js.snap @@ -52,12 +52,20 @@ exports[`Relationships should render dashboards normally 1`] = ` + } >

    - Here are some visualizations used on this dashboard. You can - safely delete this dashboard and the visualizations will still - work properly. +

    @@ -142,7 +150,13 @@ exports[`Relationships should render errors 1`] = ` + } > foo @@ -242,10 +256,20 @@ exports[`Relationships should render searches normally 1`] = ` + } >

    - Here is the index pattern tied to this saved search. +

    @@ -299,12 +323,20 @@ exports[`Relationships should render searches normally 1`] = ` + } >

    - Here are some visualizations that use this saved search. If - you delete this saved search, these visualizations will not - longer work properly. +

    @@ -402,12 +434,20 @@ exports[`Relationships should render visualizations normally 1`] = ` + } >

    - Here are some dashboards which contain this visualization. If - you delete this visualization, these dashboards will no longer - show them. +

    diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/relationships.test.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/relationships.test.js index 6e1e591fa9a9..9eb269ea4644 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/relationships.test.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__tests__/relationships.test.js @@ -18,7 +18,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('ui/errors', () => ({ SavedObjectNotFound: class SavedObjectNotFound extends Error { @@ -62,8 +62,8 @@ describe('Relationships', () => { close: jest.fn(), }; - const component = shallow( - ); @@ -102,8 +102,8 @@ describe('Relationships', () => { close: jest.fn(), }; - const component = shallow( - ); @@ -140,8 +140,8 @@ describe('Relationships', () => { close: jest.fn(), }; - const component = shallow( - ); @@ -178,8 +178,8 @@ describe('Relationships', () => { close: jest.fn(), }; - const component = shallow( - ); @@ -209,8 +209,8 @@ describe('Relationships', () => { close: jest.fn(), }; - const component = shallow( - ); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js index 50b83de790c5..c73f77d2687c 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js @@ -34,9 +34,10 @@ import { EuiInMemoryTable, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { getSavedObjectIcon, getSavedObjectLabel } from '../../../../lib'; -export class Relationships extends Component { +class RelationshipsUI extends Component { static propTypes = { getRelationships: PropTypes.func.isRequired, id: PropTypes.string.isRequired, @@ -88,14 +89,19 @@ export class Relationships extends Component { } return ( - + + )} + color="danger" + > {error} ); } renderRelationships() { - const { getEditUrl, goInApp } = this.props; + const { getEditUrl, goInApp, intl } = this.props; const { relationships, isLoading, error } = this.state; if (error) { @@ -112,48 +118,78 @@ export class Relationships extends Component { if (list.length === 0) { items.push( - No {type} found. + ); } else { // let node; - let calloutTitle = 'Warning'; + let calloutTitle = (); let calloutColor = 'warning'; let calloutText; switch (this.props.type) { case 'dashboard': calloutColor = 'success'; - calloutTitle = 'Dashboard'; - calloutText = `Here are some visualizations used on this dashboard. You can - safely delete this dashboard and the visualizations will still - work properly.`; + calloutTitle = (); + calloutText = (); break; case 'search': if (type === 'visualizations') { - calloutText = `Here are some visualizations that use this saved search. If - you delete this saved search, these visualizations will not - longer work properly.`; + calloutText = (); } else { calloutColor = 'success'; - calloutTitle = 'Saved Search'; - calloutText = `Here is the index pattern tied to this saved search.`; + calloutTitle = (); + calloutText = (); } break; case 'visualization': - calloutText = `Here are some dashboards which contain this visualization. If - you delete this visualization, these dashboards will no longer - show them.`; + calloutText = (); break; case 'index-pattern': if (type === 'visualizations') { - calloutText = `Here are some visualizations that use this index pattern. If - you delete this index pattern, these visualizations will not - longer work properly.`; + calloutText = (); } else if (type === 'searches') { - calloutText = `Here are some saved searches that use this index pattern. If - you delete this index pattern, these saved searches will not - longer work properly.`; + calloutText = (); } break; } @@ -184,7 +220,9 @@ export class Relationships extends Component { ), }, { - name: 'Title', + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.relationships.columnTitleName', defaultMessage: 'Title' + }), field: 'title', render: (title, item) => ( @@ -193,11 +231,19 @@ export class Relationships extends Component { ), }, { - name: 'Actions', + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.relationships.columnActionsName', defaultMessage: 'Actions' + }), actions: [ { - name: 'In app', - description: 'View this saved object within Kibana', + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.relationships.columnActions.inAppName', + defaultMessage: 'In app' + }), + description: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.relationships.columnActions.inAppDescription', + defaultMessage: 'View this saved object within Kibana' + }), icon: 'eye', onClick: object => goInApp(object.id, type), }, @@ -240,3 +286,5 @@ export class Relationships extends Component { ); } } + +export const Relationships = injectI18n(RelationshipsUI); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/__snapshots__/table.test.js.snap b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/__snapshots__/table.test.js.snap index abd8ee272608..eecc496328f3 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/__snapshots__/table.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/__snapshots__/table.test.js.snap @@ -30,7 +30,11 @@ exports[`Table should render normally 1`] = ` onClick={[Function]} type="button" > - Delete + , - Export + , ] } diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/table.test.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/table.test.js index 7a6d9f829658..41470722a1b3 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/table.test.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__tests__/table.test.js @@ -18,7 +18,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('ui/errors', () => ({ SavedObjectNotFound: class SavedObjectNotFound extends Error { @@ -64,8 +64,8 @@ describe('Table', () => { onShowRelationships: () => {}, }; - const component = shallow( -
); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js index f9a5dab7fef9..0effa28d9c40 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js @@ -30,8 +30,9 @@ import { EuiToolTip } from '@elastic/eui'; import { getSavedObjectLabel, getSavedObjectIcon } from '../../../../lib'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -export class Table extends PureComponent { +class TableUI extends PureComponent { static propTypes = { selectedSavedObjects: PropTypes.array.isRequired, selectionConfig: PropTypes.shape({ @@ -78,6 +79,7 @@ export class Table extends PureComponent { goInApp, getEditUrl, onShowRelationships, + intl, } = this.props; const pagination = { @@ -91,7 +93,7 @@ export class Table extends PureComponent { { type: 'field_value_selection', field: 'type', - name: 'Type', + name: intl.formatMessage({ id: 'kbn.management.objects.objectsTable.table.typeFilterName', defaultMessage: 'Type' }), multiSelect: 'or', options: filterOptions, }, @@ -108,10 +110,13 @@ export class Table extends PureComponent { const columns = [ { field: 'type', - name: 'Type', + name: intl.formatMessage({ id: 'kbn.management.objects.objectsTable.table.columnTypeName', defaultMessage: 'Type' }), width: '50px', align: 'center', - description: `Type of the saved object`, + description: + intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnTypeDescription', defaultMessage: 'Type of the saved object' + }), sortable: false, render: type => { return ( @@ -130,8 +135,11 @@ export class Table extends PureComponent { }, { field: 'title', - name: 'Title', - description: `Title of the saved object`, + name: intl.formatMessage({ id: 'kbn.management.objects.objectsTable.table.columnTitleName', defaultMessage: 'Title' }), + description: + intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnTitleDescription', defaultMessage: 'Title of the saved object' + }), dataType: 'string', sortable: false, render: (title, object) => ( @@ -139,20 +147,32 @@ export class Table extends PureComponent { ), }, { - name: 'Actions', + name: intl.formatMessage({ id: 'kbn.management.objects.objectsTable.table.columnActionsName', defaultMessage: 'Actions' }), actions: [ { - name: 'In app', + name: intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnActions.viewInAppActionName', defaultMessage: 'In app' + }), description: - 'View this saved object within Kibana', + intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnActions.viewInAppActionDescription', + defaultMessage: 'View this saved object within Kibana' + }), type: 'icon', icon: 'eye', onClick: object => goInApp(object.id, object.type), }, { - name: 'Relationships', + name: + intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionName', + defaultMessage: 'Relationships' + }), description: - 'View the relationships this saved object has to other saved objects', + intl.formatMessage({ + id: 'kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionDescription', + defaultMessage: 'View the relationships this saved object has to other saved objects' + }), type: 'icon', icon: 'kqlSelector', onClick: object => @@ -175,7 +195,10 @@ export class Table extends PureComponent { onClick={onDelete} isDisabled={selectedSavedObjects.length === 0} > - Delete + , - Export + , ]} /> @@ -203,3 +229,5 @@ export class Table extends PureComponent { ); } } + +export const Table = injectI18n(TableUI); diff --git a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js index 27d77ca92028..c29f2f5d8613 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js +++ b/src/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js @@ -51,6 +51,7 @@ import { getSavedObjectLabel, } from '../../lib'; import { ensureMinimumTime } from '../../../indices/create_index_pattern_wizard/lib/ensure_minimum_time'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; export const INCLUDED_TYPES = [ 'index-pattern', @@ -59,7 +60,7 @@ export const INCLUDED_TYPES = [ 'search', ]; -export class ObjectsTable extends Component { +class ObjectsTableUI extends Component { static propTypes = { savedObjectsClient: PropTypes.object.isRequired, indexPatterns: PropTypes.object.isRequired, @@ -383,6 +384,7 @@ export class ObjectsTable extends Component { isDeleting, selectedSavedObjects, } = this.state; + const { intl } = this.props; if (!isShowingDeleteConfirmModal) { return null; @@ -406,20 +408,47 @@ export class ObjectsTable extends Component { modal = ( + } onCancel={onCancel} onConfirm={onConfirm} - cancelButtonText="Cancel" - confirmButtonText={isDeleting ? 'Deleting...' : 'Delete'} + cancelButtonText={( + + )} + confirmButtonText={ + isDeleting + ? () + : () + } defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} > -

This action will delete the following saved objects:

+

+ +

( )} onCancel={() => this.setState({ isShowingExportAllOptionsModal: false }) } onConfirm={this.onExportAll} - cancelButtonText="Cancel" - confirmButtonText="Export All" + cancelButtonText={( + + )} + confirmButtonText={( + + )} defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} >

- Select which types to export. The number in parentheses indicates - how many of this type are available to export. +

- + {this.renderFlyout()} {this.renderRelationships()} {this.renderDeleteConfirmModal()} @@ -560,3 +611,5 @@ export class ObjectsTable extends Component { ); } } + +export const ObjectsTable = injectI18n(ObjectsTableUI); diff --git a/src/core_plugins/kibana/public/management/sections/objects/index.js b/src/core_plugins/kibana/public/management/sections/objects/index.js index b5deddc00e31..6cb8ef88aae7 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/index.js +++ b/src/core_plugins/kibana/public/management/sections/objects/index.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { management } from 'ui/management'; import './_view'; import './_objects'; @@ -29,7 +30,9 @@ import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/r uiModules.get('apps/management'); management.getSection('kibana').register('objects', { - display: 'Saved Objects', + display: i18n.translate('kbn.management.objects.savedObjectsSectionLabel', { + defaultMessage: 'Saved Objects', + }), order: 10, url: '#/management/kibana/objects' }); @@ -37,8 +40,12 @@ management.getSection('kibana').register('objects', { FeatureCatalogueRegistryProvider.register(() => { return { id: 'saved_objects', - title: 'Saved Objects', - description: 'Import, export, and manage your saved searches, visualizations, and dashboards.', + title: i18n.translate('kbn.management.objects.savedObjectsTitle', { + defaultMessage: 'Saved Objects', + }), + description: i18n.translate('kbn.management.objects.savedObjectsDescription', { + defaultMessage: 'Import, export, and manage your saved searches, visualizations, and dashboards.', + }), icon: 'savedObjectsApp', path: '/app/kibana#/management/kibana/objects', showOnHomePage: true, diff --git a/src/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js b/src/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js index 145ea26da80a..24c59142e75f 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js +++ b/src/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.js @@ -18,6 +18,7 @@ */ import { SavedObjectNotFound } from 'ui/errors'; +import { i18n } from '@kbn/i18n'; async function getSavedObject(doc, services) { const service = services.find(service => service.type === doc._type); @@ -36,7 +37,16 @@ function addJsonFieldToIndexPattern(target, sourceString, fieldName, indexName) try { target[fieldName] = JSON.parse(sourceString); } catch (error) { - throw new Error(`Error encountered parsing ${fieldName} for index pattern ${indexName}: ${error.message}`); + throw new Error( + i18n.translate('kbn.management.objects.parsingFieldErrorMessage', { + defaultMessage: 'Error encountered parsing {fieldName} for index pattern {indexName}: {errorMessage}', + values: { + fieldName, + indexName, + errorMessage: error.message, + } + }), + ); } } }