mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[file upload] document file upload privileges and provide actionable UI when failures occur (#95883)
* [file upload] document file upload privileges and provide actionable UI when failures occur * doc link * call hasImportPermission * docs tweeks * tslint * Update docs/maps/import-geospatial-data.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/maps/import-geospatial-data.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/maps/import-geospatial-data.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update docs/maps/import-geospatial-data.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * review feedback * fix bullet list format * clean-up i18n ids * Update docs/maps/import-geospatial-data.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * documenation review feedback * add period to last privilege bullet item Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e457f212c4
commit
2eae0969cb
8 changed files with 155 additions and 45 deletions
|
@ -6,6 +6,30 @@ To import geospatical data into the Elastic Stack, the data must be indexed as {
|
|||
Geospatial data comes in many formats.
|
||||
Choose an import tool based on the format of your geospatial data.
|
||||
|
||||
[discrete]
|
||||
[[import-geospatial-privileges]]
|
||||
=== Security privileges
|
||||
|
||||
The {stack-security-features} provide roles and privileges that control which users can upload files.
|
||||
You can manage your roles, privileges, and
|
||||
spaces in **{stack-manage-app}** in {kib}. For more information, see
|
||||
{ref}/security-privileges.html[Security privileges],
|
||||
<<kibana-privileges, {kib} privileges>>, and <<xpack-kibana-role-management, {kib} role management>>.
|
||||
|
||||
To upload GeoJSON files in {kib} with *Maps*, you must have:
|
||||
|
||||
* The `all` {kib} privilege for *Maps*.
|
||||
* The `all` {kib} privilege for *Index Pattern Management*.
|
||||
* The `create` and `create_index` index privileges for destination indices.
|
||||
* To use the index in *Maps*, you must also have the `read` and `view_index_metadata` index privileges for destination indices.
|
||||
|
||||
To upload CSV files in {kib} with the *{file-data-viz}*, you must have privileges to upload GeoJSON files and:
|
||||
|
||||
* The `manage_pipeline` cluster privilege.
|
||||
* The `read` {kib} privilege for *Machine Learning*.
|
||||
* The `machine_learning_admin` or `machine_learning_user` role.
|
||||
|
||||
|
||||
[discrete]
|
||||
=== Upload CSV with latitude and longitude columns
|
||||
|
||||
|
|
|
@ -216,6 +216,7 @@ export class DocLinksService {
|
|||
},
|
||||
maps: {
|
||||
guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`,
|
||||
importGeospatialPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/import-geospatial-data.html#import-geospatial-privileges`,
|
||||
},
|
||||
monitoring: {
|
||||
alertsKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html`,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { ES_FIELD_TYPES } from '../../../../src/plugins/data/common';
|
||||
|
||||
export interface HasImportPermission {
|
||||
|
@ -83,7 +84,9 @@ export interface ImportResponse {
|
|||
pipelineId?: string;
|
||||
docCount: number;
|
||||
failures: ImportFailure[];
|
||||
error?: any;
|
||||
error?: {
|
||||
error: estypes.ErrorCause;
|
||||
};
|
||||
ingestError?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,19 +7,20 @@
|
|||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { CodeEditor, KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { getHttp, getUiSettings } from '../kibana_services';
|
||||
import { getDocLinks, getHttp, getUiSettings } from '../kibana_services';
|
||||
import { ImportResults } from '../importer';
|
||||
|
||||
const services = {
|
||||
|
@ -27,8 +28,10 @@ const services = {
|
|||
};
|
||||
|
||||
interface Props {
|
||||
failedPermissionCheck: boolean;
|
||||
importResults?: ImportResults;
|
||||
indexPatternResp?: object;
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export class ImportCompleteView extends Component<Props, {}> {
|
||||
|
@ -57,9 +60,12 @@ export class ImportCompleteView extends Component<Props, {}> {
|
|||
iconType="copy"
|
||||
color="text"
|
||||
data-test-subj={copyButtonDataTestSubj}
|
||||
aria-label={i18n.translate('xpack.fileUpload.copyButtonAriaLabel', {
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
})}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.fileUpload.importComplete.copyButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
|
@ -90,21 +96,65 @@ export class ImportCompleteView extends Component<Props, {}> {
|
|||
}
|
||||
|
||||
_getStatusMsg() {
|
||||
if (!this.props.importResults || !this.props.importResults.success) {
|
||||
return i18n.translate('xpack.fileUpload.uploadFailureMsg', {
|
||||
defaultMessage: 'File upload failed.',
|
||||
});
|
||||
if (this.props.failedPermissionCheck) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.fileUpload.importComplete.uploadFailureTitle', {
|
||||
defaultMessage: 'Unable to upload file',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.fileUpload.importComplete.permissionFailureMsg', {
|
||||
defaultMessage:
|
||||
'You do not have permission to create or import data into index "{indexName}".',
|
||||
values: { indexName: this.props.indexName },
|
||||
})}
|
||||
</p>
|
||||
<EuiLink
|
||||
href={getDocLinks().links.maps.importGeospatialPrivileges}
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
{i18n.translate('xpack.fileUpload.importComplete.permission.docLink', {
|
||||
defaultMessage: 'View file import permissions',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
const successMsg = i18n.translate('xpack.fileUpload.uploadSuccessMsg', {
|
||||
defaultMessage: 'File upload complete: indexed {numFeatures} features.',
|
||||
if (!this.props.importResults || !this.props.importResults.success) {
|
||||
const errorMsg =
|
||||
this.props.importResults && this.props.importResults.error
|
||||
? i18n.translate('xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock', {
|
||||
defaultMessage: 'Error: {reason}',
|
||||
values: { reason: this.props.importResults.error.error.reason },
|
||||
})
|
||||
: '';
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.fileUpload.importComplete.uploadFailureTitle', {
|
||||
defaultMessage: 'Unable to upload file',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>{errorMsg}</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
const successMsg = i18n.translate('xpack.fileUpload.importComplete.uploadSuccessMsg', {
|
||||
defaultMessage: 'Indexed {numFeatures} features.',
|
||||
values: {
|
||||
numFeatures: this.props.importResults.docCount,
|
||||
},
|
||||
});
|
||||
|
||||
const failedFeaturesMsg = this.props.importResults.failures?.length
|
||||
? i18n.translate('xpack.fileUpload.failedFeaturesMsg', {
|
||||
? i18n.translate('xpack.fileUpload.importComplete.failedFeaturesMsg', {
|
||||
defaultMessage: 'Unable to index {numFailures} features.',
|
||||
values: {
|
||||
numFailures: this.props.importResults.failures.length,
|
||||
|
@ -112,47 +162,60 @@ export class ImportCompleteView extends Component<Props, {}> {
|
|||
})
|
||||
: '';
|
||||
|
||||
return `${successMsg} ${failedFeaturesMsg}`;
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.fileUpload.importComplete.uploadSuccessTitle', {
|
||||
defaultMessage: 'File upload complete',
|
||||
})}
|
||||
>
|
||||
<p>{`${successMsg} ${failedFeaturesMsg}`}</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
_renderIndexManagementMsg() {
|
||||
return this.props.importResults && this.props.importResults.success ? (
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fileUpload.importComplete.indexModsMsg"
|
||||
defaultMessage="To modify the index, go to "
|
||||
/>
|
||||
<a
|
||||
data-test-subj="indexManagementNewIndexLink"
|
||||
target="_blank"
|
||||
href={getHttp().basePath.prepend('/app/management/kibana/indexPatterns')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fileUpload.importComplete.indexMgmtLink"
|
||||
defaultMessage="Index Management."
|
||||
/>
|
||||
</a>
|
||||
</p>
|
||||
</EuiText>
|
||||
) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<KibanaContextProvider services={services}>
|
||||
<EuiText>
|
||||
<p>{this._getStatusMsg()}</p>
|
||||
</EuiText>
|
||||
{this._getStatusMsg()}
|
||||
|
||||
{this._renderCodeEditor(
|
||||
this.props.importResults,
|
||||
i18n.translate('xpack.fileUpload.jsonImport.indexingResponse', {
|
||||
i18n.translate('xpack.fileUpload.importComplete.indexingResponse', {
|
||||
defaultMessage: 'Import response',
|
||||
}),
|
||||
'indexRespCopyButton'
|
||||
)}
|
||||
{this._renderCodeEditor(
|
||||
this.props.indexPatternResp,
|
||||
i18n.translate('xpack.fileUpload.jsonImport.indexPatternResponse', {
|
||||
i18n.translate('xpack.fileUpload.importComplete.indexPatternResponse', {
|
||||
defaultMessage: 'Index pattern response',
|
||||
}),
|
||||
'indexPatternRespCopyButton'
|
||||
)}
|
||||
<EuiCallOut>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.fileUpload.jsonImport.indexModsMsg"
|
||||
defaultMessage="Further index modifications can be made using "
|
||||
/>
|
||||
<a
|
||||
data-test-subj="indexManagementNewIndexLink"
|
||||
target="_blank"
|
||||
href={getHttp().basePath.prepend('/app/management/kibana/indexPatterns')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fileUpload.jsonImport.indexMgmtLink"
|
||||
defaultMessage="Index Management"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</EuiCallOut>
|
||||
{this._renderIndexManagementMsg()}
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { FileUploadComponentProps } from '../lazy_load_bundle';
|
|||
import { ImportResults } from '../importer';
|
||||
import { GeoJsonImporter } from '../importer/geojson_importer';
|
||||
import { Settings } from '../../common';
|
||||
import { hasImportPermission } from '../api';
|
||||
|
||||
enum PHASE {
|
||||
CONFIGURE = 'CONFIGURE',
|
||||
|
@ -31,6 +32,7 @@ function getWritingToIndexMsg(progress: number) {
|
|||
}
|
||||
|
||||
interface State {
|
||||
failedPermissionCheck: boolean;
|
||||
geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE;
|
||||
importStatus: string;
|
||||
importResults?: ImportResults;
|
||||
|
@ -45,6 +47,7 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
private _isMounted = false;
|
||||
|
||||
state: State = {
|
||||
failedPermissionCheck: false,
|
||||
geoFieldType: ES_FIELD_TYPES.GEO_SHAPE,
|
||||
importStatus: '',
|
||||
indexName: '',
|
||||
|
@ -74,6 +77,26 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// check permissions
|
||||
//
|
||||
const canImport = await hasImportPermission({
|
||||
checkCreateIndexPattern: true,
|
||||
checkHasManagePipeline: false,
|
||||
indexName: this.state.indexName,
|
||||
});
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
if (!canImport) {
|
||||
this.setState({
|
||||
phase: PHASE.COMPLETE,
|
||||
failedPermissionCheck: true,
|
||||
});
|
||||
this.props.onIndexingError();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// create index
|
||||
//
|
||||
|
@ -111,6 +134,7 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
if (initializeImportResp.index === undefined || initializeImportResp.id === undefined) {
|
||||
this.setState({
|
||||
phase: PHASE.COMPLETE,
|
||||
importResults: initializeImportResp,
|
||||
});
|
||||
this.props.onIndexingError();
|
||||
return;
|
||||
|
@ -255,6 +279,8 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
<ImportCompleteView
|
||||
importResults={this.state.importResults}
|
||||
indexPatternResp={this.state.indexPatternResp}
|
||||
indexName={this.state.indexName}
|
||||
failedPermissionCheck={this.state.failedPermissionCheck}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export function setStartServices(core: CoreStart, plugins: FileUploadStartDepend
|
|||
pluginsStart = plugins;
|
||||
}
|
||||
|
||||
export const getDocLinks = () => coreStart.docLinks;
|
||||
export const getIndexPatternService = () => pluginsStart.data.indexPatterns;
|
||||
export const getHttp = () => coreStart.http;
|
||||
export const getSavedObjectsClient = () => coreStart.savedObjects.client;
|
||||
|
|
|
@ -8169,10 +8169,6 @@
|
|||
"xpack.fileUpload.indexSettings.indexNameAlreadyExistsErrorMessage": "インデックス名またはパターンはすでに存在します。",
|
||||
"xpack.fileUpload.indexSettings.indexNameContainsIllegalCharactersErrorMessage": "インデックス名に許可されていない文字が含まれています。",
|
||||
"xpack.fileUpload.indexSettings.indexNameGuidelines": "インデックス名ガイドライン",
|
||||
"xpack.fileUpload.jsonImport.indexingResponse": "インデックス応答",
|
||||
"xpack.fileUpload.jsonImport.indexMgmtLink": "インデックス管理",
|
||||
"xpack.fileUpload.jsonImport.indexModsMsg": "次を使用すると、その他のインデックス修正を行うことができます。\n",
|
||||
"xpack.fileUpload.jsonImport.indexPatternResponse": "インデックスパターン応答",
|
||||
"xpack.fileUpload.jsonUploadAndParse.dataIndexingError": "データインデックスエラー",
|
||||
"xpack.fileUpload.jsonUploadAndParse.indexPatternError": "インデックスパターンエラー",
|
||||
"xpack.fleet.agentBulkActions.clearSelection": "選択した項目をクリア",
|
||||
|
|
|
@ -8242,10 +8242,6 @@
|
|||
"xpack.fileUpload.indexSettings.indexNameAlreadyExistsErrorMessage": "索引名称或模式已存在。",
|
||||
"xpack.fileUpload.indexSettings.indexNameContainsIllegalCharactersErrorMessage": "索引名称包含非法字符。",
|
||||
"xpack.fileUpload.indexSettings.indexNameGuidelines": "索引名称指引",
|
||||
"xpack.fileUpload.jsonImport.indexingResponse": "索引响应",
|
||||
"xpack.fileUpload.jsonImport.indexMgmtLink": "索引管理",
|
||||
"xpack.fileUpload.jsonImport.indexModsMsg": "要进一步做索引修改,可以使用\n",
|
||||
"xpack.fileUpload.jsonImport.indexPatternResponse": "索引模式响应",
|
||||
"xpack.fileUpload.jsonUploadAndParse.dataIndexingError": "数据索引错误",
|
||||
"xpack.fileUpload.jsonUploadAndParse.indexPatternError": "索引模式错误",
|
||||
"xpack.fleet.agentBulkActions.agentsSelected": "已选择 {count, plural, other {# 个代理}}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue