[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:
Nathan Reese 2021-04-05 13:39:15 -06:00 committed by GitHub
parent e457f212c4
commit 2eae0969cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 45 deletions

View file

@ -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

View file

@ -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`,

View file

@ -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;
}

View file

@ -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>
);
}

View file

@ -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}
/>
);
}

View file

@ -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;

View file

@ -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": "選択した項目をクリア",

View file

@ -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 {# 个代理}}",