[Maps][File upload] File upload functional tests (#40191)

* Add data-test-subj where needed

* Add helper functions for geojson import tests

* Add test geojson files

* Add functional tests for file upload and indexing

* Update file string names to reflect changed filenames

* Add back point & multipoint handling logic to mapping

* Update testSubjects 'getProperty' to 'getAttribute' per recent changes to master

* One more 'getProperty' -> 'getAttribute'

* Review feedback

* Remove test that no longer produces failures w/ latest es

* Move common logic to 'beforeEach'. Add function to check add layer panel closed to fix race condition

* Review feedback

* Review feedback

* Review feedback

* Review feedback
This commit is contained in:
Aaron Caldwell 2019-07-31 13:10:44 -06:00 committed by GitHub
parent bc9d0d325b
commit 0959bebd73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 463 additions and 22 deletions

View file

@ -112,6 +112,7 @@ export class IndexSettings extends Component {
}
>
<EuiSelect
data-test-subj="fileImportIndexSelect"
disabled={indexDisabled}
options={indexTypes.map(indexType => ({
text: indexType,
@ -131,6 +132,7 @@ export class IndexSettings extends Component {
error={[indexNameError]}
>
<EuiFieldText
data-test-subj="fileUploadIndexNameInput"
disabled={indexDisabled}
placeholder={i18n.translate('xpack.fileUpload.enterIndexName', {
defaultMessage: 'Enter Index Name',

View file

@ -84,7 +84,7 @@ export class JsonImportProgress extends Component {
/>
</h4>
</EuiTitle>
<EuiCodeBlock paddingSize="s" overflowHeight={200}>
<EuiCodeBlock data-test-subj="indexRespCodeBlock" paddingSize="s" overflowHeight={200}>
{indexDataJson}
</EuiCodeBlock>
<EuiSpacer size="m" />
@ -100,7 +100,7 @@ export class JsonImportProgress extends Component {
/>
</h4>
</EuiTitle>
<EuiCodeBlock paddingSize="s" overflowHeight={200}>
<EuiCodeBlock data-test-subj="indexPatternRespCodeBlock" paddingSize="s" overflowHeight={200}>
{indexPatternJson}
</EuiCodeBlock>
<EuiSpacer size="m" />
@ -112,6 +112,7 @@ export class JsonImportProgress extends Component {
defaultMessage: 'Further index modifications can be made using\n',
})}
<a
data-test-subj="indexManagementNewIndexLink"
target="_blank"
href={`${chrome.getBasePath()}/app/kibana#/
management/elasticsearch/index_management/indices/

View file

@ -133,7 +133,6 @@ export class JsonIndexFilePicker extends Component {
return (
<Fragment>
{fileParsingProgress ? <EuiProgress size="xs" color="accent" position="absolute" /> : null}
<EuiFormRow
label={
<FormattedMessage

View file

@ -26,15 +26,18 @@ const DEFAULT_GEO_POINT_MAPPINGS = {
const DEFAULT_INGEST_PIPELINE = {};
export function getGeoIndexTypesForFeatures(featureTypes) {
if (!featureTypes || !featureTypes.length) {
const hasNoFeatureType = !featureTypes || !featureTypes.length;
if (hasNoFeatureType) {
return [];
} else if (!featureTypes.includes('Point')) {
return [ES_GEO_FIELD_TYPE.GEO_SHAPE];
} else if (featureTypes.includes('Point') && featureTypes.length === 1) {
return [ ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE ];
} else {
return [ ES_GEO_FIELD_TYPE.GEO_SHAPE ];
}
const isPoint = featureTypes.includes('Point') || featureTypes.includes('MultiPoint');
if (!isPoint) {
return [ES_GEO_FIELD_TYPE.GEO_SHAPE];
} else if (isPoint && featureTypes.length === 1) {
return [ ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE ];
}
return [ ES_GEO_FIELD_TYPE.GEO_SHAPE ];
}
// Reduces & flattens geojson to coordinates and properties (if any)

View file

@ -22,6 +22,7 @@ export const FlyoutFooter = ({
const nextButton = showNextButton
? (
<EuiButton
data-test-subj="importFileButton"
disabled={!hasLayerSelected || disableNextButton || isLoading}
isLoading={hasLayerSelected && isLoading}
iconSide="right"

View file

@ -24,15 +24,6 @@ export default function ({ getPageObjects, getService }) {
return requestTimestamp;
}
async function getHits() {
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');
await inspector.close();
return hits;
}
it('should re-fetch documents with refresh timer', async () => {
const beforeRefreshTimerTimestamp = await getRequestTimestamp();
expect(beforeRefreshTimerTimestamp.length).to.be(24);
@ -43,7 +34,7 @@ export default function ({ getPageObjects, getService }) {
describe('inspector', () => {
it('should register elasticsearch request in inspector', async () => {
const hits = await getHits();
const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('6');
});
});
@ -112,14 +103,14 @@ export default function ({ getPageObjects, getService }) {
describe('filter by extent', () => {
it('should handle geo_point filtering with extents that cross antimeridian', async () => {
await PageObjects.maps.loadSavedMap('antimeridian points example');
const hits = await getHits();
const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('2');
});
// Disabling pending fix for issue preventing shapes from showing
it.skip('should handle geo_shape filtering with extents that cross antimeridian', async () => {
await PageObjects.maps.loadSavedMap('antimeridian shapes example');
const hits = await getHits();
const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('2');
});
});

View file

@ -0,0 +1,106 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import path from 'path';
export default function ({ getPageObjects }) {
const PageObjects = getPageObjects(['maps', 'common']);
const IMPORT_FILE_PREVIEW_NAME = 'Import File';
const FILE_LOAD_DIR = 'test_upload_files';
const DEFAULT_LOAD_FILE_NAME = 'point.json';
describe('GeoJSON import layer panel', () => {
before(async () => {
await PageObjects.maps.openNewMap();
});
beforeEach(async () => {
await PageObjects.maps.clickAddLayer();
await PageObjects.maps.selectGeoJsonUploadSource();
await PageObjects.maps.uploadJsonFileForIndexing(
path.join(__dirname, FILE_LOAD_DIR, DEFAULT_LOAD_FILE_NAME)
);
});
afterEach(async () => {
await PageObjects.maps.cancelLayerAdd();
});
it('should add GeoJSON file to map', async () => {
const layerLoadedInToc = await PageObjects.maps
.doesLayerExist(IMPORT_FILE_PREVIEW_NAME);
expect(layerLoadedInToc).to.be(true);
const filePickerLoadedFile = await PageObjects.maps
.hasFilePickerLoadedFile(DEFAULT_LOAD_FILE_NAME);
expect(filePickerLoadedFile).to.be(true);
});
it('should close & remove preview layer on clicking "Cancel" after uploading file',
async () => {
await PageObjects.maps.cancelLayerAdd();
const panelOpen = await PageObjects.maps.isLayerAddPanelOpen();
expect(panelOpen).to.be(false);
await PageObjects.maps.waitForLayerDeleted(IMPORT_FILE_PREVIEW_NAME);
const layerLoadedInToc = await PageObjects.maps.doesLayerExist(IMPORT_FILE_PREVIEW_NAME);
expect(layerLoadedInToc).to.be(false);
});
it('should replace layer on input change',
async () => {
// Upload second file
const secondLoadFileName = 'polygon.json';
await PageObjects.maps.uploadJsonFileForIndexing(
path.join(__dirname, FILE_LOAD_DIR, secondLoadFileName)
);
await PageObjects.maps.waitForLayersToLoad();
// Check second file is loaded in file picker
const filePickerLoadedFile = await PageObjects.maps.hasFilePickerLoadedFile(secondLoadFileName);
expect(filePickerLoadedFile).to.be(true);
});
it('should clear layer on replacement layer load error',
async () => {
// Upload second file
const secondLoadFileName = 'not_json.txt';
await PageObjects.maps.uploadJsonFileForIndexing(
path.join(__dirname, FILE_LOAD_DIR, secondLoadFileName)
);
await PageObjects.maps.waitForLayersToLoad();
// Check second file is loaded in file picker
const filePickerLoadedFile = await PageObjects.maps
.hasFilePickerLoadedFile(secondLoadFileName);
expect(filePickerLoadedFile).to.be(true);
// Check that no file is loaded in layer preview
const layerLoadedInToc = await PageObjects.maps.doesLayerExist(IMPORT_FILE_PREVIEW_NAME);
expect(layerLoadedInToc).to.be(false);
});
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);
// Set index to valid name
await PageObjects.maps.setIndexName('validindexname');
// Check button
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);
});
});
}

View file

@ -0,0 +1,137 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import path from 'path';
import uuid from 'uuid/v4';
export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['maps', 'common']);
const testSubjects = getService('testSubjects');
const log = getService('log');
const IMPORT_FILE_PREVIEW_NAME = 'Import File';
const FILE_LOAD_DIR = 'test_upload_files';
const DEFAULT_LOAD_POINT_FILE_NAME = 'point.json';
const indexPoint = async () => await loadFileAndIndex(DEFAULT_LOAD_POINT_FILE_NAME);
async function loadFileAndIndex(loadFileName) {
log.debug(`Uploading ${loadFileName} for indexing`);
await PageObjects.maps.uploadJsonFileForIndexing(
path.join(__dirname, FILE_LOAD_DIR, loadFileName)
);
await PageObjects.maps.waitForLayersToLoad();
await PageObjects.maps.doesLayerExist(IMPORT_FILE_PREVIEW_NAME);
await PageObjects.maps.hasFilePickerLoadedFile(loadFileName);
const indexName = uuid();
await PageObjects.maps.setIndexName(indexName);
await PageObjects.maps.clickImportFileButton();
return indexName;
}
describe('On GeoJSON index name & pattern operation complete', () => {
before(async () => {
await PageObjects.maps.openNewMap();
});
beforeEach(async () => {
await PageObjects.maps.clickAddLayer();
await PageObjects.maps.selectGeoJsonUploadSource();
});
afterEach(async () => {
await PageObjects.maps.cancelLayerAdd();
await PageObjects.maps.waitForLayerAddPanelClosed();
});
it('should not activate add layer button until indexing succeeds', async () => {
await indexPoint();
let layerAddReady = await PageObjects.maps.importFileButtonEnabled();
expect(layerAddReady).to.be(false);
layerAddReady = await PageObjects.maps.importLayerReadyForAdd();
expect(layerAddReady).to.be(true);
});
it('should load geojson file as ES document source layer', async () => {
const indexName = await indexPoint();
const layerAddReady = await PageObjects.maps.importLayerReadyForAdd();
expect(layerAddReady).to.be(true);
await PageObjects.maps.clickImportFileButton();
const geojsonTempLayerExists = await PageObjects.maps.doesLayerExist('Import File');
expect(geojsonTempLayerExists).to.be(false);
const newIndexedLayerExists = await PageObjects.maps.doesLayerExist(indexName);
expect(newIndexedLayerExists).to.be(true);
const hits = await PageObjects.maps.getHits();
expect(hits).to.equal('1');
});
it('should remove index layer on cancel', async () => {
const indexName = await indexPoint();
const layerAddReady = await PageObjects.maps.importLayerReadyForAdd();
expect(layerAddReady).to.be(true);
await PageObjects.maps.cancelLayerAdd();
const geojsonTempLayerExists = await PageObjects.maps.doesLayerExist('Import File');
expect(geojsonTempLayerExists).to.be(false);
const newIndexedLayerExists = await PageObjects.maps.doesLayerExist(indexName);
expect(newIndexedLayerExists).to.be(false);
});
it('should create a link to new index in management', async () => {
const indexName = await indexPoint();
const layerAddReady = await PageObjects.maps.importLayerReadyForAdd();
expect(layerAddReady).to.be(true);
const newIndexLinkExists = await testSubjects.exists('indexManagementNewIndexLink');
expect(newIndexLinkExists).to.be(true);
const indexLink = await testSubjects.getAttribute('indexManagementNewIndexLink', 'href');
const linkDirectsToNewIndex = indexLink.endsWith(indexName);
expect(linkDirectsToNewIndex).to.be(true);
});
const GEO_POINT = 'geo_point';
const pointGeojsonFiles = ['point.json', 'multi_point.json'];
pointGeojsonFiles.forEach(async pointFile => {
it(`should index with type geo_point for file: ${pointFile}`,
async () => {
await loadFileAndIndex(pointFile);
const indexPatternResults = await PageObjects.maps.getIndexPatternResults();
const coordinatesField = indexPatternResults.fields.find(
({ name }) => name === 'coordinates'
);
expect(coordinatesField.type).to.be(GEO_POINT);
});
});
const GEO_SHAPE = 'geo_shape';
const nonPointGeojsonFiles = [
'line_string.json', 'multi_line_string.json', 'multi_polygon.json',
'polygon.json'
];
nonPointGeojsonFiles.forEach(async shapeFile => {
it(`should index with type geo_shape for file: ${shapeFile}`,
async () => {
await loadFileAndIndex(shapeFile);
const indexPatternResults = await PageObjects.maps.getIndexPatternResults();
const coordinatesField = indexPatternResults.fields.find(
({ name }) => name === 'coordinates'
);
expect(coordinatesField.type).to.be(GEO_SHAPE);
});
});
});
}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export default function ({ loadTestFile }) {
describe('import_geojson', function () {
loadTestFile(require.resolve('./add_layer_import_panel'));
loadTestFile(require.resolve('./file_indexing_panel'));
});
}

View file

@ -0,0 +1,12 @@
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[30, 10], [10, 30], [40, 40]
]
},
"properties": {
"name": "Line time"
}
}

View file

@ -0,0 +1,13 @@
{
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [
[[10, 10], [20, 20], [10, 40]],
[[40, 40], [30, 30], [40, 20], [30, 10]]
]
},
"properties": {
"name": "MultiLine time"
}
}

View file

@ -0,0 +1,12 @@
{
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": [
[10, 40], [40, 30], [20, 20], [30, 10]
]
},
"properties": {
"name": "MultiPoint islands"
}
}

View file

@ -0,0 +1,18 @@
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[[40, 40], [20, 45], [45, 30], [40, 40]]
],
[
[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]],
[[30, 20], [20, 15], [20, 25], [30, 20]]
]
]
},
"properties": {
"name": "Polys want features"
}
}

View file

@ -0,0 +1 @@
This is not a json file.

View file

@ -0,0 +1,13 @@
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
30,
10
]
},
"properties": {
"name": "Point island"
}
}

View file

@ -0,0 +1,13 @@
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[[35, 10], [45, 45], [15, 40], [10, 20], [35, 10]],
[[20, 30], [35, 35], [30, 20], [20, 30]]
]
},
"properties": {
"name": "Poly want a feature"
}
}

View file

@ -43,6 +43,7 @@ export default function ({ loadTestFile, getService }) {
loadTestFile(require.resolve('./es_geo_grid_source'));
loadTestFile(require.resolve('./joins'));
loadTestFile(require.resolve('./add_layer_panel'));
loadTestFile(require.resolve('./import_geojson'));
loadTestFile(require.resolve('./layer_errors'));
loadTestFile(require.resolve('./embeddable'));
});

View file

@ -185,6 +185,15 @@ export function GisPageProvider({ getService, getPageObjects }) {
await testSubjects.click(`mapListingTitleLink-${name.split(' ').join('-')}`);
}
async getHits() {
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = this.getInspectorStatRowHit(requestStats, 'Hits');
await inspector.close();
return hits;
}
async gotoMapListingPage() {
log.debug('gotoMapListingPage');
const onPage = await this.onMapListingPage();
@ -329,6 +338,14 @@ export function GisPageProvider({ getService, getPageObjects }) {
return await testSubjects.exists(`layerTocActionsPanelToggleButton${escapeLayerName(layerName)}`);
}
async hasFilePickerLoadedFile(fileName) {
log.debug(`Has file picker loaded file ${fileName}`);
const filePickerText = await find.byCssSelector('.euiFilePicker__promptText');
const filePickerTextContent = await filePickerText.getVisibleText();
return fileName === filePickerTextContent;
}
/*
* Layer panel utility functions
*/
@ -337,6 +354,14 @@ export function GisPageProvider({ getService, getPageObjects }) {
return await testSubjects.exists('layerAddForm');
}
async waitForLayerAddPanelClosed() {
let layerAddPanelOpen = false;
await retry.waitForWithTimeout('Layer add panel closed', 1000, async () => {
layerAddPanelOpen = await this.isLayerAddPanelOpen();
return !layerAddPanelOpen;
});
}
async cancelLayerAdd(layerName) {
log.debug(`Cancel layer add`);
const cancelExists = await testSubjects.exists('layerAddCancelButton');
@ -348,6 +373,67 @@ export function GisPageProvider({ getService, getPageObjects }) {
}
}
async importFileButtonEnabled() {
log.debug(`Check "Import file" button enabled`);
const importFileButton = await testSubjects.find('importFileButton');
const isDisabled = await importFileButton.getAttribute('disabled');
return !isDisabled;
}
async importLayerReadyForAdd() {
log.debug(`Wait until import complete`);
await testSubjects.find('indexRespCodeBlock', 5000);
let layerAddReady = false;
await retry.waitForWithTimeout('Add layer button ready', 2000, async () => {
layerAddReady = await this.importFileButtonEnabled();
return layerAddReady;
});
return layerAddReady;
}
async clickImportFileButton() {
log.debug(`Click "Import file" button`);
await testSubjects.click('importFileButton');
}
async setIndexName(indexName) {
log.debug(`Set index name to: ${indexName}`);
await testSubjects.setValue('fileUploadIndexNameInput', indexName);
}
async setIndexType(indexType) {
log.debug(`Set index type to: ${indexType}`);
await find.clickByCssSelector(
`select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]`
);
}
async indexTypeOptionExists(indexType) {
log.debug(`Check index type "${indexType}" available`);
return await find.existsByCssSelector(
`select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]`
);
}
async getCodeBlockParsedJson(dataTestSubjName) {
log.debug(`Get parsed code block for ${dataTestSubjName}`);
const indexRespCodeBlock = await find.byCssSelector(
`[data-test-subj="${dataTestSubjName}"]`
);
const indexRespJson = await indexRespCodeBlock.getAttribute('innerText');
return JSON.parse(indexRespJson);
}
async getIndexResults() {
log.debug('Get index results');
return await this.getCodeBlockParsedJson('indexRespCodeBlock');
}
async getIndexPatternResults() {
log.debug('Get index pattern results');
return await this.getCodeBlockParsedJson('indexPatternRespCodeBlock');
}
async setLayerQuery(layerName, query) {
await this.openLayerPanel(layerName);
await testSubjects.click('mapLayerPanelOpenFilterEditorButton');
@ -379,6 +465,25 @@ export function GisPageProvider({ getService, getPageObjects }) {
await testSubjects.click('vectorShapes');
}
async selectGeoJsonUploadSource() {
log.debug(`Select upload geojson vector file`);
await testSubjects.click('uploadGeoJsonVectorFile');
}
async uploadJsonFileForIndexing(path) {
log.debug(`Setting the path on the file input`);
if (browser.isW3CEnabled) {
const input = await find.byCssSelector('.euiFilePicker__input');
await input.type(path);
} else {
await find.setValue('.euiFilePicker__input', path);
}
log.debug(`File selected`);
await PageObjects.header.waitUntilLoadingHasFinished();
await this.waitForLayersToLoad();
}
// Returns first layer by default
async selectVectorLayer(vectorLayerName) {
log.debug(`Select EMS vector layer ${vectorLayerName}`);