[Maps] use index_exists route instead of /api/index_management/indices (#98479)
* [Maps] use index_exists route instead of /api/index_management/indices * fix functional test * add retry and correct permissions to fix functional tests * fix upload functional test
This commit is contained in:
parent
9c469feb3b
commit
40fddce405
|
@ -92,13 +92,17 @@ export async function checkIndexExists(
|
|||
): Promise<boolean> {
|
||||
const body = JSON.stringify({ index });
|
||||
const fileUploadModules = await lazyLoadModules();
|
||||
const { exists } = await fileUploadModules.getHttp().fetch<{ exists: boolean }>({
|
||||
path: `/internal/file_upload/index_exists`,
|
||||
method: 'POST',
|
||||
body,
|
||||
query: params,
|
||||
});
|
||||
return exists;
|
||||
try {
|
||||
const { exists } = await fileUploadModules.getHttp().fetch<{ exists: boolean }>({
|
||||
path: `/internal/file_upload/index_exists`,
|
||||
method: 'POST',
|
||||
body,
|
||||
query: params,
|
||||
});
|
||||
return exists;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTimeFieldRange(index: string, query: unknown, timeFieldName?: string) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { GeoJsonFilePicker, OnFileSelectParameters } from './geojson_file_picker';
|
||||
import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/public';
|
||||
import { IndexNameForm } from './index_name_form';
|
||||
import { validateIndexName } from '../../util/indexing_service';
|
||||
import { validateIndexName } from '../../validate_index_name';
|
||||
|
||||
const GEO_FIELD_TYPE_OPTIONS = [
|
||||
{
|
||||
|
@ -32,6 +32,8 @@ interface Props {
|
|||
onFileSelect: (onFileSelectParameters: OnFileSelectParameters) => void;
|
||||
onGeoFieldTypeSelect: (geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE) => void;
|
||||
onIndexNameChange: (name: string, error?: string) => void;
|
||||
onIndexNameValidationStart: () => void;
|
||||
onIndexNameValidationEnd: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -40,11 +42,20 @@ interface State {
|
|||
}
|
||||
|
||||
export class GeoJsonUploadForm extends Component<Props, State> {
|
||||
private _isMounted = false;
|
||||
state: State = {
|
||||
hasFile: false,
|
||||
isPointsOnly: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
_onFileSelect = async (onFileSelectParameters: OnFileSelectParameters) => {
|
||||
this.setState({
|
||||
hasFile: true,
|
||||
|
@ -53,7 +64,12 @@ export class GeoJsonUploadForm extends Component<Props, State> {
|
|||
|
||||
this.props.onFileSelect(onFileSelectParameters);
|
||||
|
||||
this.props.onIndexNameValidationStart();
|
||||
const indexNameError = await validateIndexName(onFileSelectParameters.indexName);
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
this.props.onIndexNameValidationEnd();
|
||||
this.props.onIndexNameChange(onFileSelectParameters.indexName, indexNameError);
|
||||
|
||||
const geoFieldType =
|
||||
|
@ -107,6 +123,8 @@ export class GeoJsonUploadForm extends Component<Props, State> {
|
|||
indexName={this.props.indexName}
|
||||
indexNameError={this.props.indexNameError}
|
||||
onIndexNameChange={this.props.onIndexNameChange}
|
||||
onIndexNameValidationStart={this.props.onIndexNameValidationStart}
|
||||
onIndexNameValidationEnd={this.props.onIndexNameValidationEnd}
|
||||
/>
|
||||
) : null}
|
||||
</EuiForm>
|
||||
|
|
|
@ -5,24 +5,47 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { ChangeEvent, Component } from 'react';
|
||||
import { EuiFormRow, EuiFieldText, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { validateIndexName } from '../../util/indexing_service';
|
||||
import { validateIndexName } from '../../validate_index_name';
|
||||
|
||||
export interface Props {
|
||||
indexName: string;
|
||||
indexNameError?: string;
|
||||
onIndexNameChange: (name: string, error?: string) => void;
|
||||
onIndexNameValidationStart: () => void;
|
||||
onIndexNameValidationEnd: () => void;
|
||||
}
|
||||
|
||||
export class IndexNameForm extends Component<Props> {
|
||||
_onIndexNameChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
private _isMounted = false;
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
_onIndexNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const indexName = event.target.value;
|
||||
const indexNameError = await validateIndexName(indexName);
|
||||
this.props.onIndexNameChange(indexName, indexNameError);
|
||||
this.props.onIndexNameChange(indexName);
|
||||
this._validateIndexName(indexName);
|
||||
this.props.onIndexNameValidationStart();
|
||||
};
|
||||
|
||||
_validateIndexName = _.debounce(async (indexName: string) => {
|
||||
const indexNameError = await validateIndexName(indexName);
|
||||
if (!this._isMounted || indexName !== this.props.indexName) {
|
||||
return;
|
||||
}
|
||||
this.props.onIndexNameValidationEnd();
|
||||
this.props.onIndexNameChange(indexName, indexNameError);
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
const errors = [...(this.props.indexNameError ? [this.props.indexNameError] : [])];
|
||||
|
||||
|
|
|
@ -274,7 +274,11 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
});
|
||||
|
||||
const isReadyToImport = !!name && error === undefined;
|
||||
this.props.onIndexReady(isReadyToImport);
|
||||
if (isReadyToImport) {
|
||||
this.props.enableImportBtn();
|
||||
} else {
|
||||
this.props.disableImportBtn();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -309,6 +313,8 @@ export class JsonUploadAndParse extends Component<FileUploadComponentProps, Stat
|
|||
onFileSelect={this._onFileSelect}
|
||||
onGeoFieldTypeSelect={this._onGeoFieldTypeSelect}
|
||||
onIndexNameChange={this._onIndexNameChange}
|
||||
onIndexNameValidationStart={this.props.disableImportBtn}
|
||||
onIndexNameValidationEnd={this.props.enableImportBtn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ export interface FileUploadComponentProps {
|
|||
isIndexingTriggered: boolean;
|
||||
onFileSelect: (geojsonFile: FeatureCollection, name: string, previewCoverage: number) => void;
|
||||
onFileClear: () => void;
|
||||
onIndexReady: (indexReady: boolean) => void;
|
||||
enableImportBtn: () => void;
|
||||
disableImportBtn: () => void;
|
||||
onUploadComplete: (results: FileUploadGeoResults) => void;
|
||||
onUploadError: () => void;
|
||||
}
|
||||
|
|
|
@ -1,73 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getIndexPatternService, getHttp } from '../kibana_services';
|
||||
|
||||
export const getExistingIndexNames = _.debounce(
|
||||
async () => {
|
||||
let indexes;
|
||||
try {
|
||||
indexes = await getHttp().fetch({
|
||||
path: `/api/index_management/indices`,
|
||||
method: 'GET',
|
||||
});
|
||||
} catch (e) {
|
||||
// Log to console. Further diagnostics can be made in network request
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
return indexes ? indexes.map(({ name }: { name: string }) => name) : [];
|
||||
},
|
||||
10000,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
export function checkIndexPatternValid(name: string) {
|
||||
const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
|
||||
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
|
||||
const indexPatternInvalid =
|
||||
byteLength > 255 || // name can't be greater than 255 bytes
|
||||
name !== name.toLowerCase() || // name should be lowercase
|
||||
name === '.' ||
|
||||
name === '..' || // name can't be . or ..
|
||||
name.match(/^[-_+]/) !== null || // name can't start with these chars
|
||||
name.match(reg) !== null; // name can't contain these chars
|
||||
return !indexPatternInvalid;
|
||||
}
|
||||
|
||||
export const validateIndexName = async (indexName: string) => {
|
||||
if (!checkIndexPatternValid(indexName)) {
|
||||
return i18n.translate(
|
||||
'xpack.fileUpload.util.indexingService.indexNameContainsIllegalCharactersErrorMessage',
|
||||
{
|
||||
defaultMessage: 'Index name contains illegal characters.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const indexNames = await getExistingIndexNames();
|
||||
const indexPatternNames = await getIndexPatternService().getTitles();
|
||||
let indexNameError;
|
||||
if (indexNames.includes(indexName)) {
|
||||
indexNameError = i18n.translate(
|
||||
'xpack.fileUpload.util.indexingService.indexNameAlreadyExistsErrorMessage',
|
||||
{
|
||||
defaultMessage: 'Index name already exists.',
|
||||
}
|
||||
);
|
||||
} else if (indexPatternNames.includes(indexName)) {
|
||||
indexNameError = i18n.translate(
|
||||
'xpack.fileUpload.util.indexingService.indexPatternAlreadyExistsErrorMessage',
|
||||
{
|
||||
defaultMessage: 'Index pattern already exists.',
|
||||
}
|
||||
);
|
||||
}
|
||||
return indexNameError;
|
||||
};
|
|
@ -5,12 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// Not all index pattern dependencies are avab. in jest context,
|
||||
// prevent unrelated import errors by mocking kibana services
|
||||
jest.mock('../kibana_services', () => {});
|
||||
import { checkIndexPatternValid } from './indexing_service';
|
||||
jest.mock('./kibana_services', () => {});
|
||||
import { checkIndexPatternValid } from './validate_index_name';
|
||||
|
||||
describe('indexing_service', () => {
|
||||
describe('checkIndexPatternValid', () => {
|
||||
const validNames = [
|
||||
'lowercaseletters', // Lowercase only
|
||||
'123', // Cannot include \, /, *, ?, ", <, >, |, " " (space character), , (comma), #
|
45
x-pack/plugins/file_upload/public/validate_index_name.ts
Normal file
45
x-pack/plugins/file_upload/public/validate_index_name.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getIndexPatternService } from './kibana_services';
|
||||
import { checkIndexExists } from './api';
|
||||
|
||||
export function checkIndexPatternValid(name: string) {
|
||||
const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
|
||||
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
|
||||
const indexPatternInvalid =
|
||||
byteLength > 255 || // name can't be greater than 255 bytes
|
||||
name !== name.toLowerCase() || // name should be lowercase
|
||||
name === '.' ||
|
||||
name === '..' || // name can't be . or ..
|
||||
name.match(/^[-_+]/) !== null || // name can't start with these chars
|
||||
name.match(reg) !== null; // name can't contain these chars
|
||||
return !indexPatternInvalid;
|
||||
}
|
||||
|
||||
export const validateIndexName = async (indexName: string) => {
|
||||
if (!checkIndexPatternValid(indexName)) {
|
||||
return i18n.translate('xpack.fileUpload.indexNameContainsIllegalCharactersErrorMessage', {
|
||||
defaultMessage: 'Index name contains illegal characters.',
|
||||
});
|
||||
}
|
||||
|
||||
const indexPatternNames = await getIndexPatternService().getTitles();
|
||||
if (indexPatternNames.includes(indexName)) {
|
||||
return i18n.translate('xpack.fileUpload.indexPatternAlreadyExistsErrorMessage', {
|
||||
defaultMessage: 'Index pattern already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
const indexExists = await checkIndexExists(indexName);
|
||||
if (indexExists) {
|
||||
return i18n.translate('xpack.fileUpload.indexNameAlreadyExistsErrorMessage', {
|
||||
defaultMessage: 'Index name already exists.',
|
||||
});
|
||||
}
|
||||
};
|
|
@ -195,7 +195,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup<StartDeps, unknown>, logge
|
|||
body: schema.object({ index: schema.string() }),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:fileUpload:analyzeFile'],
|
||||
tags: ['access:fileUpload:import'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
|
|
|
@ -26,14 +26,14 @@ export enum UPLOAD_STEPS {
|
|||
}
|
||||
|
||||
enum INDEXING_STAGE {
|
||||
READY = 'READY',
|
||||
CONFIGURE = 'CONFIGURE',
|
||||
TRIGGERED = 'TRIGGERED',
|
||||
SUCCESS = 'SUCCESS',
|
||||
ERROR = 'ERROR',
|
||||
}
|
||||
|
||||
interface State {
|
||||
indexingStage: INDEXING_STAGE | null;
|
||||
indexingStage: INDEXING_STAGE;
|
||||
fileUploadComponent: React.ComponentType<FileUploadComponentProps> | null;
|
||||
results?: FileUploadGeoResults;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
|
|||
private _isMounted: boolean = false;
|
||||
|
||||
state: State = {
|
||||
indexingStage: null,
|
||||
indexingStage: INDEXING_STAGE.CONFIGURE,
|
||||
fileUploadComponent: null,
|
||||
};
|
||||
|
||||
|
@ -58,7 +58,7 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
|
|||
componentDidUpdate() {
|
||||
if (
|
||||
this.props.currentStepId === UPLOAD_STEPS.UPLOAD &&
|
||||
this.state.indexingStage === INDEXING_STAGE.READY
|
||||
this.state.indexingStage === INDEXING_STAGE.CONFIGURE
|
||||
) {
|
||||
this.setState({ indexingStage: INDEXING_STAGE.TRIGGERED });
|
||||
this.props.startStepLoading();
|
||||
|
@ -156,19 +156,6 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
|
|||
this.setState({ indexingStage: INDEXING_STAGE.ERROR });
|
||||
};
|
||||
|
||||
// Called on file upload screen when UI state changes
|
||||
_onIndexReady = (indexReady: boolean) => {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
this.setState({ indexingStage: indexReady ? INDEXING_STAGE.READY : null });
|
||||
if (indexReady) {
|
||||
this.props.enableNextBtn();
|
||||
} else {
|
||||
this.props.disableNextBtn();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.fileUploadComponent) {
|
||||
return null;
|
||||
|
@ -181,7 +168,8 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
|
|||
isIndexingTriggered={this.state.indexingStage === INDEXING_STAGE.TRIGGERED}
|
||||
onFileSelect={this._onFileSelect}
|
||||
onFileClear={this._onFileClear}
|
||||
onIndexReady={this._onIndexReady}
|
||||
enableImportBtn={this.props.enableNextBtn}
|
||||
disableImportBtn={this.props.disableNextBtn}
|
||||
onUploadComplete={this._onUploadComplete}
|
||||
onUploadError={this._onUploadError}
|
||||
/>
|
||||
|
|
|
@ -14,10 +14,15 @@ export default function ({ getPageObjects, getService }) {
|
|||
const FILE_LOAD_DIR = 'test_upload_files';
|
||||
const DEFAULT_LOAD_FILE_NAME = 'point.json';
|
||||
const security = getService('security');
|
||||
const retry = getService('retry');
|
||||
|
||||
describe('GeoJSON import layer panel', () => {
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['global_maps_all', 'global_index_pattern_management_all']);
|
||||
await security.testUser.setRoles([
|
||||
'global_maps_all',
|
||||
'geoall_data_writer',
|
||||
'global_index_pattern_management_all',
|
||||
]);
|
||||
await PageObjects.maps.openNewMap();
|
||||
});
|
||||
|
||||
|
@ -87,23 +92,23 @@ export default function ({ getPageObjects, getService }) {
|
|||
});
|
||||
|
||||
it('should prevent import button from activating unless valid index name provided', async () => {
|
||||
// Set index to invalid name
|
||||
await PageObjects.maps.setIndexName('NoCapitalLetters');
|
||||
// Check button
|
||||
let importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(false);
|
||||
await retry.try(async () => {
|
||||
const importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(false);
|
||||
});
|
||||
|
||||
// Set index to valid name
|
||||
await PageObjects.maps.setIndexName('validindexname');
|
||||
// Check button
|
||||
importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(true);
|
||||
await retry.try(async () => {
|
||||
const importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(true);
|
||||
});
|
||||
|
||||
// Set index back to invalid name
|
||||
await PageObjects.maps.setIndexName('?noquestionmarks?');
|
||||
// Check button
|
||||
importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(false);
|
||||
await retry.try(async () => {
|
||||
const importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
const log = getService('log');
|
||||
const security = getService('security');
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
|
||||
const IMPORT_FILE_PREVIEW_NAME = 'Import File';
|
||||
const FILE_LOAD_DIR = 'test_upload_files';
|
||||
|
@ -32,6 +33,10 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
const indexName = uuid();
|
||||
await PageObjects.maps.setIndexName(indexName);
|
||||
await retry.try(async () => {
|
||||
const importButtonActive = await PageObjects.maps.importFileButtonEnabled();
|
||||
expect(importButtonActive).to.be(true);
|
||||
});
|
||||
await PageObjects.maps.clickImportFileButton();
|
||||
return indexName;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue