Skip to content

Commit

Permalink
[8.x] [Dashboard] Public CRUD API MVP (elastic#193067) (elastic#199517)
Browse files Browse the repository at this point in the history
Manual backport of elastic#193067 to 8.x
  • Loading branch information
nickpeihl authored Nov 8, 2024
1 parent 22fefbc commit 401b00d
Show file tree
Hide file tree
Showing 117 changed files with 4,057 additions and 683 deletions.
18 changes: 16 additions & 2 deletions src/plugins/controls/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { ControlGroupChainingSystem } from './control_group';
import { ControlLabelPosition, ControlWidth } from './types';

export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'medium';
export const CONTROL_WIDTH_OPTIONS = { SMALL: 'small', MEDIUM: 'medium', LARGE: 'large' } as const;
export const CONTROL_LABEL_POSITION_OPTIONS = { ONE_LINE: 'oneLine', TWO_LINE: 'twoLine' } as const;
export const CONTROL_CHAINING_OPTIONS = { NONE: 'NONE', HIERARCHICAL: 'HIERARCHICAL' } as const;
export const DEFAULT_CONTROL_WIDTH: ControlWidth = CONTROL_WIDTH_OPTIONS.MEDIUM;
export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition =
CONTROL_LABEL_POSITION_OPTIONS.ONE_LINE;
export const DEFAULT_CONTROL_GROW: boolean = true;
export const DEFAULT_CONTROL_LABEL_POSITION: ControlLabelPosition = 'oneLine';
export const DEFAULT_CONTROL_CHAINING: ControlGroupChainingSystem =
CONTROL_CHAINING_OPTIONS.HIERARCHICAL;
export const DEFAULT_IGNORE_PARENT_SETTINGS = {
ignoreFilters: false,
ignoreQuery: false,
ignoreTimerange: false,
ignoreValidations: false,
} as const;
export const DEFAULT_AUTO_APPLY_SELECTIONS = true;

export const TIME_SLIDER_CONTROL = 'timeSlider';
export const RANGE_SLIDER_CONTROL = 'rangeSliderControl';
Expand Down
18 changes: 8 additions & 10 deletions src/plugins/controls/common/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

import { DataViewField } from '@kbn/data-views-plugin/common';
import { ControlLabelPosition, DefaultControlState, ParentIgnoreSettings } from '../types';
import { CONTROL_CHAINING_OPTIONS } from '../constants';

export const CONTROL_GROUP_TYPE = 'control_group';

export type ControlGroupChainingSystem = 'HIERARCHICAL' | 'NONE';
export type ControlGroupChainingSystem =
(typeof CONTROL_CHAINING_OPTIONS)[keyof typeof CONTROL_CHAINING_OPTIONS];

export type FieldFilterPredicate = (f: DataViewField) => boolean;

Expand Down Expand Up @@ -45,15 +47,11 @@ export interface ControlGroupRuntimeState<State extends DefaultControlState = De
}

