Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Dashboard] Public CRUD API MVP (#193067) #199517

Merged
merged 2 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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