diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts b/x-pack/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts new file mode 100644 index 0000000000000..1bb56464138d1 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_plugin/__mocks__/loader.ts @@ -0,0 +1,62 @@ +/* + * 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 function getIndexPatterns() { + return new Promise(resolve => { + resolve([ + { + id: '1', + title: 'Fake Index Pattern', + timeFieldName: 'timestamp', + fields: [ + { + name: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, + { + id: '2', + title: 'Fake Rollup Pattern', + timeFieldName: 'timestamp', + fields: [ + { + name: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, + ]); + }); +} diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts index b561d9b4463e7..40c119de69667 100644 --- a/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts @@ -4,15 +4,78 @@ * you may not use this file except in compliance with the Elastic License. */ -import { indexPatternDatasource, IndexPatternPersistedState } from './indexpattern'; -import { DatasourcePublicAPI, Operation } from '../types'; +import { + getIndexPatternDatasource, + IndexPatternPersistedState, + IndexPatternPrivateState, +} from './indexpattern'; +import { DatasourcePublicAPI, Operation, Datasource } from '../types'; + +jest.mock('./loader'); + +const expectedIndexPatterns = { + 1: { + id: '1', + title: 'Fake Index Pattern', + timeFieldName: 'timestamp', + fields: [ + { + name: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, + 2: { + id: '2', + title: 'Fake Rollup Pattern', + timeFieldName: 'timestamp', + fields: [ + { + name: 'timestamp', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, +}; describe('IndexPattern Data Source', () => { let persistedState: IndexPatternPersistedState; + let indexPatternDatasource: Datasource; beforeEach(() => { + // @ts-ignore + indexPatternDatasource = getIndexPatternDatasource(); + persistedState = { - currentIndexPattern: '', + currentIndexPattern: '1', columnOrder: ['col1'], columns: { col1: { @@ -32,8 +95,8 @@ describe('IndexPattern Data Source', () => { it('should load a default state', async () => { const state = await indexPatternDatasource.initialize(); expect(state).toEqual({ - currentIndexPattern: '', - indexPatterns: {}, + currentIndexPattern: '1', + indexPatterns: expectedIndexPatterns, columns: {}, columnOrder: [], }); @@ -44,7 +107,7 @@ describe('IndexPattern Data Source', () => { expect(state).toEqual({ ...persistedState, - indexPatterns: {}, + indexPatterns: expectedIndexPatterns, }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 29b018320a6db..0355bb8ae18c8 100644 --- a/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -5,9 +5,12 @@ */ import React from 'react'; +import { Chrome } from 'ui/chrome'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; import { render } from 'react-dom'; import { Datasource, DataType } from '..'; import { DatasourceDimensionPanelProps, DatasourceDataPanelProps } from '../types'; +import { getIndexPatterns } from './loader'; type OperationType = 'value' | 'terms' | 'date_histogram'; @@ -24,14 +27,15 @@ interface IndexPatternColumn { export interface IndexPattern { id: string; - fields: Field[]; + fields: IndexPatternField[]; title: string; timeFieldName?: string; } -export interface Field { +export interface IndexPatternField { name: string; type: string; + esTypes?: string[]; aggregatable: boolean; searchable: boolean; } @@ -40,80 +44,101 @@ export interface IndexPatternPersistedState { currentIndexPattern: string; columnOrder: string[]; - columns: { - [columnId: string]: IndexPatternColumn; - }; + columns: Record; } export type IndexPatternPrivateState = IndexPatternPersistedState & { - indexPatterns: { [id: string]: IndexPattern }; + indexPatterns: Record; }; -// Not stateful. State is persisted to the frame -export const indexPatternDatasource: Datasource< - IndexPatternPrivateState, - IndexPatternPersistedState -> = { - async initialize(state?: IndexPatternPersistedState) { - // TODO: Make fetch request to load indexPatterns from saved objects - if (state) { +export function getIndexPatternDatasource(chrome: Chrome, toastNotifications: ToastNotifications) { + // Not stateful. State is persisted to the frame + const indexPatternDatasource: Datasource = { + async initialize(state?: IndexPatternPersistedState) { + const indexPatternObjects = await getIndexPatterns(chrome, toastNotifications); + const indexPatterns: Record = {}; + + if (indexPatternObjects) { + indexPatternObjects.forEach(obj => { + indexPatterns[obj.id] = obj; + }); + } + + if (state) { + return { + ...state, + indexPatterns, + }; + } return { - ...state, - indexPatterns: {}, + currentIndexPattern: indexPatternObjects ? indexPatternObjects[0].id : '', + indexPatterns, + columns: {}, + columnOrder: [], }; - } - return { - currentIndexPattern: '', - indexPatterns: {}, - columns: {}, - columnOrder: [], - }; - }, - - getPersistableState({ currentIndexPattern, columns, columnOrder }: IndexPatternPrivateState) { - return { currentIndexPattern, columns, columnOrder }; - }, - - toExpression(state: IndexPatternPrivateState) { - return `${JSON.stringify(state.columns)}`; - }, - - renderDataPanel(domElement: Element, props: DatasourceDataPanelProps) { - render(
Index Pattern Data Source
, domElement); - }, - - getPublicAPI(state, setState) { - return { - getTableSpec: () => { - return state.columnOrder.map(colId => ({ columnId: colId })); - }, - getOperationForColumnId: (columnId: string) => { - const column = state.columns[columnId]; - if (columnId) { - const { dataType, label, isBucketed, operationId } = column; - return { - id: operationId, - label, - dataType, - isBucketed, - }; - } - return null; - }, - - renderDimensionPanel: (domElement: Element, props: DatasourceDimensionPanelProps) => {}, - - removeColumnInTableSpec: (columnId: string) => [], - moveColumnTo: (columnId: string, targetIndex: number) => {}, - duplicateColumn: (columnId: string) => [], - }; - }, - - getDatasourceSuggestionsForField() { - return []; - }, - - getDatasourceSuggestionsFromCurrentState() { - return []; - }, -}; + }, + + getPersistableState({ currentIndexPattern, columns, columnOrder }: IndexPatternPrivateState) { + return { currentIndexPattern, columns, columnOrder }; + }, + + toExpression(state: IndexPatternPrivateState) { + return `${JSON.stringify(state.columns)}`; + }, + + renderDataPanel( + domElement: Element, + props: DatasourceDataPanelProps + ) { + render( +
+ Index Pattern Data Source +
+ {props.state.currentIndexPattern && + Object.keys(props.state.indexPatterns).map(key => ( +
{props.state.indexPatterns[key].title}
+ ))} +
+
, + domElement + ); + }, + + getPublicAPI(state, setState) { + return { + getTableSpec: () => { + return state.columnOrder.map(colId => ({ columnId: colId })); + }, + getOperationForColumnId: (columnId: string) => { + const column = state.columns[columnId]; + if (columnId) { + const { dataType, label, isBucketed, operationId } = column; + return { + id: operationId, + label, + dataType, + isBucketed, + }; + } + return null; + }, + + renderDimensionPanel: (domElement: Element, props: DatasourceDimensionPanelProps) => {}, + + removeColumnInTableSpec: (columnId: string) => [], + moveColumnTo: (columnId: string, targetIndex: number) => {}, + duplicateColumn: (columnId: string) => [], + }; + }, + + getDatasourceSuggestionsForField() { + return []; + }, + + getDatasourceSuggestionsFromCurrentState() { + return []; + }, + }; + + return indexPatternDatasource; +} diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/plugins/lens/public/indexpattern_plugin/loader.ts new file mode 100644 index 0000000000000..3de7d511c4b49 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_plugin/loader.ts @@ -0,0 +1,41 @@ +/* + * 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 { Chrome } from 'ui/chrome'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { SavedObjectAttributes } from 'src/legacy/server/saved_objects/service/saved_objects_client'; +import { IndexPatternField } from './indexpattern'; + +interface IndexPatternAttributes extends SavedObjectAttributes { + title: string; + timeFieldName: string | null; + fields: string; + fieldFormatMap: string; +} + +export const getIndexPatterns = (chrome: Chrome, toastNotifications: ToastNotifications) => { + const savedObjectsClient = chrome.getSavedObjectsClient(); + return savedObjectsClient + .find({ + type: 'index-pattern', + perPage: 1000, // TODO: Paginate index patterns + }) + .then(resp => { + return resp.savedObjects.map(savedObject => { + const { id, attributes } = savedObject; + return Object.assign(attributes, { + id, + title: attributes.title, + fields: (JSON.parse(attributes.fields) as IndexPatternField[]).filter( + ({ type, esTypes }) => type !== 'string' || (esTypes && esTypes.includes('keyword')) + ), + }); + }); + }) + .catch(err => { + toastNotifications.addDanger('Failed to load index patterns'); + }); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/plugins/lens/public/indexpattern_plugin/plugin.tsx index 851beba8ba1ab..38fd82705dfab 100644 --- a/x-pack/plugins/lens/public/indexpattern_plugin/plugin.tsx +++ b/x-pack/plugins/lens/public/indexpattern_plugin/plugin.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { indexPatternDatasource } from './indexpattern'; +import chrome from 'ui/chrome'; +import { toastNotifications } from 'ui/notify'; +import { getIndexPatternDatasource } from './indexpattern'; class IndexPatternDatasourcePlugin { constructor() {} setup() { - return indexPatternDatasource; + return getIndexPatternDatasource(chrome, toastNotifications); } stop() {}