export interface ControlGroupSerializedState
extends Pick<ControlGroupRuntimeState, 'chainingSystem' | 'editorConfig'> {
panelsJSON: string; // stringified version of ControlSerializedState
ignoreParentSettingsJSON: string;
// In runtime state, we refer to this property as `labelPosition`;
// to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state
controlStyle: ControlLabelPosition;
// In runtime state, we refer to the inverse of this property as `autoApplySelections`
// to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state
showApplySelections?: boolean;
extends Omit<ControlGroupRuntimeState, 'initialChildControlState'> {
// In runtime state, we refer to this property as `initialChildControlState`, but in
// the serialized state we transform the state object into an array of state objects
// to make it easier for API consumers to add new controls without specifying a uuid key.
controls: Array<ControlPanelState & { id?: string }>;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/controls/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ export type {
} from './types';

export {
DEFAULT_CONTROL_CHAINING,
DEFAULT_CONTROL_GROW,
DEFAULT_CONTROL_LABEL_POSITION,
DEFAULT_CONTROL_WIDTH,
DEFAULT_IGNORE_PARENT_SETTINGS,
DEFAULT_AUTO_APPLY_SELECTIONS,
CONTROL_WIDTH_OPTIONS,
CONTROL_CHAINING_OPTIONS,
CONTROL_LABEL_POSITION_OPTIONS,
OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL,
TIME_SLIDER_CONTROL,
Expand Down
10 changes: 7 additions & 3 deletions src/plugins/controls/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export type ControlWidth = 'small' | 'medium' | 'large';
export type ControlLabelPosition = 'twoLine' | 'oneLine';
import { SerializableRecord } from '@kbn/utility-types';
import { CONTROL_LABEL_POSITION_OPTIONS, CONTROL_WIDTH_OPTIONS } from './constants';

export type ControlWidth = (typeof CONTROL_WIDTH_OPTIONS)[keyof typeof CONTROL_WIDTH_OPTIONS];
export type ControlLabelPosition =
(typeof CONTROL_LABEL_POSITION_OPTIONS)[keyof typeof CONTROL_LABEL_POSITION_OPTIONS];

export type TimeSlice = [number, number];

export interface ParentIgnoreSettings {
export interface ParentIgnoreSettings extends SerializableRecord {
ignoreFilters?: boolean;
ignoreQuery?: boolean;
ignoreTimerange?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import { useSearchApi, type ViewMode as ViewModeType } from '@kbn/presentation-p
import type { ControlGroupApi } from '../..';
import {
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_LABEL_POSITION,
type ControlGroupRuntimeState,
type ControlGroupSerializedState,
DEFAULT_CONTROL_CHAINING,
DEFAULT_AUTO_APPLY_SELECTIONS,
} from '../../../common';
import {
type ControlGroupStateBuilder,
Expand Down Expand Up @@ -136,16 +139,19 @@ export const ControlGroupRenderer = ({
...initialState,
editorConfig,
});
const state = {
...omit(initialState, ['initialChildControlState', 'ignoreParentSettings']),
const state: ControlGroupSerializedState = {
...omit(initialState, ['initialChildControlState']),
editorConfig,
controlStyle: initialState?.labelPosition,
panelsJSON: JSON.stringify(initialState?.initialChildControlState ?? {}),
ignoreParentSettingsJSON: JSON.stringify(initialState?.ignoreParentSettings ?? {}),
autoApplySelections: initialState?.autoApplySelections ?? DEFAULT_AUTO_APPLY_SELECTIONS,
labelPosition: initialState?.labelPosition ?? DEFAULT_CONTROL_LABEL_POSITION,
chainingSystem: initialState?.chainingSystem ?? DEFAULT_CONTROL_CHAINING,
controls: Object.entries(initialState?.initialChildControlState ?? {}).map(
([controlId, value]) => ({ ...value, id: controlId })
),
};

if (!cancelled) {
setSerializedState(state as ControlGroupSerializedState);
setSerializedState(state);
}
})();
return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ import type {
ControlPanelsState,
ParentIgnoreSettings,
} from '../../common';
import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_LABEL_POSITION } from '../../common';
import {
CONTROL_GROUP_TYPE,
DEFAULT_CONTROL_CHAINING,
DEFAULT_CONTROL_LABEL_POSITION,
} from '../../common';
import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor';
import { coreServices, dataViewsService } from '../services/kibana_services';
import { ControlGroup } from './components/control_group';
Expand All @@ -45,8 +49,6 @@ import { initSelectionsManager } from './selections_manager';
import type { ControlGroupApi } from './types';
import { deserializeControlGroup } from './utils/serialization_utils';

const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL';

export const getControlGroupEmbeddableFactory = () => {
const controlGroupEmbeddableFactory: ReactEmbeddableFactory<
ControlGroupSerializedState,
Expand Down Expand Up @@ -85,7 +87,7 @@ export const getControlGroupEmbeddableFactory = () => {
});
const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined);
const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(
chainingSystem ?? DEFAULT_CHAINING_SYSTEM
chainingSystem ?? DEFAULT_CONTROL_CHAINING
);
const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>(
ignoreParentSettings
Expand All @@ -108,7 +110,7 @@ export const getControlGroupEmbeddableFactory = () => {
chainingSystem: [
chainingSystem$,
(next: ControlGroupChainingSystem) => chainingSystem$.next(next),
(a, b) => (a ?? DEFAULT_CHAINING_SYSTEM) === (b ?? DEFAULT_CHAINING_SYSTEM),
(a, b) => (a ?? DEFAULT_CONTROL_CHAINING) === (b ?? DEFAULT_CONTROL_CHAINING),
],
ignoreParentSettings: [
ignoreParentSettings$,
Expand Down Expand Up @@ -187,14 +189,14 @@ export const getControlGroupEmbeddableFactory = () => {
});
},
serializeState: () => {
const { panelsJSON, references } = controlsManager.serializeControls();
const { controls, references } = controlsManager.serializeControls();
return {
rawState: {
chainingSystem: chainingSystem$.getValue(),
controlStyle: labelPosition$.getValue(),
showApplySelections: !autoApplySelections$.getValue(),
ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()),
panelsJSON,
labelPosition: labelPosition$.getValue(),
autoApplySelections: autoApplySelections$.getValue(),
ignoreParentSettings: ignoreParentSettings$.getValue(),
controls,
},
references,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,8 @@ export function initControlsManager(
},
serializeControls: () => {
const references: Reference[] = [];
const explicitInputPanels: {
[panelId: string]: ControlPanelState & { explicitInput: object };
} = {};

const controls: Array<ControlPanelState & { controlConfig: object }> = [];

controlsInOrder$.getValue().forEach(({ id }, index) => {
const controlApi = getControlApi(id);
Expand All @@ -166,18 +165,18 @@ export function initControlsManager(
references.push(...controlReferences);
}

explicitInputPanels[id] = {
controls.push({
grow,
order: index,
type: controlApi.type,
width,
/** Re-add the `explicitInput` layer on serialize so control group saved object retains shape */
explicitInput: { id, ...rest },
};
/** Re-add the `controlConfig` layer on serialize so control group saved object retains shape */
controlConfig: { id, ...rest },
});
});

return {
panelsJSON: JSON.stringify(explicitInputPanels),
controls,
references,
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState } from '../../../common';
import {
type ControlGroupRuntimeState,
DEFAULT_CONTROL_CHAINING,
DEFAULT_CONTROL_LABEL_POSITION,
DEFAULT_AUTO_APPLY_SELECTIONS,
DEFAULT_IGNORE_PARENT_SETTINGS,
} from '../../../common';

export const getDefaultControlGroupRuntimeState = (): ControlGroupRuntimeState => ({
initialChildControlState: {},
labelPosition: DEFAULT_CONTROL_LABEL_POSITION,
chainingSystem: 'HIERARCHICAL',
autoApplySelections: true,
ignoreParentSettings: {
ignoreFilters: false,
ignoreQuery: false,
ignoreTimerange: false,
ignoreValidations: false,
},
chainingSystem: DEFAULT_CONTROL_CHAINING,
autoApplySelections: DEFAULT_AUTO_APPLY_SELECTIONS,
ignoreParentSettings: DEFAULT_IGNORE_PARENT_SETTINGS,
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,31 @@ import { parseReferenceName } from '../../controls/data_controls/reference_name_
export const deserializeControlGroup = (
state: SerializedPanelState<ControlGroupSerializedState>
): ControlGroupRuntimeState => {
const panels = JSON.parse(state.rawState.panelsJSON);
const ignoreParentSettings = JSON.parse(state.rawState.ignoreParentSettingsJSON);
const { controls } = state.rawState;
const controlsMap = Object.fromEntries(controls.map(({ id, ...rest }) => [id, rest]));

/** Inject data view references into each individual control */
const references = state.references ?? [];
references.forEach((reference) => {
const referenceName = reference.name;
const { controlId } = parseReferenceName(referenceName);
if (panels[controlId]) {
panels[controlId].dataViewId = reference.id;
if (controlsMap[controlId]) {
controlsMap[controlId].dataViewId = reference.id;
}
});

/** Flatten the state of each panel by removing `explicitInput` */
const flattenedPanels = Object.keys(panels).reduce((prev, panelId) => {
const currentPanel = panels[panelId];
const currentPanelExplicitInput = panels[panelId].explicitInput;
/** Flatten the state of each control by removing `controlConfig` */
const flattenedControls = Object.keys(controlsMap).reduce((prev, controlId) => {
const currentControl = controlsMap[controlId];
const currentControlExplicitInput = controlsMap[controlId].controlConfig;
return {
...prev,
[panelId]: { ...omit(currentPanel, 'explicitInput'), ...currentPanelExplicitInput },
[controlId]: { ...omit(currentControl, 'controlConfig'), ...currentControlExplicitInput },
};
}, {});

return {
...omit(state.rawState, ['panelsJSON', 'ignoreParentSettingsJSON']),
initialChildControlState: flattenedPanels,
ignoreParentSettings,
autoApplySelections:
typeof state.rawState.showApplySelections === 'boolean'
? !state.rawState.showApplySelections
: true, // Rename "showApplySelections" to "autoApplySelections"
labelPosition: state.rawState.controlStyle, // Rename "controlStyle" to "labelPosition"
...state.rawState,
initialChildControlState: flattenedControls,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ import {
import { OptionsListControlState } from '../../common/options_list';
import { mockDataControlState, mockOptionsListControlState } from '../mocks';
import { removeHideExcludeAndHideExists } from './control_group_migrations';
import {
SerializableControlGroupState,
getDefaultControlGroupState,
} from './control_group_persistence';
import { getDefaultControlGroupState } from './control_group_persistence';
import type { SerializableControlGroupState } from './types';

describe('migrate control group', () => {
const getOptionsListControl = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
type SerializedControlState,
} from '../../common';
import { OptionsListControlState } from '../../common/options_list';
import { SerializableControlGroupState } from './control_group_persistence';
import { SerializableControlGroupState } from './types';

export const makeControlOrdersZeroBased = (state: SerializableControlGroupState) => {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
makeControlOrdersZeroBased,
removeHideExcludeAndHideExists,
} from './control_group_migrations';
import type { SerializableControlGroupState } from './control_group_persistence';
import { SerializableControlGroupState } from './types';

const getPanelStatePrefix = (state: SerializedControlState) => `${state.explicitInput.id}:`;

Expand Down
Loading

0 comments on commit 401b00d

Please sign in to comment.