mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[embeddable] make presentation interface names consistent (#205279)
PR cleans up presentation interface names for consistentency * adds `$` suffix to all observables. For example, `dataLoading` => `dataLoading$` * removes `Panel` naming convention from interface names since an api may not be a panel, an api may be a dashboard. For example, `PublisesPanelTitle` => `PublishesTitle` #### Note to Reviewers Pay special attention to any place where your application creates an untyped API. In the example below, there is no typescript violation when the parent returns `dataLoading` instead of `dataLoading$` since the parent is not typed as `PublishesDataLoading`. Please check for instances like these. ``` <ReactEmbeddableRenderer getParentApi={() => { dataLoading: new BehaviorSubject() }} /> ``` --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
33145379e5
commit
05916056cd
229 changed files with 1263 additions and 1297 deletions
|
@ -70,7 +70,7 @@ export const EditExample = () => {
|
|||
INPUT_KEY,
|
||||
JSON.stringify({
|
||||
...controlGroupAPI.snapshotRuntimeState(),
|
||||
disabledActions: controlGroupAPI.disabledActionIds.getValue(), // not part of runtime
|
||||
disabledActions: controlGroupAPI.disabledActionIds$.getValue(), // not part of runtime
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -119,9 +119,9 @@ export const ReactControlExample = ({
|
|||
const children$ = new BehaviorSubject<{ [key: string]: unknown }>({});
|
||||
|
||||
return {
|
||||
dataLoading: dataLoading$,
|
||||
dataLoading$,
|
||||
unifiedSearchFilters$,
|
||||
viewMode: viewMode$,
|
||||
viewMode$,
|
||||
filters$,
|
||||
query$,
|
||||
timeRange$,
|
||||
|
@ -149,7 +149,7 @@ export const ReactControlExample = ({
|
|||
useEffect(() => {
|
||||
const subscription = combineCompatibleChildrenApis<PublishesDataLoading, boolean | undefined>(
|
||||
dashboardApi,
|
||||
'dataLoading',
|
||||
'dataLoading$',
|
||||
apiPublishesDataLoading,
|
||||
undefined,
|
||||
// flatten method
|
||||
|
@ -249,7 +249,7 @@ export const ReactControlExample = ({
|
|||
if (!controlGroupApi) {
|
||||
return;
|
||||
}
|
||||
const subscription = controlGroupApi.unsavedChanges.subscribe((nextUnsavedChanges) => {
|
||||
const subscription = controlGroupApi.unsavedChanges$.subscribe((nextUnsavedChanges) => {
|
||||
if (!nextUnsavedChanges) {
|
||||
clearControlGroupRuntimeState();
|
||||
setUnsavedChanges(undefined);
|
||||
|
|
|
@ -37,7 +37,7 @@ export const PresentationContainerExample = ({ uiActions }: { uiActions: UiActio
|
|||
}, [cleanUp]);
|
||||
|
||||
const [dataLoading, panels, timeRange] = useBatchedPublishingSubjects(
|
||||
pageApi.dataLoading,
|
||||
pageApi.dataLoading$,
|
||||
componentApi.panels$,
|
||||
pageApi.timeRange$
|
||||
);
|
||||
|
@ -95,7 +95,7 @@ export const PresentationContainerExample = ({ uiActions }: { uiActions: UiActio
|
|||
<TopNav
|
||||
onSave={componentApi.onSave}
|
||||
resetUnsavedChanges={pageApi.resetUnsavedChanges}
|
||||
unsavedChanges$={pageApi.unsavedChanges}
|
||||
unsavedChanges$={pageApi.unsavedChanges$}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -15,7 +15,7 @@ import { PublishesUnsavedChanges } from '@kbn/presentation-publishing';
|
|||
interface Props {
|
||||
onSave: () => Promise<void>;
|
||||
resetUnsavedChanges: () => void;
|
||||
unsavedChanges$: PublishesUnsavedChanges['unsavedChanges'];
|
||||
unsavedChanges$: PublishesUnsavedChanges['unsavedChanges$'];
|
||||
}
|
||||
|
||||
export function TopNav(props: Props) {
|
||||
|
|
|
@ -81,7 +81,7 @@ export function getPageApi() {
|
|||
boolean | undefined
|
||||
>(
|
||||
{ children$ },
|
||||
'dataLoading',
|
||||
'dataLoading$',
|
||||
apiPublishesDataLoading,
|
||||
undefined,
|
||||
// flatten method
|
||||
|
@ -193,7 +193,7 @@ export function getPageApi() {
|
|||
},
|
||||
canRemovePanels: () => true,
|
||||
children$,
|
||||
dataLoading: dataLoading$,
|
||||
dataLoading$,
|
||||
executionContext: {
|
||||
type: 'presentationContainerEmbeddableExample',
|
||||
},
|
||||
|
@ -210,7 +210,7 @@ export function getPageApi() {
|
|||
children$.next(omit(children$.value, id));
|
||||
},
|
||||
saveNotification$,
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
/**
|
||||
* return last saved embeddable state
|
||||
*/
|
||||
|
@ -252,7 +252,7 @@ export function getPageApi() {
|
|||
return true;
|
||||
},
|
||||
timeRange$,
|
||||
unsavedChanges: unsavedChanges$ as PublishingSubject<object | undefined>,
|
||||
unsavedChanges$: unsavedChanges$ as PublishingSubject<object | undefined>,
|
||||
} as PageApi,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export const RenderExamples = () => {
|
|||
const [api, setApi] = useState<SearchApi | null>(null);
|
||||
const [hidePanelChrome, setHidePanelChrome] = useState<boolean>(false);
|
||||
const [dataLoading, timeRange] = useBatchedOptionalPublishingSubjects(
|
||||
api?.dataLoading,
|
||||
api?.dataLoading$,
|
||||
parentApi.timeRange$
|
||||
);
|
||||
|
||||
|
|
|
@ -40,10 +40,10 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar
|
|||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!bookApi || !bookApi.unsavedChanges) {
|
||||
if (!bookApi || !bookApi.unsavedChanges$) {
|
||||
return;
|
||||
}
|
||||
const subscription = bookApi.unsavedChanges.subscribe((unsavedChanges) => {
|
||||
const subscription = bookApi.unsavedChanges$.subscribe((unsavedChanges) => {
|
||||
setHasUnsavedChanges(unsavedChanges !== undefined);
|
||||
unsavedChangesSessionStorage.save(unsavedChanges ?? {});
|
||||
});
|
||||
|
@ -158,7 +158,7 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar
|
|||
return unsavedChangesSessionStorage.load();
|
||||
},
|
||||
saveNotification$,
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
};
|
||||
}}
|
||||
onApiAvailable={(api) => {
|
||||
|
|
|
@ -60,7 +60,7 @@ export const initializeDataTableQueries = async (
|
|||
dataView$.next(defaultDataView);
|
||||
return;
|
||||
}
|
||||
const dataViewSubscription = dataViewProvider.dataViews.subscribe((dataViews) => {
|
||||
const dataViewSubscription = dataViewProvider.dataViews$.subscribe((dataViews) => {
|
||||
dataView$.next(dataViews?.[0] ?? defaultDataView);
|
||||
});
|
||||
return () => dataViewSubscription.unsubscribe();
|
||||
|
|
|
@ -17,7 +17,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import {
|
||||
initializeTimeRange,
|
||||
initializeTitles,
|
||||
initializeTitleManager,
|
||||
useBatchedPublishingSubjects,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
|
@ -40,8 +40,8 @@ export const getDataTableFactory = (
|
|||
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
|
||||
const storage = new Storage(localStorage);
|
||||
const timeRange = initializeTimeRange(state);
|
||||
const queryLoading$ = new BehaviorSubject<boolean | undefined>(true);
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
|
||||
const dataLoading$ = new BehaviorSubject<boolean | undefined>(true);
|
||||
const titleManager = initializeTitleManager(state);
|
||||
const allServices: UnifiedDataTableProps['services'] = {
|
||||
...services,
|
||||
storage,
|
||||
|
@ -53,18 +53,18 @@ export const getDataTableFactory = (
|
|||
const api = buildApi(
|
||||
{
|
||||
...timeRange.api,
|
||||
...titlesApi,
|
||||
dataLoading: queryLoading$,
|
||||
...titleManager.api,
|
||||
dataLoading$,
|
||||
serializeState: () => {
|
||||
return {
|
||||
rawState: { ...serializeTitles(), ...timeRange.serialize() },
|
||||
rawState: { ...titleManager.serialize(), ...timeRange.serialize() },
|
||||
};
|
||||
},
|
||||
},
|
||||
{ ...titleComparators, ...timeRange.comparators }
|
||||
{ ...titleManager.comparators, ...timeRange.comparators }
|
||||
);
|
||||
|
||||
const queryService = await initializeDataTableQueries(services, api, queryLoading$);
|
||||
const queryService = await initializeDataTableQueries(services, api, dataLoading$);
|
||||
|
||||
// Create the React Embeddable component
|
||||
return {
|
||||
|
@ -74,7 +74,7 @@ export const getDataTableFactory = (
|
|||
const [fields, rows, loading, dataView] = useBatchedPublishingSubjects(
|
||||
queryService.fields$,
|
||||
queryService.rows$,
|
||||
queryLoading$,
|
||||
dataLoading$,
|
||||
queryService.dataView$
|
||||
);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { css } from '@emotion/react';
|
|||
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
initializeTitles,
|
||||
initializeTitleManager,
|
||||
useInheritedViewMode,
|
||||
useStateFromPublishingSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
|
@ -40,7 +40,7 @@ export const markdownEmbeddableFactory: ReactEmbeddableFactory<
|
|||
/**
|
||||
* initialize state (source of truth)
|
||||
*/
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
|
||||
const titleManager = initializeTitleManager(state);
|
||||
const content$ = new BehaviorSubject(state.content);
|
||||
|
||||
/**
|
||||
|
@ -50,11 +50,11 @@ export const markdownEmbeddableFactory: ReactEmbeddableFactory<
|
|||
*/
|
||||
const api = buildApi(
|
||||
{
|
||||
...titlesApi,
|
||||
...titleManager.api,
|
||||
serializeState: () => {
|
||||
return {
|
||||
rawState: {
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
content: content$.getValue(),
|
||||
},
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ export const markdownEmbeddableFactory: ReactEmbeddableFactory<
|
|||
*/
|
||||
{
|
||||
content: [content$, (value) => content$.next(value)],
|
||||
...titleComparators,
|
||||
...titleManager.comparators,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { DataView } from '@kbn/data-views-plugin/common';
|
|||
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/public';
|
||||
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { initializeTitles, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { initializeTitleManager, useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { LazyDataViewPicker, withSuspense } from '@kbn/presentation-util-plugin/public';
|
||||
import {
|
||||
UnifiedFieldListSidebarContainer,
|
||||
|
@ -69,7 +69,7 @@ export const getFieldListFactory = (
|
|||
},
|
||||
buildEmbeddable: async (initialState, buildApi) => {
|
||||
const subscriptions = new Subscription();
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(initialState);
|
||||
const titleManager = initializeTitleManager(initialState);
|
||||
|
||||
// set up data views
|
||||
const [allDataViews, defaultDataViewId] = await Promise.all([
|
||||
|
@ -105,8 +105,8 @@ export const getFieldListFactory = (
|
|||
|
||||
const api = buildApi(
|
||||
{
|
||||
...titlesApi,
|
||||
dataViews: dataViews$,
|
||||
...titleManager.api,
|
||||
dataViews$,
|
||||
selectedFields: selectedFieldNames$,
|
||||
serializeState: () => {
|
||||
const dataViewId = selectedDataViewId$.getValue();
|
||||
|
@ -121,7 +121,7 @@ export const getFieldListFactory = (
|
|||
: [];
|
||||
return {
|
||||
rawState: {
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
// here we skip serializing the dataViewId, because the reference contains that information.
|
||||
selectedFieldNames: selectedFieldNames$.getValue(),
|
||||
},
|
||||
|
@ -130,7 +130,7 @@ export const getFieldListFactory = (
|
|||
},
|
||||
},
|
||||
{
|
||||
...titleComparators,
|
||||
...titleManager.comparators,
|
||||
dataViewId: [selectedDataViewId$, (value) => selectedDataViewId$.next(value)],
|
||||
selectedFieldNames: [
|
||||
selectedFieldNames$,
|
||||
|
|
|
@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import {
|
||||
apiHasParentApi,
|
||||
getUnchangingComparator,
|
||||
initializeTitles,
|
||||
initializeTitleManager,
|
||||
SerializedTitles,
|
||||
SerializedPanelState,
|
||||
useBatchedPublishingSubjects,
|
||||
|
@ -81,7 +81,7 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
|
|||
};
|
||||
},
|
||||
buildEmbeddable: async (state, buildApi) => {
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
|
||||
const titleManager = initializeTitleManager(state);
|
||||
const bookAttributesManager = stateManagerFromAttributes(state);
|
||||
const isByReference = Boolean(state.savedBookId);
|
||||
|
||||
|
@ -90,21 +90,21 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
|
|||
// if this book is currently by reference, we serialize the reference only.
|
||||
const bookByReferenceState: BookByReferenceSerializedState = {
|
||||
savedBookId: newId ?? state.savedBookId!,
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
};
|
||||
return { rawState: bookByReferenceState };
|
||||
}
|
||||
// if this book is currently by value, we serialize the entire state.
|
||||
const bookByValueState: BookByValueSerializedState = {
|
||||
attributes: serializeBookAttributes(bookAttributesManager),
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
};
|
||||
return { rawState: bookByValueState };
|
||||
};
|
||||
|
||||
const api = buildApi(
|
||||
{
|
||||
...titlesApi,
|
||||
...titleManager.api,
|
||||
onEdit: async () => {
|
||||
openSavedBookEditor({
|
||||
attributesManager: bookAttributesManager,
|
||||
|
@ -152,7 +152,7 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
|
|||
{
|
||||
savedBookId: getUnchangingComparator(), // saved book id will not change over the lifetime of the embeddable.
|
||||
...bookAttributesManager.comparators,
|
||||
...titleComparators,
|
||||
...titleManager.comparators,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -49,9 +49,9 @@ export const getSearchEmbeddableFactory = (services: Services) => {
|
|||
const api = buildApi(
|
||||
{
|
||||
...timeRange.api,
|
||||
blockingError: blockingError$,
|
||||
dataViews: dataViews$,
|
||||
dataLoading: dataLoading$,
|
||||
blockingError$,
|
||||
dataViews$,
|
||||
dataLoading$,
|
||||
serializeState: () => {
|
||||
return {
|
||||
rawState: {
|
||||
|
|
|
@ -72,8 +72,8 @@ export const GridExample = ({
|
|||
|
||||
const mockDashboardApi = useMockDashboardApi({ savedState: savedState.current });
|
||||
const [viewMode, expandedPanelId] = useBatchedPublishingSubjects(
|
||||
mockDashboardApi.viewMode,
|
||||
mockDashboardApi.expandedPanelId
|
||||
mockDashboardApi.viewMode$,
|
||||
mockDashboardApi.expandedPanelId$
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -244,7 +244,7 @@ export const GridExample = ({
|
|||
]}
|
||||
idSelected={viewMode}
|
||||
onChange={(id) => {
|
||||
mockDashboardApi.viewMode.next(id);
|
||||
mockDashboardApi.viewMode$.next(id);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -48,10 +48,10 @@ export const useMockDashboardApi = ({
|
|||
}),
|
||||
filters$: new BehaviorSubject([]),
|
||||
query$: new BehaviorSubject(''),
|
||||
viewMode: new BehaviorSubject('edit'),
|
||||
viewMode$: new BehaviorSubject('edit'),
|
||||
panels$,
|
||||
rows$: new BehaviorSubject<MockedDashboardRowMap>(savedState.rows),
|
||||
expandedPanelId: expandedPanelId$,
|
||||
expandedPanelId$,
|
||||
expandPanel: (id: string) => {
|
||||
if (expandedPanelId$.getValue()) {
|
||||
expandedPanelId$.next(undefined);
|
||||
|
|
|
@ -27,7 +27,7 @@ export const DualDashboardsExample = () => {
|
|||
const [secondDashboardApi, setSecondDashboardApi] = useState<DashboardApi | undefined>();
|
||||
|
||||
const ButtonControls = ({ dashboardApi }: { dashboardApi: DashboardApi }) => {
|
||||
const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode);
|
||||
const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode$);
|
||||
|
||||
return (
|
||||
<EuiButtonGroup
|
||||
|
|
|
@ -104,7 +104,7 @@ describe('GetCsvReportPanelAction', () => {
|
|||
}),
|
||||
hasTimeRange: () => true,
|
||||
parentApi: {
|
||||
viewMode: new BehaviorSubject('view'),
|
||||
viewMode$: new BehaviorSubject('view'),
|
||||
},
|
||||
},
|
||||
} as EmbeddableApiContext;
|
||||
|
|
|
@ -21,7 +21,7 @@ export const apiCanDuplicatePanels = (
|
|||
|
||||
export interface CanExpandPanels {
|
||||
expandPanel: (panelId: string) => void;
|
||||
expandedPanelId: PublishingSubject<string | undefined>;
|
||||
expandedPanelId$: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
export const apiCanExpandPanels = (unknownApi: unknown | null): unknownApi is CanExpandPanels => {
|
||||
|
|
|
@ -13,11 +13,11 @@ import { waitFor } from '@testing-library/react';
|
|||
|
||||
describe('childrenUnsavedChanges$', () => {
|
||||
const child1Api = {
|
||||
unsavedChanges: new BehaviorSubject<object | undefined>(undefined),
|
||||
unsavedChanges$: new BehaviorSubject<object | undefined>(undefined),
|
||||
resetUnsavedChanges: () => true,
|
||||
};
|
||||
const child2Api = {
|
||||
unsavedChanges: new BehaviorSubject<object | undefined>(undefined),
|
||||
unsavedChanges$: new BehaviorSubject<object | undefined>(undefined),
|
||||
resetUnsavedChanges: () => true,
|
||||
};
|
||||
const children$ = new BehaviorSubject<{ [key: string]: unknown }>({});
|
||||
|
@ -25,8 +25,8 @@ describe('childrenUnsavedChanges$', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
onFireMock.mockReset();
|
||||
child1Api.unsavedChanges.next(undefined);
|
||||
child2Api.unsavedChanges.next(undefined);
|
||||
child1Api.unsavedChanges$.next(undefined);
|
||||
child2Api.unsavedChanges$.next(undefined);
|
||||
children$.next({
|
||||
child1: child1Api,
|
||||
child2: child2Api,
|
||||
|
@ -61,7 +61,7 @@ describe('childrenUnsavedChanges$', () => {
|
|||
}
|
||||
);
|
||||
|
||||
child1Api.unsavedChanges.next({
|
||||
child1Api.unsavedChanges$.next({
|
||||
key1: 'modified value',
|
||||
});
|
||||
|
||||
|
@ -98,7 +98,7 @@ describe('childrenUnsavedChanges$', () => {
|
|||
children$.next({
|
||||
...children$.value,
|
||||
child3: {
|
||||
unsavedChanges: new BehaviorSubject<object | undefined>({ key1: 'modified value' }),
|
||||
unsavedChanges$: new BehaviorSubject<object | undefined>({ key1: 'modified value' }),
|
||||
resetUnsavedChanges: () => true,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ export function childrenUnsavedChanges$(children$: PresentationContainer['childr
|
|||
? of([])
|
||||
: combineLatest(
|
||||
childrenThatPublishUnsavedChanges.map(([childId, child]) =>
|
||||
child.unsavedChanges.pipe(map((unsavedChanges) => ({ childId, unsavedChanges })))
|
||||
child.unsavedChanges$.pipe(map((unsavedChanges) => ({ childId, unsavedChanges })))
|
||||
)
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -43,14 +43,14 @@ describe('unsavedChanges api', () => {
|
|||
});
|
||||
|
||||
test('should have no unsaved changes after initialization', () => {
|
||||
expect(api?.unsavedChanges.value).toBeUndefined();
|
||||
expect(api?.unsavedChanges$.value).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should have unsaved changes when state changes', async () => {
|
||||
key1$.next('modified key1 value');
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(api?.unsavedChanges.value).toEqual({
|
||||
expect(api?.unsavedChanges$.value).toEqual({
|
||||
key1: 'modified key1 value',
|
||||
}),
|
||||
{
|
||||
|
@ -61,28 +61,28 @@ describe('unsavedChanges api', () => {
|
|||
|
||||
test('should have no unsaved changes after save', async () => {
|
||||
key1$.next('modified key1 value');
|
||||
await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), {
|
||||
await waitFor(() => expect(api?.unsavedChanges$.value).not.toBeUndefined(), {
|
||||
interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1,
|
||||
});
|
||||
|
||||
// trigger save
|
||||
parentApi.saveNotification$.next();
|
||||
|
||||
await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), {
|
||||
await waitFor(() => expect(api?.unsavedChanges$.value).toBeUndefined(), {
|
||||
interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('should have no unsaved changes after reset', async () => {
|
||||
key1$.next('modified key1 value');
|
||||
await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), {
|
||||
await waitFor(() => expect(api?.unsavedChanges$.value).not.toBeUndefined(), {
|
||||
interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1,
|
||||
});
|
||||
|
||||
// trigger reset
|
||||
api?.resetUnsavedChanges();
|
||||
|
||||
await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), {
|
||||
await waitFor(() => expect(api?.unsavedChanges$.value).toBeUndefined(), {
|
||||
interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ export const initializeUnsavedChanges = <RuntimeState extends {} = {}>(
|
|||
comparatorKeys.push(key);
|
||||
}
|
||||
|
||||
const unsavedChanges = new BehaviorSubject<Partial<RuntimeState> | undefined>(
|
||||
const unsavedChanges$ = new BehaviorSubject<Partial<RuntimeState> | undefined>(
|
||||
runComparators(
|
||||
comparators,
|
||||
comparatorKeys,
|
||||
|
@ -84,7 +84,7 @@ export const initializeUnsavedChanges = <RuntimeState extends {} = {}>(
|
|||
combineLatestWith(lastSavedState$)
|
||||
)
|
||||
.subscribe(([latestState, lastSavedState]) => {
|
||||
unsavedChanges.next(
|
||||
unsavedChanges$.next(
|
||||
runComparators(comparators, comparatorKeys, lastSavedState, latestState)
|
||||
);
|
||||
})
|
||||
|
@ -92,7 +92,7 @@ export const initializeUnsavedChanges = <RuntimeState extends {} = {}>(
|
|||
|
||||
return {
|
||||
api: {
|
||||
unsavedChanges,
|
||||
unsavedChanges$,
|
||||
resetUnsavedChanges: () => {
|
||||
const lastSaved = lastSavedState$.getValue();
|
||||
|
||||
|
|
|
@ -127,24 +127,25 @@ export {
|
|||
type ViewMode,
|
||||
} from './interfaces/publishes_view_mode';
|
||||
export {
|
||||
apiPublishesPanelDescription,
|
||||
apiPublishesWritablePanelDescription,
|
||||
getPanelDescription,
|
||||
type PublishesPanelDescription,
|
||||
type PublishesWritablePanelDescription,
|
||||
} from './interfaces/titles/publishes_panel_description';
|
||||
apiPublishesDescription,
|
||||
apiPublishesWritableDescription,
|
||||
getDescription,
|
||||
type PublishesDescription,
|
||||
type PublishesWritableDescription,
|
||||
} from './interfaces/titles/publishes_description';
|
||||
export {
|
||||
apiPublishesPanelTitle,
|
||||
apiPublishesWritablePanelTitle,
|
||||
getPanelTitle,
|
||||
type PublishesPanelTitle,
|
||||
type PublishesWritablePanelTitle,
|
||||
} from './interfaces/titles/publishes_panel_title';
|
||||
apiPublishesTitle,
|
||||
apiPublishesWritableTitle,
|
||||
getTitle,
|
||||
type PublishesTitle,
|
||||
type PublishesWritableTitle,
|
||||
} from './interfaces/titles/publishes_title';
|
||||
export {
|
||||
initializeTitles,
|
||||
initializeTitleManager,
|
||||
stateHasTitles,
|
||||
type TitlesApi,
|
||||
type SerializedTitles,
|
||||
} from './interfaces/titles/titles_api';
|
||||
} from './interfaces/titles/title_manager';
|
||||
export {
|
||||
useBatchedOptionalPublishingSubjects,
|
||||
useBatchedPublishingSubjects,
|
||||
|
|
|
@ -30,16 +30,16 @@ export const apiCanAccessViewMode = (api: unknown): api is CanAccessViewMode =>
|
|||
* parent has a view mode, we consider the APIs version the source of truth.
|
||||
*/
|
||||
export const getInheritedViewMode = (api?: CanAccessViewMode) => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode.getValue();
|
||||
if (apiPublishesViewMode(api)) return api.viewMode$.getValue();
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) {
|
||||
return api.parentApi.viewMode.getValue();
|
||||
return api.parentApi.viewMode$.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
export const getViewModeSubject = (api?: CanAccessViewMode) => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode;
|
||||
if (apiPublishesViewMode(api)) return api.viewMode$;
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) {
|
||||
return api.parentApi.viewMode;
|
||||
return api.parentApi.viewMode$;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -10,15 +10,17 @@
|
|||
import { PublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesBlockingError {
|
||||
blockingError: PublishingSubject<Error | undefined>;
|
||||
blockingError$: PublishingSubject<Error | undefined>;
|
||||
}
|
||||
|
||||
export const apiPublishesBlockingError = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesBlockingError => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesBlockingError)?.blockingError !== undefined);
|
||||
return Boolean(
|
||||
unknownApi && (unknownApi as PublishesBlockingError)?.blockingError$ !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export function hasBlockingError(api: unknown) {
|
||||
return apiPublishesBlockingError(api) && api.blockingError?.value !== undefined;
|
||||
return apiPublishesBlockingError(api) && api.blockingError$?.value !== undefined;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
import { PublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesDataLoading {
|
||||
dataLoading: PublishingSubject<boolean | undefined>;
|
||||
dataLoading$: PublishingSubject<boolean | undefined>;
|
||||
}
|
||||
|
||||
export const apiPublishesDataLoading = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesDataLoading => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesDataLoading)?.dataLoading !== undefined);
|
||||
return Boolean(unknownApi && (unknownApi as PublishesDataLoading)?.dataLoading$ !== undefined);
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { DataView } from '@kbn/data-views-plugin/common';
|
|||
import { PublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesDataViews {
|
||||
dataViews: PublishingSubject<DataView[] | undefined>;
|
||||
dataViews$: PublishingSubject<DataView[] | undefined>;
|
||||
}
|
||||
|
||||
export type PublishesWritableDataViews = PublishesDataViews & {
|
||||
|
@ -21,5 +21,5 @@ export type PublishesWritableDataViews = PublishesDataViews & {
|
|||
export const apiPublishesDataViews = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesDataViews => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesDataViews)?.dataViews !== undefined);
|
||||
return Boolean(unknownApi && (unknownApi as PublishesDataViews)?.dataViews$ !== undefined);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import { PublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesDisabledActionIds {
|
||||
disabledActionIds: PublishingSubject<string[] | undefined>;
|
||||
disabledActionIds$: PublishingSubject<string[] | undefined>;
|
||||
setDisabledActionIds: (ids: string[] | undefined) => void;
|
||||
getAllTriggersDisabled?: () => boolean;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const apiPublishesDisabledActionIds = (
|
|||
): unknownApi is PublishesDisabledActionIds => {
|
||||
return Boolean(
|
||||
unknownApi &&
|
||||
(unknownApi as PublishesDisabledActionIds)?.disabledActionIds !== undefined &&
|
||||
(unknownApi as PublishesDisabledActionIds)?.disabledActionIds$ !== undefined &&
|
||||
typeof (unknownApi as PublishesDisabledActionIds)?.setDisabledActionIds === 'function'
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import { PublishingSubject } from '../publishing_subject';
|
|||
* This API publishes a saved object id which can be used to determine which saved object this API is linked to.
|
||||
*/
|
||||
export interface PublishesSavedObjectId {
|
||||
savedObjectId: PublishingSubject<string | undefined>;
|
||||
savedObjectId$: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,5 +22,7 @@ export interface PublishesSavedObjectId {
|
|||
export const apiPublishesSavedObjectId = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesSavedObjectId => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesSavedObjectId)?.savedObjectId !== undefined);
|
||||
return Boolean(
|
||||
unknownApi && (unknownApi as PublishesSavedObjectId)?.savedObjectId$ !== undefined
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
import { PublishingSubject } from '../publishing_subject';
|
||||
|
||||
export interface PublishesUnsavedChanges<Runtime extends object = object> {
|
||||
unsavedChanges: PublishingSubject<Partial<Runtime> | undefined>;
|
||||
unsavedChanges$: PublishingSubject<Partial<Runtime> | undefined>;
|
||||
resetUnsavedChanges: () => boolean;
|
||||
}
|
||||
|
||||
export const apiPublishesUnsavedChanges = (api: unknown): api is PublishesUnsavedChanges => {
|
||||
return Boolean(
|
||||
api &&
|
||||
(api as PublishesUnsavedChanges).unsavedChanges &&
|
||||
(api as PublishesUnsavedChanges).unsavedChanges$ &&
|
||||
(api as PublishesUnsavedChanges).resetUnsavedChanges
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ export type ViewMode = 'view' | 'edit' | 'print' | 'preview';
|
|||
* visibility of components.
|
||||
*/
|
||||
export interface PublishesViewMode {
|
||||
viewMode: PublishingSubject<ViewMode>;
|
||||
viewMode$: PublishingSubject<ViewMode>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ export type PublishesWritableViewMode = PublishesViewMode & {
|
|||
export const apiPublishesViewMode = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesViewMode => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesViewMode)?.viewMode !== undefined);
|
||||
return Boolean(unknownApi && (unknownApi as PublishesViewMode)?.viewMode$ !== undefined);
|
||||
};
|
||||
|
||||
export const apiPublishesWritableViewMode = (
|
||||
|
|
|
@ -8,30 +8,30 @@
|
|||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { getPanelDescription } from './publishes_panel_description';
|
||||
import { getDescription } from './publishes_description';
|
||||
|
||||
describe('getPanelDescription', () => {
|
||||
describe('getDescription', () => {
|
||||
test('should return default description when description is undefined', () => {
|
||||
const api = {
|
||||
panelDescription: new BehaviorSubject<string | undefined>(undefined),
|
||||
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
|
||||
description$: new BehaviorSubject<string | undefined>(undefined),
|
||||
defaultDescription$: new BehaviorSubject<string | undefined>('default description'),
|
||||
};
|
||||
expect(getPanelDescription(api)).toBe('default description');
|
||||
expect(getDescription(api)).toBe('default description');
|
||||
});
|
||||
|
||||
test('should return empty description when description is empty string', () => {
|
||||
const api = {
|
||||
panelDescription: new BehaviorSubject<string | undefined>(''),
|
||||
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
|
||||
description$: new BehaviorSubject<string | undefined>(''),
|
||||
defaultDescription$: new BehaviorSubject<string | undefined>('default description'),
|
||||
};
|
||||
expect(getPanelDescription(api)).toBe('');
|
||||
expect(getDescription(api)).toBe('');
|
||||
});
|
||||
|
||||
test('should return description when description is provided', () => {
|
||||
const api = {
|
||||
panelDescription: new BehaviorSubject<string | undefined>('custom description'),
|
||||
defaultPanelDescription: new BehaviorSubject<string | undefined>('default description'),
|
||||
description$: new BehaviorSubject<string | undefined>('custom description'),
|
||||
defaultDescription$: new BehaviorSubject<string | undefined>('default description'),
|
||||
};
|
||||
expect(getPanelDescription(api)).toBe('custom description');
|
||||
expect(getDescription(api)).toBe('custom description');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PublishingSubject } from '../../publishing_subject';
|
||||
|
||||
export interface PublishesDescription {
|
||||
description$: PublishingSubject<string | undefined>;
|
||||
defaultDescription$?: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
export function getDescription(api: Partial<PublishesDescription>): string | undefined {
|
||||
return api.description$?.value ?? api.defaultDescription$?.value;
|
||||
}
|
||||
|
||||
export type PublishesWritableDescription = PublishesDescription & {
|
||||
setDescription: (newTitle: string | undefined) => void;
|
||||
};
|
||||
|
||||
export const apiPublishesDescription = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesDescription => {
|
||||
return Boolean(unknownApi && (unknownApi as PublishesDescription)?.description$ !== undefined);
|
||||
};
|
||||
|
||||
export const apiPublishesWritableDescription = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesWritableDescription => {
|
||||
return (
|
||||
apiPublishesDescription(unknownApi) &&
|
||||
(unknownApi as PublishesWritableDescription).setDescription !== undefined &&
|
||||
typeof (unknownApi as PublishesWritableDescription).setDescription === 'function'
|
||||
);
|
||||
};
|
|
@ -1,41 +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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PublishingSubject } from '../../publishing_subject';
|
||||
|
||||
export interface PublishesPanelDescription {
|
||||
panelDescription: PublishingSubject<string | undefined>;
|
||||
defaultPanelDescription?: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
export function getPanelDescription(api: Partial<PublishesPanelDescription>): string | undefined {
|
||||
return api.panelDescription?.value ?? api.defaultPanelDescription?.value;
|
||||
}
|
||||
|
||||
export type PublishesWritablePanelDescription = PublishesPanelDescription & {
|
||||
setPanelDescription: (newTitle: string | undefined) => void;
|
||||
};
|
||||
|
||||
export const apiPublishesPanelDescription = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesPanelDescription => {
|
||||
return Boolean(
|
||||
unknownApi && (unknownApi as PublishesPanelDescription)?.panelDescription !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export const apiPublishesWritablePanelDescription = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesWritablePanelDescription => {
|
||||
return (
|
||||
apiPublishesPanelDescription(unknownApi) &&
|
||||
(unknownApi as PublishesWritablePanelDescription).setPanelDescription !== undefined &&
|
||||
typeof (unknownApi as PublishesWritablePanelDescription).setPanelDescription === 'function'
|
||||
);
|
||||
};
|
|
@ -1,47 +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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PublishingSubject } from '../../publishing_subject';
|
||||
|
||||
export interface PublishesPanelTitle {
|
||||
panelTitle: PublishingSubject<string | undefined>;
|
||||
hidePanelTitle: PublishingSubject<boolean | undefined>;
|
||||
defaultPanelTitle?: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
export function getPanelTitle(api: Partial<PublishesPanelTitle>): string | undefined {
|
||||
return api.panelTitle?.value ?? api.defaultPanelTitle?.value;
|
||||
}
|
||||
|
||||
export type PublishesWritablePanelTitle = PublishesPanelTitle & {
|
||||
setPanelTitle: (newTitle: string | undefined) => void;
|
||||
setHidePanelTitle: (hide: boolean | undefined) => void;
|
||||
};
|
||||
|
||||
export const apiPublishesPanelTitle = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesPanelTitle => {
|
||||
return Boolean(
|
||||
unknownApi &&
|
||||
(unknownApi as PublishesPanelTitle)?.panelTitle !== undefined &&
|
||||
(unknownApi as PublishesPanelTitle)?.hidePanelTitle !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export const apiPublishesWritablePanelTitle = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesWritablePanelTitle => {
|
||||
return (
|
||||
apiPublishesPanelTitle(unknownApi) &&
|
||||
(unknownApi as PublishesWritablePanelTitle).setPanelTitle !== undefined &&
|
||||
(typeof (unknownApi as PublishesWritablePanelTitle).setPanelTitle === 'function' &&
|
||||
(unknownApi as PublishesWritablePanelTitle).setHidePanelTitle) !== undefined &&
|
||||
typeof (unknownApi as PublishesWritablePanelTitle).setHidePanelTitle === 'function'
|
||||
);
|
||||
};
|
|
@ -8,30 +8,30 @@
|
|||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { getPanelTitle } from './publishes_panel_title';
|
||||
import { getTitle } from './publishes_title';
|
||||
|
||||
describe('getPanelTitle', () => {
|
||||
test('should return default title when title is undefined', () => {
|
||||
const api = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>(undefined),
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>('default title'),
|
||||
title$: new BehaviorSubject<string | undefined>(undefined),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>('default title'),
|
||||
};
|
||||
expect(getPanelTitle(api)).toBe('default title');
|
||||
expect(getTitle(api)).toBe('default title');
|
||||
});
|
||||
|
||||
test('should return empty title when title is empty string', () => {
|
||||
const api = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>(''),
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>('default title'),
|
||||
title$: new BehaviorSubject<string | undefined>(''),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>('default title'),
|
||||
};
|
||||
expect(getPanelTitle(api)).toBe('');
|
||||
expect(getTitle(api)).toBe('');
|
||||
});
|
||||
|
||||
test('should return title when title is provided', () => {
|
||||
const api = {
|
||||
panelTitle: new BehaviorSubject<string | undefined>('custom title'),
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>('default title'),
|
||||
title$: new BehaviorSubject<string | undefined>('custom title'),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>('default title'),
|
||||
};
|
||||
expect(getPanelTitle(api)).toBe('custom title');
|
||||
expect(getTitle(api)).toBe('custom title');
|
||||
});
|
||||
});
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { PublishingSubject } from '../../publishing_subject';
|
||||
|
||||
export interface PublishesTitle {
|
||||
title$: PublishingSubject<string | undefined>;
|
||||
hideTitle$: PublishingSubject<boolean | undefined>;
|
||||
defaultTitle$?: PublishingSubject<string | undefined>;
|
||||
}
|
||||
|
||||
export function getTitle(api: Partial<PublishesTitle>): string | undefined {
|
||||
return api.title$?.value ?? api.defaultTitle$?.value;
|
||||
}
|
||||
|
||||
export type PublishesWritableTitle = PublishesTitle & {
|
||||
setTitle: (newTitle: string | undefined) => void;
|
||||
setHideTitle: (hide: boolean | undefined) => void;
|
||||
};
|
||||
|
||||
export const apiPublishesTitle = (unknownApi: null | unknown): unknownApi is PublishesTitle => {
|
||||
return Boolean(
|
||||
unknownApi &&
|
||||
(unknownApi as PublishesTitle)?.title$ !== undefined &&
|
||||
(unknownApi as PublishesTitle)?.hideTitle$ !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export const apiPublishesWritableTitle = (
|
||||
unknownApi: null | unknown
|
||||
): unknownApi is PublishesWritableTitle => {
|
||||
return (
|
||||
apiPublishesTitle(unknownApi) &&
|
||||
(unknownApi as PublishesWritableTitle).setTitle !== undefined &&
|
||||
(typeof (unknownApi as PublishesWritableTitle).setTitle === 'function' &&
|
||||
(unknownApi as PublishesWritableTitle).setHideTitle) !== undefined &&
|
||||
typeof (unknownApi as PublishesWritableTitle).setHideTitle === 'function'
|
||||
);
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { initializeTitleManager, SerializedTitles } from './title_manager';
|
||||
|
||||
describe('titles api', () => {
|
||||
const rawState: SerializedTitles = {
|
||||
title: 'very cool title',
|
||||
description: 'less cool description',
|
||||
hidePanelTitles: false,
|
||||
};
|
||||
|
||||
it('should initialize publishing subjects with the provided rawState', () => {
|
||||
const { api } = initializeTitleManager(rawState);
|
||||
expect(api.title$.value).toBe(rawState.title);
|
||||
expect(api.description$.value).toBe(rawState.description);
|
||||
expect(api.hideTitle$.value).toBe(rawState.hidePanelTitles);
|
||||
});
|
||||
|
||||
it('should update publishing subject values when set functions are called', () => {
|
||||
const { api } = initializeTitleManager(rawState);
|
||||
|
||||
api.setTitle('even cooler title');
|
||||
api.setDescription('super uncool description');
|
||||
api.setHideTitle(true);
|
||||
|
||||
expect(api.title$.value).toEqual('even cooler title');
|
||||
expect(api.description$.value).toEqual('super uncool description');
|
||||
expect(api.hideTitle$.value).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly serialize current state', () => {
|
||||
const titleManager = initializeTitleManager(rawState);
|
||||
titleManager.api.setTitle('UH OH, A TITLE');
|
||||
|
||||
const serializedTitles = titleManager.serialize();
|
||||
expect(serializedTitles).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"description": "less cool description",
|
||||
"hidePanelTitles": false,
|
||||
"title": "UH OH, A TITLE",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the correct set of comparators', () => {
|
||||
const { comparators } = initializeTitleManager(rawState);
|
||||
|
||||
expect(comparators.title).toBeDefined();
|
||||
expect(comparators.description).toBeDefined();
|
||||
expect(comparators.hidePanelTitles).toBeDefined();
|
||||
});
|
||||
|
||||
it('should correctly compare hidePanelTitles with custom comparator', () => {
|
||||
const { comparators } = initializeTitleManager(rawState);
|
||||
|
||||
expect(comparators.hidePanelTitles![2]!(true, false)).toBe(false);
|
||||
expect(comparators.hidePanelTitles![2]!(undefined, false)).toBe(true);
|
||||
expect(comparators.hidePanelTitles![2]!(true, undefined)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { StateComparators } from '../../comparators';
|
||||
import { PublishesWritableDescription } from './publishes_description';
|
||||
import { PublishesWritableTitle } from './publishes_title';
|
||||
|
||||
export interface SerializedTitles {
|
||||
title?: string;
|
||||
description?: string;
|
||||
hidePanelTitles?: boolean;
|
||||
}
|
||||
|
||||
export const stateHasTitles = (state: unknown): state is SerializedTitles => {
|
||||
return (
|
||||
(state as SerializedTitles)?.title !== undefined ||
|
||||
(state as SerializedTitles)?.description !== undefined ||
|
||||
(state as SerializedTitles)?.hidePanelTitles !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export interface TitlesApi extends PublishesWritableTitle, PublishesWritableDescription {}
|
||||
|
||||
export const initializeTitleManager = (
|
||||
rawState: SerializedTitles
|
||||
): {
|
||||
api: TitlesApi;
|
||||
comparators: StateComparators<SerializedTitles>;
|
||||
serialize: () => SerializedTitles;
|
||||
} => {
|
||||
const title$ = new BehaviorSubject<string | undefined>(rawState.title);
|
||||
const description$ = new BehaviorSubject<string | undefined>(rawState.description);
|
||||
const hideTitle$ = new BehaviorSubject<boolean | undefined>(rawState.hidePanelTitles);
|
||||
|
||||
const setTitle = (value: string | undefined) => {
|
||||
if (value !== title$.value) title$.next(value);
|
||||
};
|
||||
const setHideTitle = (value: boolean | undefined) => {
|
||||
if (value !== hideTitle$.value) hideTitle$.next(value);
|
||||
};
|
||||
const setDescription = (value: string | undefined) => {
|
||||
if (value !== description$.value) description$.next(value);
|
||||
};
|
||||
|
||||
return {
|
||||
api: {
|
||||
title$,
|
||||
hideTitle$,
|
||||
setTitle,
|
||||
setHideTitle,
|
||||
description$,
|
||||
setDescription,
|
||||
},
|
||||
comparators: {
|
||||
title: [title$, setTitle],
|
||||
description: [description$, setDescription],
|
||||
hidePanelTitles: [hideTitle$, setHideTitle, (a, b) => Boolean(a) === Boolean(b)],
|
||||
} as StateComparators<SerializedTitles>,
|
||||
serialize: () => ({
|
||||
title: title$.value,
|
||||
hidePanelTitles: hideTitle$.value,
|
||||
description: description$.value,
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -1,67 +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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { initializeTitles, SerializedTitles } from './titles_api';
|
||||
|
||||
describe('titles api', () => {
|
||||
const rawState: SerializedTitles = {
|
||||
title: 'very cool title',
|
||||
description: 'less cool description',
|
||||
hidePanelTitles: false,
|
||||
};
|
||||
|
||||
it('should initialize publishing subjects with the provided rawState', () => {
|
||||
const { titlesApi } = initializeTitles(rawState);
|
||||
expect(titlesApi.panelTitle.value).toBe(rawState.title);
|
||||
expect(titlesApi.panelDescription.value).toBe(rawState.description);
|
||||
expect(titlesApi.hidePanelTitle.value).toBe(rawState.hidePanelTitles);
|
||||
});
|
||||
|
||||
it('should update publishing subject values when set functions are called', () => {
|
||||
const { titlesApi } = initializeTitles(rawState);
|
||||
|
||||
titlesApi.setPanelTitle('even cooler title');
|
||||
titlesApi.setPanelDescription('super uncool description');
|
||||
titlesApi.setHidePanelTitle(true);
|
||||
|
||||
expect(titlesApi.panelTitle.value).toEqual('even cooler title');
|
||||
expect(titlesApi.panelDescription.value).toEqual('super uncool description');
|
||||
expect(titlesApi.hidePanelTitle.value).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly serialize current state', () => {
|
||||
const { serializeTitles, titlesApi } = initializeTitles(rawState);
|
||||
titlesApi.setPanelTitle('UH OH, A TITLE');
|
||||
|
||||
const serializedTitles = serializeTitles();
|
||||
expect(serializedTitles).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"description": "less cool description",
|
||||
"hidePanelTitles": false,
|
||||
"title": "UH OH, A TITLE",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the correct set of comparators', () => {
|
||||
const { titleComparators } = initializeTitles(rawState);
|
||||
|
||||
expect(titleComparators.title).toBeDefined();
|
||||
expect(titleComparators.description).toBeDefined();
|
||||
expect(titleComparators.hidePanelTitles).toBeDefined();
|
||||
});
|
||||
|
||||
it('should correctly compare hidePanelTitles with custom comparator', () => {
|
||||
const { titleComparators } = initializeTitles(rawState);
|
||||
|
||||
expect(titleComparators.hidePanelTitles![2]!(true, false)).toBe(false);
|
||||
expect(titleComparators.hidePanelTitles![2]!(undefined, false)).toBe(true);
|
||||
expect(titleComparators.hidePanelTitles![2]!(true, undefined)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -1,76 +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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { StateComparators } from '../../comparators';
|
||||
import { PublishesWritablePanelDescription } from './publishes_panel_description';
|
||||
import { PublishesWritablePanelTitle } from './publishes_panel_title';
|
||||
|
||||
export interface SerializedTitles {
|
||||
title?: string;
|
||||
description?: string;
|
||||
hidePanelTitles?: boolean;
|
||||
}
|
||||
|
||||
export const stateHasTitles = (state: unknown): state is SerializedTitles => {
|
||||
return (
|
||||
(state as SerializedTitles)?.title !== undefined ||
|
||||
(state as SerializedTitles)?.description !== undefined ||
|
||||
(state as SerializedTitles)?.hidePanelTitles !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
export interface TitlesApi extends PublishesWritablePanelTitle, PublishesWritablePanelDescription {}
|
||||
|
||||
export const initializeTitles = (
|
||||
rawState: SerializedTitles
|
||||
): {
|
||||
titlesApi: TitlesApi;
|
||||
titleComparators: StateComparators<SerializedTitles>;
|
||||
serializeTitles: () => SerializedTitles;
|
||||
} => {
|
||||
const panelTitle = new BehaviorSubject<string | undefined>(rawState.title);
|
||||
const panelDescription = new BehaviorSubject<string | undefined>(rawState.description);
|
||||
const hidePanelTitle = new BehaviorSubject<boolean | undefined>(rawState.hidePanelTitles);
|
||||
|
||||
const setPanelTitle = (value: string | undefined) => {
|
||||
if (value !== panelTitle.value) panelTitle.next(value);
|
||||
};
|
||||
const setHidePanelTitle = (value: boolean | undefined) => {
|
||||
if (value !== hidePanelTitle.value) hidePanelTitle.next(value);
|
||||
};
|
||||
const setPanelDescription = (value: string | undefined) => {
|
||||
if (value !== panelDescription.value) panelDescription.next(value);
|
||||
};
|
||||
|
||||
const titleComparators: StateComparators<SerializedTitles> = {
|
||||
title: [panelTitle, setPanelTitle],
|
||||
description: [panelDescription, setPanelDescription],
|
||||
hidePanelTitles: [hidePanelTitle, setHidePanelTitle, (a, b) => Boolean(a) === Boolean(b)],
|
||||
};
|
||||
|
||||
const titlesApi = {
|
||||
panelTitle,
|
||||
hidePanelTitle,
|
||||
setPanelTitle,
|
||||
setHidePanelTitle,
|
||||
panelDescription,
|
||||
setPanelDescription,
|
||||
};
|
||||
|
||||
return {
|
||||
serializeTitles: () => ({
|
||||
title: panelTitle.value,
|
||||
hidePanelTitles: hidePanelTitle.value,
|
||||
description: panelDescription.value,
|
||||
}),
|
||||
titleComparators,
|
||||
titlesApi,
|
||||
};
|
||||
};
|
|
@ -15,7 +15,7 @@ import { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/p
|
|||
import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PresentationContainer } from '@kbn/presentation-containers';
|
||||
import { getUnchangingComparator, initializeTitles } from '@kbn/presentation-publishing';
|
||||
import { getUnchangingComparator, initializeTitleManager } from '@kbn/presentation-publishing';
|
||||
|
||||
import { IMAGE_CLICK_TRIGGER } from '../actions';
|
||||
import { openImageEditor } from '../components/image_editor/open_image_editor';
|
||||
|
@ -38,11 +38,11 @@ export const getImageEmbeddableFactory = ({
|
|||
type: IMAGE_EMBEDDABLE_TYPE,
|
||||
deserializeState: (state) => state.rawState,
|
||||
buildEmbeddable: async (initialState, buildApi, uuid) => {
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(initialState);
|
||||
const titleManager = initializeTitleManager(initialState);
|
||||
|
||||
const dynamicActionsApi = embeddableEnhanced?.initializeReactEmbeddableDynamicActions(
|
||||
uuid,
|
||||
() => titlesApi.panelTitle.getValue(),
|
||||
() => titleManager.api.title$.getValue(),
|
||||
initialState
|
||||
);
|
||||
// if it is provided, start the dynamic actions manager
|
||||
|
@ -54,9 +54,9 @@ export const getImageEmbeddableFactory = ({
|
|||
|
||||
const embeddable = buildApi(
|
||||
{
|
||||
...titlesApi,
|
||||
...titleManager.api,
|
||||
...(dynamicActionsApi?.dynamicActionsApi ?? {}),
|
||||
dataLoading: dataLoading$,
|
||||
dataLoading$,
|
||||
supportedTriggers: () => [IMAGE_CLICK_TRIGGER],
|
||||
onEdit: async () => {
|
||||
try {
|
||||
|
@ -77,7 +77,7 @@ export const getImageEmbeddableFactory = ({
|
|||
serializeState: () => {
|
||||
return {
|
||||
rawState: {
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
...(dynamicActionsApi?.serializeDynamicActions() ?? {}),
|
||||
imageConfig: imageConfig$.getValue(),
|
||||
},
|
||||
|
@ -85,7 +85,7 @@ export const getImageEmbeddableFactory = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
...titleComparators,
|
||||
...titleManager.comparators,
|
||||
...(dynamicActionsApi?.dynamicActionsComparator ?? {
|
||||
enhancements: getUnchangingComparator(),
|
||||
}),
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import {
|
||||
apiPublishesPanelDescription,
|
||||
apiPublishesPanelTitle,
|
||||
apiPublishesDescription,
|
||||
apiPublishesTitle,
|
||||
apiPublishesSavedObjectId,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { LinksParentApi } from '../types';
|
||||
|
@ -18,5 +18,5 @@ import { LinksParentApi } from '../types';
|
|||
export const isParentApiCompatible = (parentApi: unknown): parentApi is LinksParentApi =>
|
||||
apiIsPresentationContainer(parentApi) &&
|
||||
apiPublishesSavedObjectId(parentApi) &&
|
||||
apiPublishesPanelTitle(parentApi) &&
|
||||
apiPublishesPanelDescription(parentApi);
|
||||
apiPublishesTitle(parentApi) &&
|
||||
apiPublishesDescription(parentApi);
|
||||
|
|
|
@ -263,8 +263,8 @@ describe('Dashboard link component', () => {
|
|||
|
||||
test('current dashboard is not a clickable href', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
parentApi.savedObjectId = new BehaviorSubject<string | undefined>('123');
|
||||
parentApi.panelTitle = new BehaviorSubject<string | undefined>('current dashboard');
|
||||
parentApi.savedObjectId$ = new BehaviorSubject<string | undefined>('123');
|
||||
parentApi.title$ = new BehaviorSubject<string | undefined>('current dashboard');
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
|
@ -310,9 +310,9 @@ describe('Dashboard link component', () => {
|
|||
test('current dashboard title updates when parent changes', async () => {
|
||||
const parentApi = {
|
||||
...createMockLinksParent({}),
|
||||
panelTitle: new BehaviorSubject<string | undefined>('old title'),
|
||||
panelDescription: new BehaviorSubject<string | undefined>('old description'),
|
||||
savedObjectId: new BehaviorSubject<string | undefined>('123'),
|
||||
title$: new BehaviorSubject<string | undefined>('old title'),
|
||||
description$: new BehaviorSubject<string | undefined>('old description'),
|
||||
savedObjectId$: new BehaviorSubject<string | undefined>('123'),
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
|
@ -328,7 +328,7 @@ describe('Dashboard link component', () => {
|
|||
);
|
||||
expect(await screen.findByTestId('dashboardLink--bar')).toHaveTextContent('old title');
|
||||
|
||||
parentApi.panelTitle.next('new title');
|
||||
parentApi.title$.next('new title');
|
||||
rerender(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
|
@ -367,7 +367,7 @@ describe('Dashboard link component', () => {
|
|||
test('can override link label for the current dashboard', async () => {
|
||||
const customLabel = 'my new label for the current dashboard';
|
||||
const parentApi = createMockLinksParent({});
|
||||
parentApi.savedObjectId = new BehaviorSubject<string | undefined>('123');
|
||||
parentApi.savedObjectId$ = new BehaviorSubject<string | undefined>('123');
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
|
|
|
@ -46,9 +46,9 @@ export const DashboardLinkComponent = ({
|
|||
filters,
|
||||
query,
|
||||
] = useBatchedPublishingSubjects(
|
||||
parentApi.savedObjectId,
|
||||
parentApi.panelTitle,
|
||||
parentApi.panelDescription,
|
||||
parentApi.savedObjectId$,
|
||||
parentApi.title$,
|
||||
parentApi.description$,
|
||||
parentApi.timeRange$,
|
||||
parentApi.filters$,
|
||||
parentApi.query$
|
||||
|
|
|
@ -60,7 +60,7 @@ export async function openEditorFlyout({
|
|||
|
||||
const parentDashboardId =
|
||||
parentDashboard && apiPublishesSavedObjectId(parentDashboard)
|
||||
? parentDashboard.savedObjectId.value
|
||||
? parentDashboard.savedObjectId$.value
|
||||
: undefined;
|
||||
|
||||
return new Promise<LinksRuntimeState | undefined>((resolve) => {
|
||||
|
|
|
@ -218,8 +218,8 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
references: [],
|
||||
});
|
||||
expect(await api.canUnlinkFromLibrary()).toBe(true);
|
||||
expect(api.defaultPanelTitle!.value).toBe('links 001');
|
||||
expect(api.defaultPanelDescription!.value).toBe('some links');
|
||||
expect(api.defaultTitle$?.value).toBe('links 001');
|
||||
expect(api.defaultDescription$?.value).toBe('some links');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { EuiListGroup, EuiPanel } from '@elastic/eui';
|
|||
import { PanelIncompatibleError, ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
SerializedTitles,
|
||||
initializeTitles,
|
||||
initializeTitleManager,
|
||||
SerializedPanelState,
|
||||
useBatchedOptionalPublishingSubjects,
|
||||
} from '@kbn/presentation-publishing';
|
||||
|
@ -94,25 +94,25 @@ export const getLinksEmbeddableFactory = () => {
|
|||
};
|
||||
},
|
||||
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
|
||||
const error$ = new BehaviorSubject<Error | undefined>(state.error);
|
||||
if (!isParentApiCompatible(parentApi)) error$.next(new PanelIncompatibleError());
|
||||
const blockingError$ = new BehaviorSubject<Error | undefined>(state.error);
|
||||
if (!isParentApiCompatible(parentApi)) blockingError$.next(new PanelIncompatibleError());
|
||||
|
||||
const links$ = new BehaviorSubject<ResolvedLink[] | undefined>(state.links);
|
||||
const layout$ = new BehaviorSubject<LinksLayoutType | undefined>(state.layout);
|
||||
const defaultPanelTitle = new BehaviorSubject<string | undefined>(state.defaultPanelTitle);
|
||||
const defaultPanelDescription = new BehaviorSubject<string | undefined>(
|
||||
const defaultTitle$ = new BehaviorSubject<string | undefined>(state.defaultPanelTitle);
|
||||
const defaultDescription$ = new BehaviorSubject<string | undefined>(
|
||||
state.defaultPanelDescription
|
||||
);
|
||||
const savedObjectId$ = new BehaviorSubject(state.savedObjectId);
|
||||
const isByReference = Boolean(state.savedObjectId);
|
||||
|
||||
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
|
||||
const titleManager = initializeTitleManager(state);
|
||||
|
||||
const serializeLinksState = (byReference: boolean, newId?: string) => {
|
||||
if (byReference) {
|
||||
const linksByReferenceState: LinksByReferenceSerializedState = {
|
||||
savedObjectId: newId ?? state.savedObjectId!,
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
};
|
||||
return { rawState: linksByReferenceState, references: [] };
|
||||
}
|
||||
|
@ -120,22 +120,22 @@ export const getLinksEmbeddableFactory = () => {
|
|||
const { attributes, references } = serializeLinksAttributes(runtimeState);
|
||||
const linksByValueState: LinksByValueSerializedState = {
|
||||
attributes,
|
||||
...serializeTitles(),
|
||||
...titleManager.serialize(),
|
||||
};
|
||||
return { rawState: linksByValueState, references };
|
||||
};
|
||||
|
||||
const api = buildApi(
|
||||
{
|
||||
...titlesApi,
|
||||
blockingError: error$,
|
||||
defaultPanelTitle,
|
||||
defaultPanelDescription,
|
||||
isEditingEnabled: () => Boolean(error$.value === undefined),
|
||||
...titleManager.api,
|
||||
blockingError$,
|
||||
defaultTitle$,
|
||||
defaultDescription$,
|
||||
isEditingEnabled: () => Boolean(blockingError$.value === undefined),
|
||||
getTypeDisplayName: () => DISPLAY_NAME,
|
||||
serializeState: () => serializeLinksState(isByReference),
|
||||
saveToLibrary: async (newTitle: string) => {
|
||||
defaultPanelTitle.next(newTitle);
|
||||
defaultTitle$.next(newTitle);
|
||||
const runtimeState = api.snapshotRuntimeState();
|
||||
const { attributes, references } = serializeLinksAttributes(runtimeState);
|
||||
const {
|
||||
|
@ -196,26 +196,23 @@ export const getLinksEmbeddableFactory = () => {
|
|||
}
|
||||
links$.next(newState.links);
|
||||
layout$.next(newState.layout);
|
||||
defaultPanelTitle.next(newState.defaultPanelTitle);
|
||||
defaultPanelDescription.next(newState.defaultPanelDescription);
|
||||
defaultTitle$.next(newState.defaultPanelTitle);
|
||||
defaultDescription$.next(newState.defaultPanelDescription);
|
||||
},
|
||||
},
|
||||
{
|
||||
...titleComparators,
|
||||
...titleManager.comparators,
|
||||
links: [links$, (nextLinks?: ResolvedLink[]) => links$.next(nextLinks ?? [])],
|
||||
layout: [
|
||||
layout$,
|
||||
(nextLayout?: LinksLayoutType) => layout$.next(nextLayout ?? LINKS_VERTICAL_LAYOUT),
|
||||
],
|
||||
error: [error$, (nextError?: Error) => error$.next(nextError)],
|
||||
error: [blockingError$, (nextError?: Error) => blockingError$.next(nextError)],
|
||||
defaultPanelDescription: [
|
||||
defaultPanelDescription,
|
||||
(nextDescription?: string) => defaultPanelDescription.next(nextDescription),
|
||||
],
|
||||
defaultPanelTitle: [
|
||||
defaultPanelTitle,
|
||||
(nextTitle?: string) => defaultPanelTitle.next(nextTitle),
|
||||
defaultDescription$,
|
||||
(nextDescription?: string) => defaultDescription$.next(nextDescription),
|
||||
],
|
||||
defaultPanelTitle: [defaultTitle$, (nextTitle?: string) => defaultTitle$.next(nextTitle)],
|
||||
savedObjectId: [savedObjectId$, (val) => savedObjectId$.next(val)],
|
||||
}
|
||||
);
|
||||
|
|
|
@ -58,9 +58,9 @@ export const getMockLinksParentApi = (
|
|||
to: 'now',
|
||||
}),
|
||||
timeslice$: new BehaviorSubject<[number, number] | undefined>(undefined),
|
||||
savedObjectId: new BehaviorSubject<string | undefined>('999'),
|
||||
hidePanelTitle: new BehaviorSubject<boolean | undefined>(false),
|
||||
panelTitle: new BehaviorSubject<string | undefined>('My Dashboard'),
|
||||
panelDescription: new BehaviorSubject<string | undefined>(''),
|
||||
savedObjectId$: new BehaviorSubject<string | undefined>('999'),
|
||||
hideTitle$: new BehaviorSubject<boolean | undefined>(false),
|
||||
title$: new BehaviorSubject<string | undefined>('My Dashboard'),
|
||||
description$: new BehaviorSubject<string | undefined>(''),
|
||||
getSerializedStateForChild: () => ({ rawState: serializedState, references }),
|
||||
});
|
||||
|
|
|
@ -11,8 +11,8 @@ import {
|
|||
HasEditCapabilities,
|
||||
HasLibraryTransforms,
|
||||
HasType,
|
||||
PublishesPanelDescription,
|
||||
PublishesPanelTitle,
|
||||
PublishesDescription,
|
||||
PublishesTitle,
|
||||
PublishesSavedObjectId,
|
||||
PublishesUnifiedSearch,
|
||||
SerializedTitles,
|
||||
|
@ -31,8 +31,8 @@ export type LinksParentApi = PresentationContainer &
|
|||
HasType<typeof DASHBOARD_API_TYPE> &
|
||||
HasSerializedChildState<LinksSerializedState> &
|
||||
PublishesSavedObjectId &
|
||||
PublishesPanelTitle &
|
||||
PublishesPanelDescription &
|
||||
PublishesTitle &
|
||||
PublishesDescription &
|
||||
PublishesUnifiedSearch & {
|
||||
locator?: Pick<LocatorPublic<DashboardLocatorParams>, 'navigate' | 'getRedirectUrl'>;
|
||||
};
|
||||
|
|
|
@ -25,8 +25,8 @@ describe('Customize panel action', () => {
|
|||
context = {
|
||||
embeddable: {
|
||||
parentApi: {},
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>(undefined),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews$: new BehaviorSubject<DataView[] | undefined>(undefined),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ describe('Customize panel action', () => {
|
|||
});
|
||||
|
||||
it('is compatible in view mode when API exposes writable unified search', async () => {
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode$ = new BehaviorSubject<ViewMode>('view');
|
||||
context.embeddable.timeRange$ = new BehaviorSubject<TimeRange | undefined>({
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
|
|
|
@ -13,15 +13,15 @@ import {
|
|||
apiCanAccessViewMode,
|
||||
apiPublishesDataViews,
|
||||
apiPublishesUnifiedSearch,
|
||||
apiPublishesPanelTitle,
|
||||
apiPublishesTitle,
|
||||
CanAccessViewMode,
|
||||
EmbeddableApiContext,
|
||||
getInheritedViewMode,
|
||||
HasParentApi,
|
||||
PublishesDataViews,
|
||||
PublishesWritableUnifiedSearch,
|
||||
PublishesWritablePanelDescription,
|
||||
PublishesWritablePanelTitle,
|
||||
PublishesWritableDescription,
|
||||
PublishesWritableTitle,
|
||||
PublishesUnifiedSearch,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -32,15 +32,15 @@ export type CustomizePanelActionApi = CanAccessViewMode &
|
|||
Partial<
|
||||
PublishesDataViews &
|
||||
PublishesWritableUnifiedSearch &
|
||||
PublishesWritablePanelDescription &
|
||||
PublishesWritablePanelTitle &
|
||||
PublishesWritableDescription &
|
||||
PublishesWritableTitle &
|
||||
HasParentApi<Partial<PublishesUnifiedSearch & TracksOverlays>>
|
||||
>;
|
||||
|
||||
export const isApiCompatibleWithCustomizePanelAction = (
|
||||
api: unknown | null
|
||||
): api is CustomizePanelActionApi =>
|
||||
apiCanAccessViewMode(api) && (apiPublishesDataViews(api) || apiPublishesPanelTitle(api));
|
||||
apiCanAccessViewMode(api) && (apiPublishesDataViews(api) || apiPublishesTitle(api));
|
||||
|
||||
export class CustomizePanelAction implements Action<EmbeddableApiContext> {
|
||||
public type = ACTION_CUSTOMIZE_PANEL;
|
||||
|
|
|
@ -25,20 +25,20 @@ describe('customize panel editor', () => {
|
|||
let setDescription: (description?: string) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
const titleSubject = new BehaviorSubject<string | undefined>(undefined);
|
||||
setTitle = jest.fn((title) => titleSubject.next(title));
|
||||
const descriptionSubject = new BehaviorSubject<string | undefined>(undefined);
|
||||
setDescription = jest.fn((description) => descriptionSubject.next(description));
|
||||
const viewMode = new BehaviorSubject<ViewMode>('edit');
|
||||
setViewMode = jest.fn((nextViewMode) => viewMode.next(nextViewMode));
|
||||
const title$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
setTitle = jest.fn((title) => title$.next(title));
|
||||
const description$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
setDescription = jest.fn((description) => description$.next(description));
|
||||
const viewMode$ = new BehaviorSubject<ViewMode>('edit');
|
||||
setViewMode = jest.fn((nextViewMode) => viewMode$.next(nextViewMode));
|
||||
|
||||
api = {
|
||||
viewMode,
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
panelTitle: titleSubject,
|
||||
setPanelTitle: setTitle,
|
||||
panelDescription: descriptionSubject,
|
||||
setPanelDescription: setDescription,
|
||||
viewMode$,
|
||||
dataViews$: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
title$,
|
||||
setTitle,
|
||||
description$,
|
||||
setDescription,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -61,7 +61,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('Initializes panel title with default title from API', () => {
|
||||
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
|
||||
api.defaultTitle$ = new BehaviorSubject<string | undefined>('Default title');
|
||||
renderPanelEditor();
|
||||
expect(screen.getByTestId('customEmbeddablePanelTitleInput')).toHaveValue('Default title');
|
||||
});
|
||||
|
@ -82,7 +82,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should use default title when title is undefined', () => {
|
||||
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
|
||||
api.defaultTitle$ = new BehaviorSubject<string | undefined>('Default title');
|
||||
setTitle(undefined);
|
||||
renderPanelEditor();
|
||||
const titleInput = screen.getByTestId('customEmbeddablePanelTitleInput');
|
||||
|
@ -90,7 +90,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should use title even when empty string', () => {
|
||||
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
|
||||
api.defaultTitle$ = new BehaviorSubject<string | undefined>('Default title');
|
||||
setTitle('');
|
||||
renderPanelEditor();
|
||||
const titleInput = screen.getByTestId('customEmbeddablePanelTitleInput');
|
||||
|
@ -98,7 +98,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('Resets panel title to default when reset button is pressed', async () => {
|
||||
api.defaultPanelTitle = new BehaviorSubject<string | undefined>('Default title');
|
||||
api.defaultTitle$ = new BehaviorSubject<string | undefined>('Default title');
|
||||
setTitle('Initial title');
|
||||
renderPanelEditor();
|
||||
await userEvent.type(screen.getByTestId('customEmbeddablePanelTitleInput'), 'New title');
|
||||
|
@ -107,7 +107,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should hide title reset when no default exists', async () => {
|
||||
api.defaultPanelTitle = new BehaviorSubject<string | undefined>(undefined);
|
||||
api.defaultTitle$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
setTitle('Initial title');
|
||||
renderPanelEditor();
|
||||
await userEvent.type(screen.getByTestId('customEmbeddablePanelTitleInput'), 'New title');
|
||||
|
@ -129,7 +129,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('Initializes panel description with default description from API', () => {
|
||||
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
|
||||
api.defaultDescription$ = new BehaviorSubject<string | undefined>('Default description');
|
||||
renderPanelEditor();
|
||||
expect(screen.getByTestId('customEmbeddablePanelDescriptionInput')).toHaveValue(
|
||||
'Default description'
|
||||
|
@ -155,7 +155,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should use default description when description is undefined', () => {
|
||||
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
|
||||
api.defaultDescription$ = new BehaviorSubject<string | undefined>('Default description');
|
||||
setDescription(undefined);
|
||||
renderPanelEditor();
|
||||
const descriptionInput = screen.getByTestId('customEmbeddablePanelDescriptionInput');
|
||||
|
@ -163,7 +163,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should use description even when empty string', () => {
|
||||
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
|
||||
api.defaultDescription$ = new BehaviorSubject<string | undefined>('Default description');
|
||||
setDescription('');
|
||||
renderPanelEditor();
|
||||
const descriptionInput = screen.getByTestId('customEmbeddablePanelDescriptionInput');
|
||||
|
@ -171,7 +171,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('Resets panel description to default when reset button is pressed', async () => {
|
||||
api.defaultPanelDescription = new BehaviorSubject<string | undefined>('Default description');
|
||||
api.defaultDescription$ = new BehaviorSubject<string | undefined>('Default description');
|
||||
setDescription('Initial description');
|
||||
renderPanelEditor();
|
||||
await userEvent.type(
|
||||
|
@ -185,7 +185,7 @@ describe('customize panel editor', () => {
|
|||
});
|
||||
|
||||
it('should hide description reset when no default exists', async () => {
|
||||
api.defaultPanelDescription = new BehaviorSubject<string | undefined>(undefined);
|
||||
api.defaultDescription$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
setDescription('Initial description');
|
||||
renderPanelEditor();
|
||||
await userEvent.type(
|
||||
|
|
|
@ -34,8 +34,8 @@ import {
|
|||
apiPublishesTimeRange,
|
||||
apiPublishesUnifiedSearch,
|
||||
getInheritedViewMode,
|
||||
getPanelDescription,
|
||||
getPanelTitle,
|
||||
getDescription,
|
||||
getTitle,
|
||||
PublishesUnifiedSearch,
|
||||
} from '@kbn/presentation-publishing';
|
||||
|
||||
|
@ -63,9 +63,9 @@ export const CustomizePanelEditor = ({
|
|||
* For now, we copy the state here with `useState` initializing it to the latest value.
|
||||
*/
|
||||
const editMode = getInheritedViewMode(api) === 'edit';
|
||||
const [hideTitle, setHideTitle] = useState(api.hidePanelTitle?.value);
|
||||
const [panelTitle, setPanelTitle] = useState(getPanelTitle(api));
|
||||
const [panelDescription, setPanelDescription] = useState(getPanelDescription(api));
|
||||
const [hideTitle, setHideTitle] = useState(api.hideTitle$?.value);
|
||||
const [panelTitle, setPanelTitle] = useState(getTitle(api));
|
||||
const [panelDescription, setPanelDescription] = useState(getDescription(api));
|
||||
const [timeRange, setTimeRange] = useState(
|
||||
api.timeRange$?.value ?? api.parentApi?.timeRange$?.value
|
||||
);
|
||||
|
@ -99,10 +99,9 @@ export const CustomizePanelEditor = ({
|
|||
const dateFormat = useMemo(() => core.uiSettings.get<string>(UI_SETTINGS.DATE_FORMAT), []);
|
||||
|
||||
const save = () => {
|
||||
if (panelTitle !== api.panelTitle?.value) api.setPanelTitle?.(panelTitle);
|
||||
if (hideTitle !== api.hidePanelTitle?.value) api.setHidePanelTitle?.(hideTitle);
|
||||
if (panelDescription !== api.panelDescription?.value)
|
||||
api.setPanelDescription?.(panelDescription);
|
||||
if (panelTitle !== api.title$?.value) api.setTitle?.(panelTitle);
|
||||
if (hideTitle !== api.hideTitle$?.value) api.setHideTitle?.(hideTitle);
|
||||
if (panelDescription !== api.description$?.value) api.setDescription?.(panelDescription);
|
||||
|
||||
const newTimeRange = hasOwnTimeRange ? timeRange : undefined;
|
||||
if (newTimeRange !== api.timeRange$?.value) {
|
||||
|
@ -139,12 +138,12 @@ export const CustomizePanelEditor = ({
|
|||
/>
|
||||
}
|
||||
labelAppend={
|
||||
api?.defaultPanelTitle?.value && (
|
||||
api?.defaultTitle$?.value && (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="resetCustomEmbeddablePanelTitleButton"
|
||||
onClick={() => setPanelTitle(api.defaultPanelTitle?.value)}
|
||||
disabled={hideTitle || panelTitle === api?.defaultPanelTitle?.value}
|
||||
onClick={() => setPanelTitle(api.defaultTitle$?.value)}
|
||||
disabled={hideTitle || panelTitle === api?.defaultTitle$?.value}
|
||||
aria-label={i18n.translate(
|
||||
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomTitleButtonAriaLabel',
|
||||
{
|
||||
|
@ -186,12 +185,12 @@ export const CustomizePanelEditor = ({
|
|||
/>
|
||||
}
|
||||
labelAppend={
|
||||
api.defaultPanelDescription?.value && (
|
||||
api.defaultDescription$?.value && (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="resetCustomEmbeddablePanelDescriptionButton"
|
||||
onClick={() => setPanelDescription(api.defaultPanelDescription?.value)}
|
||||
disabled={api.defaultPanelDescription?.value === panelDescription}
|
||||
onClick={() => setPanelDescription(api.defaultDescription$?.value)}
|
||||
disabled={api.defaultDescription$?.value === panelDescription}
|
||||
aria-label={i18n.translate(
|
||||
'presentationPanel.action.customizePanel.flyout.optionsMenuForm.resetCustomDescriptionButtonAriaLabel',
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ interface FiltersDetailsProps {
|
|||
export function FiltersDetails({ editMode, api }: FiltersDetailsProps) {
|
||||
const [queryString, setQueryString] = useState<string>('');
|
||||
const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>();
|
||||
const dataViews = api.dataViews?.value ?? [];
|
||||
const dataViews = api.dataViews$?.value ?? [];
|
||||
|
||||
const filters = useMemo(() => api.filters$?.value ?? [], [api]);
|
||||
|
||||
|
|
|
@ -14,16 +14,16 @@ import { EditPanelAction, EditPanelActionApi } from './edit_panel_action';
|
|||
describe('Edit panel action', () => {
|
||||
let action: EditPanelAction;
|
||||
let context: { embeddable: EditPanelActionApi };
|
||||
let updateViewMode: (viewMode: ViewMode) => void;
|
||||
let setViewMode: (viewMode: ViewMode) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
const viewModeSubject = new BehaviorSubject<ViewMode>('edit');
|
||||
updateViewMode = (viewMode) => viewModeSubject.next(viewMode);
|
||||
const viewMode$ = new BehaviorSubject<ViewMode>('edit');
|
||||
setViewMode = (viewMode) => viewMode$.next(viewMode);
|
||||
|
||||
action = new EditPanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
viewMode: viewModeSubject,
|
||||
viewMode$,
|
||||
onEdit: jest.fn(),
|
||||
isEditingEnabled: jest.fn().mockReturnValue(true),
|
||||
getTypeDisplayName: jest.fn().mockReturnValue('A very fun panel type'),
|
||||
|
@ -43,7 +43,7 @@ describe('Edit panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode$ = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -66,7 +66,7 @@ describe('Edit panel action', () => {
|
|||
it('calls onChange when view mode changes', () => {
|
||||
const onChange = jest.fn();
|
||||
action.subscribeToCompatibilityChanges(context, onChange);
|
||||
updateViewMode('view');
|
||||
setViewMode('view');
|
||||
expect(onChange).toHaveBeenCalledWith(false, action);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,16 +12,15 @@ import { apiHasInspectorAdapters, HasInspectorAdapters } from '@kbn/inspector-pl
|
|||
import { tracksOverlays } from '@kbn/presentation-containers';
|
||||
import {
|
||||
EmbeddableApiContext,
|
||||
getPanelTitle,
|
||||
PublishesPanelTitle,
|
||||
getTitle,
|
||||
PublishesTitle,
|
||||
HasParentApi,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { ACTION_INSPECT_PANEL } from './constants';
|
||||
import { inspector } from '../../kibana_services';
|
||||
|
||||
export type InspectPanelActionApi = HasInspectorAdapters &
|
||||
Partial<PublishesPanelTitle & HasParentApi>;
|
||||
export type InspectPanelActionApi = HasInspectorAdapters & Partial<PublishesTitle & HasParentApi>;
|
||||
const isApiCompatible = (api: unknown | null): api is InspectPanelActionApi => {
|
||||
return Boolean(api) && apiHasInspectorAdapters(api);
|
||||
};
|
||||
|
@ -57,7 +56,7 @@ export class InspectPanelAction implements Action<EmbeddableApiContext> {
|
|||
}
|
||||
|
||||
const panelTitle =
|
||||
getPanelTitle(embeddable) ||
|
||||
getTitle(embeddable) ||
|
||||
i18n.translate('presentationPanel.action.inspectPanel.untitledEmbeddableFilename', {
|
||||
defaultMessage: '[No Title]',
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('Remove panel action', () => {
|
|||
context = {
|
||||
embeddable: {
|
||||
uuid: 'superId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: getMockPresentationContainer(),
|
||||
},
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ describe('Remove panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
context.embeddable.viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
context.embeddable.viewMode$ = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -209,12 +209,12 @@ export const PresentationPanelHoverActions = ({
|
|||
parentHideTitle,
|
||||
parentViewMode,
|
||||
] = useBatchedOptionalPublishingSubjects(
|
||||
api?.defaultPanelTitle,
|
||||
api?.panelTitle,
|
||||
api?.panelDescription,
|
||||
api?.hidePanelTitle,
|
||||
api?.defaultTitle$,
|
||||
api?.title$,
|
||||
api?.description$,
|
||||
api?.hideTitle$,
|
||||
api?.hasLockedHoverActions$,
|
||||
api?.parentApi?.hidePanelTitle,
|
||||
api?.parentApi?.hideTitle$,
|
||||
/**
|
||||
* View mode changes often have the biggest influence over which actions will be compatible,
|
||||
* so we build and update all actions when the view mode changes. This is temporary, as these
|
||||
|
@ -332,7 +332,7 @@ export const PresentationPanelHoverActions = ({
|
|||
})()) as AnyApiAction[];
|
||||
if (canceled) return;
|
||||
|
||||
const disabledActions = api.disabledActionIds?.value;
|
||||
const disabledActions = api.disabledActionIds$?.value;
|
||||
if (disabledActions) {
|
||||
compatibleActions = compatibleActions.filter(
|
||||
(action) => disabledActions.indexOf(action.id) === -1
|
||||
|
|
|
@ -50,7 +50,7 @@ export const usePresentationPanelHeaderActions = <
|
|||
embeddable: api,
|
||||
})) as AnyApiAction[]) ?? [];
|
||||
|
||||
const disabledActions = (api.disabledActionIds?.value ?? []).concat(disabledNotifications);
|
||||
const disabledActions = (api.disabledActionIds$?.value ?? []).concat(disabledNotifications);
|
||||
nextActions = nextActions.filter((badge) => disabledActions.indexOf(badge.id) === -1);
|
||||
return nextActions;
|
||||
};
|
||||
|
|
|
@ -62,7 +62,7 @@ export const PresentationPanelErrorInternal = ({ api, error }: PresentationPanel
|
|||
});
|
||||
}, [api, isEditable]);
|
||||
|
||||
const panelTitle = useStateFromPublishingSubject(api?.panelTitle);
|
||||
const panelTitle = useStateFromPublishingSubject(api?.title$);
|
||||
const ariaLabel = useMemo(
|
||||
() =>
|
||||
panelTitle
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('Presentation panel', () => {
|
|||
it('renders a blocking error when one is present', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
blockingError: new BehaviorSubject<Error | undefined>(new Error('UH OH')),
|
||||
blockingError$: new BehaviorSubject<Error | undefined>(new Error('UH OH')),
|
||||
};
|
||||
render(<PresentationPanel Component={getMockPresentationPanelCompatibleComponent(api)} />);
|
||||
await waitFor(() => expect(screen.getByTestId('embeddableStackError')).toBeInTheDocument());
|
||||
|
@ -91,7 +91,7 @@ describe('Presentation panel', () => {
|
|||
it('gets compatible actions for the given API', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('superTest'),
|
||||
title$: new BehaviorSubject<string | undefined>('superTest'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(uiActions.getTriggerCompatibleActions).toHaveBeenCalledWith('CONTEXT_MENU_TRIGGER', {
|
||||
|
@ -116,7 +116,7 @@ describe('Presentation panel', () => {
|
|||
it('does not show actions which are disabled by the API', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
disabledActionIds: new BehaviorSubject<string[] | undefined>(['actionA']),
|
||||
disabledActionIds$: new BehaviorSubject<string[] | undefined>(['actionA']),
|
||||
};
|
||||
const getActions = jest.fn().mockReturnValue([mockAction('actionA'), mockAction('actionB')]);
|
||||
await renderPresentationPanel({ api, props: { getActions } });
|
||||
|
@ -161,8 +161,8 @@ describe('Presentation panel', () => {
|
|||
it('renders the panel title from the api and not the default title', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>('SO Title'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>('SO Title'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -173,7 +173,7 @@ describe('Presentation panel', () => {
|
|||
it('renders the default title from the api when a panel title is not provided', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>('SO Title'),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>('SO Title'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -184,7 +184,7 @@ describe('Presentation panel', () => {
|
|||
it("does not render an info icon when the api doesn't provide a panel description or default description", async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -195,8 +195,8 @@ describe('Presentation panel', () => {
|
|||
it('renders an info icon when the api provides a panel description', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
panelDescription: new BehaviorSubject<string | undefined>('SUPER DESCRIPTION'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
description$: new BehaviorSubject<string | undefined>('SUPER DESCRIPTION'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -207,8 +207,8 @@ describe('Presentation panel', () => {
|
|||
it('renders an info icon when the api provides a default description', async () => {
|
||||
const api: DefaultPresentationPanelApi = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
defaultPanelDescription: new BehaviorSubject<string | undefined>('SO Description'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
defaultDescription$: new BehaviorSubject<string | undefined>('SO Description'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -219,8 +219,8 @@ describe('Presentation panel', () => {
|
|||
it('does not render a title when in view mode when the provided title is blank', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
title$: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
|
||||
|
@ -229,9 +229,9 @@ describe('Presentation panel', () => {
|
|||
it('does not render a title when in edit mode and the provided title is blank', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
title$: new BehaviorSubject<string | undefined>(''),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews$: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
|
||||
|
@ -242,9 +242,9 @@ describe('Presentation panel', () => {
|
|||
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
title$: new BehaviorSubject<string | undefined>('TITLE'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
dataViews$: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -259,9 +259,9 @@ describe('Presentation panel', () => {
|
|||
it('does not show title customize link in view mode', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesDataViews & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
dataViews: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
dataViews$: new BehaviorSubject<DataView[] | undefined>([]),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
await waitFor(() => {
|
||||
|
@ -273,9 +273,9 @@ describe('Presentation panel', () => {
|
|||
it('hides title in view mode when API hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
hidePanelTitle: new BehaviorSubject<boolean | undefined>(true),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
hideTitle$: new BehaviorSubject<boolean | undefined>(true),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
|
||||
|
@ -284,9 +284,9 @@ describe('Presentation panel', () => {
|
|||
it('hides title in edit mode when API hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
hidePanelTitle: new BehaviorSubject<boolean | undefined>(true),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
hideTitle$: new BehaviorSubject<boolean | undefined>(true),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
};
|
||||
await renderPresentationPanel({ api });
|
||||
expect(screen.queryByTestId('presentationPanelTitle')).not.toBeInTheDocument();
|
||||
|
@ -295,10 +295,10 @@ describe('Presentation panel', () => {
|
|||
it('hides title in view mode when parent hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
parentApi: {
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
...getMockPresentationContainer(),
|
||||
},
|
||||
};
|
||||
|
@ -309,10 +309,10 @@ describe('Presentation panel', () => {
|
|||
it('hides title in edit mode when parent hide title option is true', async () => {
|
||||
const api: DefaultPresentationPanelApi & PublishesViewMode = {
|
||||
uuid: 'test',
|
||||
panelTitle: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
title$: new BehaviorSubject<string | undefined>('SUPER TITLE'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
parentApi: {
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
...getMockPresentationContainer(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -50,8 +50,8 @@ export const PresentationPanelInternal = <
|
|||
const dragHandles = useRef<{ [dragHandleKey: string]: HTMLElement | null }>({});
|
||||
|
||||
const viewModeSubject = (() => {
|
||||
if (apiPublishesViewMode(api)) return api.viewMode;
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) return api.parentApi.viewMode;
|
||||
if (apiPublishesViewMode(api)) return api.viewMode$;
|
||||
if (apiHasParentApi(api) && apiPublishesViewMode(api.parentApi)) return api.parentApi.viewMode$;
|
||||
})();
|
||||
|
||||
const [
|
||||
|
@ -65,20 +65,20 @@ export const PresentationPanelInternal = <
|
|||
rawViewMode,
|
||||
parentHidePanelTitle,
|
||||
] = useBatchedOptionalPublishingSubjects(
|
||||
api?.dataLoading,
|
||||
api?.blockingError,
|
||||
api?.panelTitle,
|
||||
api?.hidePanelTitle,
|
||||
api?.panelDescription,
|
||||
api?.defaultPanelTitle,
|
||||
api?.defaultPanelDescription,
|
||||
api?.dataLoading$,
|
||||
api?.blockingError$,
|
||||
api?.title$,
|
||||
api?.hideTitle$,
|
||||
api?.description$,
|
||||
api?.defaultTitle$,
|
||||
api?.defaultDescription$,
|
||||
viewModeSubject,
|
||||
api?.parentApi?.hidePanelTitle
|
||||
api?.parentApi?.hideTitle$
|
||||
);
|
||||
const viewMode = rawViewMode ?? 'view';
|
||||
|
||||
const [initialLoadComplete, setInitialLoadComplete] = useState(!dataLoading);
|
||||
if (!initialLoadComplete && (dataLoading === false || (api && !api.dataLoading))) {
|
||||
if (!initialLoadComplete && (dataLoading === false || (api && !api.dataLoading$))) {
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ import {
|
|||
PublishesBlockingError,
|
||||
PublishesDataLoading,
|
||||
PublishesDisabledActionIds,
|
||||
PublishesPanelDescription,
|
||||
PublishesPanelTitle,
|
||||
PublishesDescription,
|
||||
PublishesTitle,
|
||||
PublishesViewMode,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { UiActionsService } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -74,14 +74,13 @@ export interface PresentationPanelInternalProps<
|
|||
export interface DefaultPresentationPanelApi
|
||||
extends HasUniqueId,
|
||||
Partial<
|
||||
PublishesPanelTitle &
|
||||
PublishesTitle &
|
||||
PublishesDataLoading &
|
||||
PublishesBlockingError &
|
||||
PublishesPanelDescription &
|
||||
PublishesDescription &
|
||||
PublishesDisabledActionIds &
|
||||
HasParentApi<
|
||||
PresentationContainer &
|
||||
Partial<Pick<PublishesPanelTitle, 'hidePanelTitle'> & PublishesViewMode>
|
||||
PresentationContainer & Partial<Pick<PublishesTitle, 'hideTitle$'> & PublishesViewMode>
|
||||
> &
|
||||
CanLockHoverActions
|
||||
> {}
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ClearControlAction } from './clear_control_action';
|
|||
import type { ViewMode } from '@kbn/presentation-publishing';
|
||||
|
||||
const dashboardApi = {
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
const controlGroupApi = getMockedControlGroupApi(dashboardApi, {
|
||||
removePanel: jest.fn(),
|
||||
|
|
|
@ -17,7 +17,7 @@ import { coreServices } from '../services/kibana_services';
|
|||
import { DeleteControlAction } from './delete_control_action';
|
||||
|
||||
const dashboardApi = {
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
const controlGroupApi = getMockedControlGroupApi(dashboardApi, {
|
||||
removePanel: jest.fn(),
|
||||
|
|
|
@ -29,7 +29,7 @@ dataService.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange)
|
|||
};
|
||||
|
||||
const dashboardApi = {
|
||||
viewMode: new BehaviorSubject<ViewMode>('view'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('view'),
|
||||
};
|
||||
const controlGroupApi = getMockedControlGroupApi(dashboardApi, {
|
||||
removePanel: jest.fn(),
|
||||
|
@ -88,7 +88,7 @@ describe('Incompatible embeddables', () => {
|
|||
|
||||
describe('Compatible embeddables', () => {
|
||||
beforeAll(() => {
|
||||
dashboardApi.viewMode.next('edit');
|
||||
dashboardApi.viewMode$.next('edit');
|
||||
});
|
||||
|
||||
test('Action is compatible with embeddables that are editable', async () => {
|
||||
|
|
|
@ -31,8 +31,8 @@ export const ControlClone = ({
|
|||
}) => {
|
||||
const [width, panelTitle, defaultPanelTitle] = useBatchedPublishingSubjects(
|
||||
controlApi ? controlApi.width : new BehaviorSubject(DEFAULT_CONTROL_GROW),
|
||||
controlApi?.panelTitle ? controlApi.panelTitle : new BehaviorSubject(undefined),
|
||||
controlApi?.defaultPanelTitle ? controlApi.defaultPanelTitle : new BehaviorSubject('')
|
||||
controlApi?.title$ ? controlApi.title$ : new BehaviorSubject(undefined),
|
||||
controlApi?.defaultTitle$ ? controlApi.defaultTitle$ : new BehaviorSubject('')
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -87,7 +87,7 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA
|
|||
apiHasParentApi(api.parentApi) && // api.parentApi => controlGroupApi
|
||||
apiPublishesViewMode(api.parentApi.parentApi) // controlGroupApi.parentApi => dashboardApi
|
||||
)
|
||||
return api.parentApi.parentApi.viewMode; // get view mode from dashboard API
|
||||
return api.parentApi.parentApi.viewMode$; // get view mode from dashboard API
|
||||
})();
|
||||
|
||||
const [
|
||||
|
@ -101,21 +101,21 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA
|
|||
disabledActionIds,
|
||||
rawViewMode,
|
||||
] = useBatchedOptionalPublishingSubjects(
|
||||
api?.dataLoading,
|
||||
api?.blockingError,
|
||||
api?.panelTitle,
|
||||
api?.defaultPanelTitle,
|
||||
api?.dataLoading$,
|
||||
api?.blockingError$,
|
||||
api?.title$,
|
||||
api?.defaultTitle$,
|
||||
api?.grow,
|
||||
api?.width,
|
||||
api?.parentApi?.labelPosition,
|
||||
api?.parentApi?.disabledActionIds,
|
||||
api?.parentApi?.disabledActionIds$,
|
||||
viewModeSubject
|
||||
);
|
||||
const usingTwoLineLayout = labelPosition === 'twoLine';
|
||||
const controlType = api ? api.type : undefined;
|
||||
|
||||
const [initialLoadComplete, setInitialLoadComplete] = useState(!dataLoading);
|
||||
if (!initialLoadComplete && (dataLoading === false || (api && !api.dataLoading))) {
|
||||
if (!initialLoadComplete && (dataLoading === false || (api && !api.dataLoading$))) {
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ export const ControlGroupRenderer = ({
|
|||
*/
|
||||
useEffect(() => {
|
||||
if (!controlGroup) return;
|
||||
const stateChangeSubscription = controlGroup.unsavedChanges.subscribe((changes) => {
|
||||
const stateChangeSubscription = controlGroup.unsavedChanges$.subscribe((changes) => {
|
||||
runtimeState$.next({ ...runtimeState$.getValue(), ...changes });
|
||||
});
|
||||
return () => {
|
||||
|
@ -168,8 +168,8 @@ export const ControlGroupRenderer = ({
|
|||
type={CONTROL_GROUP_TYPE}
|
||||
getParentApi={() => ({
|
||||
reload$,
|
||||
dataLoading: dataLoading$,
|
||||
viewMode: viewMode$,
|
||||
dataLoading$,
|
||||
viewMode$,
|
||||
query$: searchApi.query$,
|
||||
timeRange$: searchApi.timeRange$,
|
||||
unifiedSearchFilters$: searchApi.filters$,
|
||||
|
|
|
@ -55,8 +55,8 @@ export function initializeControlGroupUnsavedChanges(
|
|||
|
||||
return {
|
||||
api: {
|
||||
unsavedChanges: combineLatest([
|
||||
controlGroupUnsavedChanges.api.unsavedChanges,
|
||||
unsavedChanges$: combineLatest([
|
||||
controlGroupUnsavedChanges.api.unsavedChanges$,
|
||||
childrenUnsavedChanges$(children$),
|
||||
]).pipe(
|
||||
map(([unsavedControlGroupState, unsavedControlsState]) => {
|
||||
|
@ -87,7 +87,7 @@ export function initializeControlGroupUnsavedChanges(
|
|||
applySelections();
|
||||
}
|
||||
},
|
||||
} as Pick<PublishesUnsavedChanges, 'unsavedChanges'> & {
|
||||
} as Pick<PublishesUnsavedChanges, 'unsavedChanges$'> & {
|
||||
asyncResetUnsavedChanges: () => Promise<void>;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -85,7 +85,7 @@ export const getControlGroupEmbeddableFactory = () => {
|
|||
...controlsManager.api,
|
||||
autoApplySelections$,
|
||||
});
|
||||
const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined);
|
||||
const dataViews$ = new BehaviorSubject<DataView[] | undefined>(undefined);
|
||||
const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(
|
||||
chainingSystem ?? DEFAULT_CONTROL_CHAINING
|
||||
);
|
||||
|
@ -130,7 +130,7 @@ export const getControlGroupEmbeddableFactory = () => {
|
|||
|
||||
const api = setApi({
|
||||
...controlsManager.api,
|
||||
disabledActionIds: disabledActionIds$,
|
||||
disabledActionIds$,
|
||||
...unsavedChanges.api,
|
||||
...selectionsManager.api,
|
||||
controlFetch$: (controlUuid: string) =>
|
||||
|
@ -166,7 +166,7 @@ export const getControlGroupEmbeddableFactory = () => {
|
|||
isEditingEnabled: () => true,
|
||||
openAddDataControlFlyout: (settings) => {
|
||||
const parentDataViewId = apiPublishesDataViews(parentApi)
|
||||
? parentApi.dataViews.value?.[0]?.id
|
||||
? parentApi.dataViews$.value?.[0]?.id
|
||||
: undefined;
|
||||
const newControlState = controlsManager.getNewControlState();
|
||||
|
||||
|
@ -201,7 +201,7 @@ export const getControlGroupEmbeddableFactory = () => {
|
|||
references,
|
||||
};
|
||||
},
|
||||
dataViews,
|
||||
dataViews$,
|
||||
labelPosition: labelPosition$,
|
||||
saveNotification$: apiHasSaveNotification(parentApi)
|
||||
? parentApi.saveNotification$
|
||||
|
@ -227,8 +227,8 @@ export const getControlGroupEmbeddableFactory = () => {
|
|||
const childrenDataViewsSubscription = combineCompatibleChildrenApis<
|
||||
PublishesDataViews,
|
||||
DataView[]
|
||||
>(api, 'dataViews', apiPublishesDataViews, []).subscribe((newDataViews) =>
|
||||
dataViews.next(newDataViews)
|
||||
>(api, 'dataViews$', apiPublishesDataViews, []).subscribe((newDataViews) =>
|
||||
dataViews$.next(newDataViews)
|
||||
);
|
||||
|
||||
const saveNotificationSubscription = apiHasSaveNotification(parentApi)
|
||||
|
|
|
@ -53,7 +53,7 @@ export type ControlGroupApi = PresentationContainer &
|
|||
PublishesDataViews &
|
||||
HasSerializedChildState<ControlPanelState> &
|
||||
HasEditCapabilities &
|
||||
Pick<PublishesUnsavedChanges<ControlGroupRuntimeState>, 'unsavedChanges'> &
|
||||
Pick<PublishesUnsavedChanges<ControlGroupRuntimeState>, 'unsavedChanges$'> &
|
||||
PublishesTimeslice &
|
||||
PublishesDisabledActionIds &
|
||||
Partial<HasParentApi<PublishesUnifiedSearch> & HasSaveNotification & PublishesReload> & {
|
||||
|
|
|
@ -52,20 +52,20 @@ describe('initializeDataControl', () => {
|
|||
controlGroupApi
|
||||
);
|
||||
|
||||
dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => {
|
||||
dataControl.api.defaultTitle$!.pipe(skip(1), first()).subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should set data view', () => {
|
||||
const dataViews = dataControl!.api.dataViews.value;
|
||||
const dataViews = dataControl!.api.dataViews$.value;
|
||||
expect(dataViews).not.toBeUndefined();
|
||||
expect(dataViews!.length).toBe(1);
|
||||
expect(dataViews![0].id).toBe('myDataViewId');
|
||||
});
|
||||
|
||||
test('should set default panel title', () => {
|
||||
const defaultPanelTitle = dataControl!.api.defaultPanelTitle!.value;
|
||||
const defaultPanelTitle = dataControl!.api.defaultTitle$!.value;
|
||||
expect(defaultPanelTitle).not.toBeUndefined();
|
||||
expect(defaultPanelTitle).toBe('My field name');
|
||||
});
|
||||
|
@ -86,13 +86,13 @@ describe('initializeDataControl', () => {
|
|||
controlGroupApi
|
||||
);
|
||||
|
||||
dataControl.api.dataViews.pipe(skip(1), first()).subscribe(() => {
|
||||
dataControl.api.dataViews$.pipe(skip(1), first()).subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should set blocking error', () => {
|
||||
const error = dataControl!.api.blockingError.value;
|
||||
const error = dataControl!.api.blockingError$.value;
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error!.message).toBe(
|
||||
'Simulated error: no data view found for id notGonnaFindMeDataViewId'
|
||||
|
@ -100,9 +100,9 @@ describe('initializeDataControl', () => {
|
|||
});
|
||||
|
||||
test('should clear blocking error when valid data view id provided', (done) => {
|
||||
dataControl!.api.dataViews.pipe(skip(1), first()).subscribe((dataView) => {
|
||||
dataControl!.api.dataViews$.pipe(skip(1), first()).subscribe((dataView) => {
|
||||
expect(dataView).not.toBeUndefined();
|
||||
expect(dataControl!.api.blockingError.value).toBeUndefined();
|
||||
expect(dataControl!.api.blockingError$.value).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
dataControl!.stateManager.dataViewId.next('myDataViewId');
|
||||
|
@ -124,25 +124,23 @@ describe('initializeDataControl', () => {
|
|||
controlGroupApi
|
||||
);
|
||||
|
||||
dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => {
|
||||
dataControl.api.defaultTitle$!.pipe(skip(1), first()).subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should set blocking error', () => {
|
||||
const error = dataControl!.api.blockingError.value;
|
||||
const error = dataControl!.api.blockingError$.value;
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error!.message).toBe('Could not locate field: notGonnaFindMeFieldName');
|
||||
});
|
||||
|
||||
test('should clear blocking error when valid field name provided', (done) => {
|
||||
dataControl!.api
|
||||
.defaultPanelTitle!.pipe(skip(1), first())
|
||||
.subscribe((defaultPanelTitle) => {
|
||||
expect(defaultPanelTitle).toBe('My field name');
|
||||
expect(dataControl!.api.blockingError.value).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
dataControl!.api.defaultTitle$!.pipe(skip(1), first()).subscribe((defaultTitle) => {
|
||||
expect(defaultTitle).toBe('My field name');
|
||||
expect(dataControl!.api.blockingError$.value).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
dataControl!.stateManager.fieldName.next('myFieldName');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -52,11 +52,11 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
} => {
|
||||
const defaultControl = initializeDefaultControlApi(state);
|
||||
|
||||
const panelTitle = new BehaviorSubject<string | undefined>(state.title);
|
||||
const defaultPanelTitle = new BehaviorSubject<string | undefined>(undefined);
|
||||
const title$ = new BehaviorSubject<string | undefined>(state.title);
|
||||
const defaultTitle$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
const dataViewId = new BehaviorSubject<string>(state.dataViewId);
|
||||
const fieldName = new BehaviorSubject<string>(state.fieldName);
|
||||
const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined);
|
||||
const dataViews$ = new BehaviorSubject<DataView[] | undefined>(undefined);
|
||||
const filters$ = new BehaviorSubject<Filter[] | undefined>(undefined);
|
||||
const filtersReady$ = new BehaviorSubject<boolean>(false);
|
||||
const field$ = new BehaviorSubject<DataViewField | undefined>(undefined);
|
||||
|
@ -68,14 +68,14 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
...defaultControl.stateManager,
|
||||
dataViewId,
|
||||
fieldName,
|
||||
title: panelTitle,
|
||||
title: title$,
|
||||
};
|
||||
|
||||
const dataViewIdSubscription = dataViewId
|
||||
.pipe(
|
||||
tap(() => {
|
||||
filtersReady$.next(false);
|
||||
if (defaultControl.api.blockingError.value) {
|
||||
if (defaultControl.api.blockingError$.value) {
|
||||
defaultControl.api.setBlockingError(undefined);
|
||||
}
|
||||
}),
|
||||
|
@ -93,10 +93,10 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
if (error) {
|
||||
defaultControl.api.setBlockingError(error);
|
||||
}
|
||||
dataViews.next(dataView ? [dataView] : undefined);
|
||||
dataViews$.next(dataView ? [dataView] : undefined);
|
||||
});
|
||||
|
||||
const fieldNameSubscription = combineLatest([dataViews, fieldName])
|
||||
const fieldNameSubscription = combineLatest([dataViews$, fieldName])
|
||||
.pipe(
|
||||
tap(() => {
|
||||
filtersReady$.next(false);
|
||||
|
@ -120,12 +120,12 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
})
|
||||
)
|
||||
);
|
||||
} else if (defaultControl.api.blockingError.value) {
|
||||
} else if (defaultControl.api.blockingError$.value) {
|
||||
defaultControl.api.setBlockingError(undefined);
|
||||
}
|
||||
|
||||
field$.next(field);
|
||||
defaultPanelTitle.next(field ? field.displayName || field.name : nextFieldName);
|
||||
defaultTitle$.next(field ? field.displayName || field.name : nextFieldName);
|
||||
const spec = field?.toSpec();
|
||||
if (spec) {
|
||||
fieldFormatter.next(dataView.getFormatterForField(spec).getConverterFor('text'));
|
||||
|
@ -172,7 +172,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
},
|
||||
controlType,
|
||||
controlId,
|
||||
initialDefaultPanelTitle: defaultPanelTitle.getValue(),
|
||||
initialDefaultPanelTitle: defaultTitle$.getValue(),
|
||||
controlGroupApi,
|
||||
});
|
||||
};
|
||||
|
@ -186,9 +186,9 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
|
||||
const api: ControlApiInitialization<DataControlApi> = {
|
||||
...defaultControl.api,
|
||||
panelTitle,
|
||||
defaultPanelTitle,
|
||||
dataViews,
|
||||
title$,
|
||||
defaultTitle$,
|
||||
dataViews$,
|
||||
field$,
|
||||
fieldFormatter,
|
||||
onEdit,
|
||||
|
@ -196,7 +196,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
isEditingEnabled: () => true,
|
||||
untilFiltersReady: async () => {
|
||||
return new Promise((resolve) => {
|
||||
combineLatest([defaultControl.api.blockingError, filtersReady$])
|
||||
combineLatest([defaultControl.api.blockingError$, filtersReady$])
|
||||
.pipe(
|
||||
first(([blockingError, filtersReady]) => filtersReady || blockingError !== undefined)
|
||||
)
|
||||
|
@ -216,7 +216,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
},
|
||||
comparators: {
|
||||
...defaultControl.comparators,
|
||||
title: [panelTitle, (value: string | undefined) => panelTitle.next(value)],
|
||||
title: [title$, (value: string | undefined) => title$.next(value)],
|
||||
dataViewId: [dataViewId, (value: string) => dataViewId.next(value)],
|
||||
fieldName: [fieldName, (value: string) => fieldName.next(value)],
|
||||
},
|
||||
|
@ -235,7 +235,7 @@ export const initializeDataControl = <EditorState extends object = {}>(
|
|||
...defaultControl.serialize().rawState,
|
||||
dataViewId: dataViewId.getValue(),
|
||||
fieldName: fieldName.getValue(),
|
||||
title: panelTitle.getValue(),
|
||||
title: title$.getValue(),
|
||||
},
|
||||
references: [
|
||||
{
|
||||
|
|
|
@ -31,7 +31,7 @@ export const getOptionsListMocks = () => {
|
|||
availableOptions$: new BehaviorSubject<OptionsListSuggestions | undefined>(undefined),
|
||||
invalidSelections$: new BehaviorSubject<Set<OptionsListSelection>>(new Set([])),
|
||||
totalCardinality$: new BehaviorSubject<number | undefined>(undefined),
|
||||
dataLoading: new BehaviorSubject<boolean>(false),
|
||||
dataLoading$: new BehaviorSubject<boolean>(false),
|
||||
parentApi: {
|
||||
allowExpensiveQueries$: new BehaviorSubject<boolean>(true),
|
||||
},
|
||||
|
|
|
@ -58,12 +58,12 @@ export const OptionsListControl = ({
|
|||
stateManager.selectedOptions,
|
||||
api.invalidSelections$,
|
||||
api.field$,
|
||||
api.dataLoading,
|
||||
api.panelTitle,
|
||||
api.dataLoading$,
|
||||
api.title$,
|
||||
api.fieldFormatter
|
||||
);
|
||||
|
||||
const [defaultPanelTitle] = useBatchedOptionalPublishingSubjects(api.defaultPanelTitle);
|
||||
const [defaultPanelTitle] = useBatchedOptionalPublishingSubjects(api.defaultTitle$);
|
||||
|
||||
const delimiter = useMemo(() => OptionsListStrings.control.getSeparator(field?.type), [field]);
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const OptionsListPopover = () => {
|
|||
api.field$,
|
||||
api.availableOptions$,
|
||||
api.invalidSelections$,
|
||||
api.dataLoading
|
||||
api.dataLoading$
|
||||
);
|
||||
const [showOnlySelected, setShowOnlySelected] = useState(false);
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export const OptionsListPopoverFooter = () => {
|
|||
|
||||
const [exclude, loading, allowExpensiveQueries] = useBatchedPublishingSubjects(
|
||||
stateManager.exclude,
|
||||
api.dataLoading,
|
||||
api.dataLoading$,
|
||||
api.parentApi.allowExpensiveQueries$
|
||||
);
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export const OptionsListPopoverInvalidSelections = () => {
|
|||
api.invalidSelections$,
|
||||
api.fieldFormatter
|
||||
);
|
||||
const defaultPanelTitle = useStateFromPublishingSubject(api.defaultPanelTitle);
|
||||
const defaultPanelTitle = useStateFromPublishingSubject(api.defaultTitle$);
|
||||
|
||||
const [selectableOptions, setSelectableOptions] = useState<EuiSelectableOption[]>([]); // will be set in following useEffect
|
||||
useEffect(() => {
|
||||
|
|
|
@ -59,7 +59,7 @@ export const OptionsListPopoverSuggestions = ({
|
|||
api.invalidSelections$,
|
||||
api.availableOptions$,
|
||||
api.totalCardinality$,
|
||||
api.dataLoading,
|
||||
api.dataLoading$,
|
||||
api.fieldFormatter,
|
||||
api.parentApi.allowExpensiveQueries$
|
||||
);
|
||||
|
|
|
@ -33,7 +33,7 @@ export function fetchAndValidate$({
|
|||
api,
|
||||
stateManager,
|
||||
}: {
|
||||
api: Pick<OptionsListControlApi, 'dataViews' | 'field$' | 'setBlockingError' | 'parentApi'> &
|
||||
api: Pick<OptionsListControlApi, 'dataViews$' | 'field$' | 'setBlockingError' | 'parentApi'> &
|
||||
Pick<OptionsListComponentApi, 'loadMoreSubject'> & {
|
||||
controlFetch$: Observable<ControlFetchContext>;
|
||||
loadingSuggestions$: BehaviorSubject<boolean>;
|
||||
|
@ -49,7 +49,7 @@ export function fetchAndValidate$({
|
|||
let abortController: AbortController | undefined;
|
||||
|
||||
return combineLatest([
|
||||
api.dataViews,
|
||||
api.dataViews$,
|
||||
api.field$,
|
||||
api.controlFetch$,
|
||||
api.parentApi.allowExpensiveQueries$,
|
||||
|
|
|
@ -126,7 +126,7 @@ export const getOptionsListControlFactory = (): DataControlFactory<
|
|||
const loadingSuggestions$ = new BehaviorSubject<boolean>(false);
|
||||
const dataLoadingSubscription = combineLatest([
|
||||
loadingSuggestions$,
|
||||
dataControl.api.dataLoading,
|
||||
dataControl.api.dataLoading$,
|
||||
])
|
||||
.pipe(
|
||||
debounceTime(100), // debounce set loading so that it doesn't flash as the user types
|
||||
|
@ -188,7 +188,7 @@ export const getOptionsListControlFactory = (): DataControlFactory<
|
|||
if (Object.hasOwn(result, 'error')) {
|
||||
dataControl.api.setBlockingError((result as { error: Error }).error);
|
||||
return;
|
||||
} else if (dataControl.api.blockingError.getValue()) {
|
||||
} else if (dataControl.api.blockingError$.getValue()) {
|
||||
// otherwise, if there was a previous error, clear it
|
||||
dataControl.api.setBlockingError(undefined);
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ export const getOptionsListControlFactory = (): DataControlFactory<
|
|||
});
|
||||
/** Output filters when selections change */
|
||||
const outputFilterSubscription = combineLatest([
|
||||
dataControl.api.dataViews,
|
||||
dataControl.api.dataViews$,
|
||||
dataControl.stateManager.fieldName,
|
||||
selections.selectedOptions$,
|
||||
selections.existsSelected$,
|
||||
|
@ -263,7 +263,7 @@ export const getOptionsListControlFactory = (): DataControlFactory<
|
|||
const api = buildApi(
|
||||
{
|
||||
...dataControl.api,
|
||||
dataLoading: dataLoading$,
|
||||
dataLoading$,
|
||||
getTypeDisplayName: OptionsListStrings.control.getDisplayName,
|
||||
serializeState: () => {
|
||||
const { rawState: dataControlState, references } = dataControl.serialize();
|
||||
|
|
|
@ -147,7 +147,7 @@ describe('RangesliderControlApi', () => {
|
|||
controlGroupApi
|
||||
);
|
||||
expect(api.filters$.value).toBeUndefined();
|
||||
expect(api.blockingError.value?.message).toEqual(
|
||||
expect(api.blockingError$.value?.message).toEqual(
|
||||
'no data view found for id notGonnaFindMeDataView'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -85,7 +85,7 @@ export const getRangesliderControlFactory = (): DataControlFactory<
|
|||
const api = buildApi(
|
||||
{
|
||||
...dataControl.api,
|
||||
dataLoading: dataLoading$,
|
||||
dataLoading$,
|
||||
getTypeDisplayName: RangeSliderStrings.control.getDisplayName,
|
||||
serializeState: () => {
|
||||
const { rawState: dataControlState, references } = dataControl.serialize();
|
||||
|
@ -117,7 +117,7 @@ export const getRangesliderControlFactory = (): DataControlFactory<
|
|||
const dataLoadingSubscription = combineLatest([
|
||||
loadingMinMax$,
|
||||
loadingHasNoResults$,
|
||||
dataControl.api.dataLoading,
|
||||
dataControl.api.dataLoading$,
|
||||
])
|
||||
.pipe(
|
||||
debounceTime(100),
|
||||
|
@ -142,11 +142,11 @@ export const getRangesliderControlFactory = (): DataControlFactory<
|
|||
const min$ = new BehaviorSubject<number | undefined>(undefined);
|
||||
const minMaxSubscription = minMax$({
|
||||
controlFetch$,
|
||||
dataViews$: dataControl.api.dataViews,
|
||||
dataViews$: dataControl.api.dataViews$,
|
||||
fieldName$: dataControl.stateManager.fieldName,
|
||||
setIsLoading: (isLoading: boolean) => {
|
||||
// clear previous loading error on next loading start
|
||||
if (isLoading && dataControl.api.blockingError.value) {
|
||||
if (isLoading && dataControl.api.blockingError$.value) {
|
||||
dataControl.api.setBlockingError(undefined);
|
||||
}
|
||||
loadingMinMax$.next(isLoading);
|
||||
|
@ -171,7 +171,7 @@ export const getRangesliderControlFactory = (): DataControlFactory<
|
|||
);
|
||||
|
||||
const outputFilterSubscription = combineLatest([
|
||||
dataControl.api.dataViews,
|
||||
dataControl.api.dataViews$,
|
||||
dataControl.stateManager.fieldName,
|
||||
selections.value$,
|
||||
])
|
||||
|
@ -201,7 +201,7 @@ export const getRangesliderControlFactory = (): DataControlFactory<
|
|||
const selectionHasNoResults$ = new BehaviorSubject(false);
|
||||
const hasNotResultsSubscription = hasNoResults$({
|
||||
controlFetch$,
|
||||
dataViews$: dataControl.api.dataViews,
|
||||
dataViews$: dataControl.api.dataViews$,
|
||||
rangeFilters$: dataControl.api.filters$,
|
||||
ignoreParentSettings$: controlGroupApi.ignoreParentSettings$,
|
||||
setIsLoading: (isLoading: boolean) => {
|
||||
|
|
|
@ -25,7 +25,7 @@ export function hasNoResults$({
|
|||
setIsLoading,
|
||||
}: {
|
||||
controlFetch$: Observable<ControlFetchContext>;
|
||||
dataViews$?: PublishesDataViews['dataViews'];
|
||||
dataViews$?: PublishesDataViews['dataViews$'];
|
||||
rangeFilters$: DataControlApi['filters$'];
|
||||
ignoreParentSettings$: ControlGroupApi['ignoreParentSettings$'];
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
|
|
|
@ -26,7 +26,7 @@ export function minMax$({
|
|||
}: {
|
||||
controlFetch$: Observable<ControlFetchContext>;
|
||||
controlGroupApi: ControlGroupApi;
|
||||
dataViews$: PublishesDataViews['dataViews'];
|
||||
dataViews$: PublishesDataViews['dataViews$'];
|
||||
fieldName$: PublishingSubject<string>;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
}) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { FieldFormatConvertFunction } from '@kbn/field-formats-plugin/common';
|
|||
import {
|
||||
HasEditCapabilities,
|
||||
PublishesDataViews,
|
||||
PublishesPanelTitle,
|
||||
PublishesTitle,
|
||||
PublishingSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
|
||||
|
@ -29,7 +29,7 @@ export interface PublishesField {
|
|||
}
|
||||
|
||||
export type DataControlApi = DefaultControlApi &
|
||||
Omit<PublishesPanelTitle, 'hidePanelTitle'> & // control titles cannot be hidden
|
||||
Omit<PublishesTitle, 'hideTitle$'> & // control titles cannot be hidden
|
||||
HasEditCapabilities &
|
||||
PublishesDataViews &
|
||||
PublishesField &
|
||||
|
|
|
@ -23,8 +23,8 @@ export const initializeDefaultControlApi = (
|
|||
comparators: StateComparators<DefaultControlState>;
|
||||
serialize: () => SerializedPanelState<DefaultControlState>;
|
||||
} => {
|
||||
const dataLoading = new BehaviorSubject<boolean | undefined>(false);
|
||||
const blockingError = new BehaviorSubject<Error | undefined>(undefined);
|
||||
const dataLoading$ = new BehaviorSubject<boolean | undefined>(false);
|
||||
const blockingError$ = new BehaviorSubject<Error | undefined>(undefined);
|
||||
const grow = new BehaviorSubject<boolean | undefined>(state.grow);
|
||||
const width = new BehaviorSubject<ControlWidth | undefined>(state.width);
|
||||
|
||||
|
@ -32,10 +32,10 @@ export const initializeDefaultControlApi = (
|
|||
api: {
|
||||
grow,
|
||||
width,
|
||||
dataLoading,
|
||||
blockingError,
|
||||
setBlockingError: (error) => blockingError.next(error),
|
||||
setDataLoading: (loading) => dataLoading.next(loading),
|
||||
dataLoading$,
|
||||
blockingError$,
|
||||
setBlockingError: (error) => blockingError$.next(error),
|
||||
setDataLoading: (loading) => dataLoading$.next(loading),
|
||||
},
|
||||
comparators: {
|
||||
grow: [grow, (newGrow: boolean | undefined) => grow.next(newGrow)],
|
||||
|
|
|
@ -42,7 +42,7 @@ export const getMockedBuildApi =
|
|||
...api,
|
||||
uuid,
|
||||
parentApi: controlGroupApi ?? getMockedControlGroupApi(),
|
||||
unsavedChanges: new BehaviorSubject<Partial<StateType> | undefined>(undefined),
|
||||
unsavedChanges$: new BehaviorSubject<Partial<StateType> | undefined>(undefined),
|
||||
resetUnsavedChanges: () => {
|
||||
return true;
|
||||
},
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('TimesliderControlApi', () => {
|
|||
...api,
|
||||
uuid,
|
||||
parentApi: controlGroupApi,
|
||||
unsavedChanges: new BehaviorSubject<Partial<TimesliderControlState> | undefined>(undefined),
|
||||
unsavedChanges$: new BehaviorSubject<Partial<TimesliderControlState> | undefined>(undefined),
|
||||
resetUnsavedChanges: () => {
|
||||
return true;
|
||||
},
|
||||
|
|
|
@ -194,7 +194,7 @@ export const getTimesliderControlFactory = (): ControlFactory<
|
|||
|
||||
const dashboardDataLoading$ =
|
||||
apiHasParentApi(controlGroupApi) && apiPublishesDataLoading(controlGroupApi.parentApi)
|
||||
? controlGroupApi.parentApi.dataLoading
|
||||
? controlGroupApi.parentApi.dataLoading$
|
||||
: new BehaviorSubject<boolean | undefined>(false);
|
||||
const waitForDashboardPanelsToLoad$ = dashboardDataLoading$.pipe(
|
||||
// debounce to give time for panels to start loading if they are going to load from time changes
|
||||
|
@ -212,7 +212,7 @@ export const getTimesliderControlFactory = (): ControlFactory<
|
|||
const api = buildApi(
|
||||
{
|
||||
...defaultControl.api,
|
||||
defaultPanelTitle: new BehaviorSubject<string | undefined>(displayName),
|
||||
defaultTitle$: new BehaviorSubject<string | undefined>(displayName),
|
||||
timeslice$,
|
||||
serializeState: () => {
|
||||
const { rawState: defaultControlState } = defaultControl.serialize();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { PublishesPanelTitle, PublishesTimeslice } from '@kbn/presentation-publishing';
|
||||
import type { PublishesTitle, PublishesTimeslice } from '@kbn/presentation-publishing';
|
||||
import type { DefaultControlState } from '../../../common';
|
||||
import type { DefaultControlApi } from '../types';
|
||||
|
||||
|
@ -21,5 +21,5 @@ export interface TimesliderControlState extends DefaultControlState {
|
|||
}
|
||||
|
||||
export type TimesliderControlApi = DefaultControlApi &
|
||||
Pick<PublishesPanelTitle, 'defaultPanelTitle'> &
|
||||
Pick<PublishesTitle, 'defaultTitle$'> &
|
||||
PublishesTimeslice;
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
PublishesBlockingError,
|
||||
PublishesDataLoading,
|
||||
PublishesDisabledActionIds,
|
||||
PublishesPanelTitle,
|
||||
PublishesTitle,
|
||||
PublishesUnsavedChanges,
|
||||
PublishingSubject,
|
||||
StateComparators,
|
||||
|
@ -35,7 +35,7 @@ export interface HasCustomPrepend {
|
|||
export type DefaultControlApi = PublishesDataLoading &
|
||||
PublishesBlockingError &
|
||||
PublishesUnsavedChanges &
|
||||
Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> &
|
||||
Partial<PublishesTitle & PublishesDisabledActionIds & HasCustomPrepend> &
|
||||
CanClearSelections &
|
||||
HasType &
|
||||
HasUniqueId &
|
||||
|
@ -49,7 +49,7 @@ export type DefaultControlApi = PublishesDataLoading &
|
|||
|
||||
export type ControlApiRegistration<ControlApi extends DefaultControlApi = DefaultControlApi> = Omit<
|
||||
ControlApi,
|
||||
'uuid' | 'parentApi' | 'type' | 'unsavedChanges' | 'resetUnsavedChanges'
|
||||
'uuid' | 'parentApi' | 'type' | 'unsavedChanges$' | 'resetUnsavedChanges'
|
||||
>;
|
||||
|
||||
export type ControlApiInitialization<ControlApi extends DefaultControlApi = DefaultControlApi> =
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Clone panel action', () => {
|
|||
context = {
|
||||
embeddable: {
|
||||
uuid: 'superId',
|
||||
viewMode: new BehaviorSubject<ViewMode>('edit'),
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'),
|
||||
serializeState: () => {
|
||||
return {
|
||||
rawState: {},
|
||||
|
@ -45,7 +45,7 @@ describe('Clone panel action', () => {
|
|||
});
|
||||
|
||||
it('is incompatible when view mode is view', async () => {
|
||||
(context.embeddable as PublishesViewMode).viewMode = new BehaviorSubject<ViewMode>('view');
|
||||
(context.embeddable as PublishesViewMode).viewMode$ = new BehaviorSubject<ViewMode>('view');
|
||||
expect(await action.isCompatible(context)).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -58,7 +58,9 @@ export class ClonePanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) return false;
|
||||
return Boolean(!embeddable.blockingError?.value && getInheritedViewMode(embeddable) === 'edit');
|
||||
return Boolean(
|
||||
!embeddable.blockingError$?.value && getInheritedViewMode(embeddable) === 'edit'
|
||||
);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: EmbeddableApiContext) {
|
||||
|
|
|
@ -48,7 +48,7 @@ export function CopyToDashboardModal({ api, closeModal }: CopyToDashboardModalPr
|
|||
null
|
||||
);
|
||||
|
||||
const dashboardId = api.parentApi.savedObjectId.value;
|
||||
const dashboardId = api.parentApi.savedObjectId$.value;
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const dashboard = api.parentApi;
|
||||
|
|
|
@ -13,17 +13,17 @@ import { ExpandPanelActionApi, ExpandPanelAction } from './expand_panel_action';
|
|||
describe('Expand panel action', () => {
|
||||
let action: ExpandPanelAction;
|
||||
let context: { embeddable: ExpandPanelActionApi };
|
||||
let expandPanelIdSubject: BehaviorSubject<string | undefined>;
|
||||
let expandedPanelId$: BehaviorSubject<string | undefined>;
|
||||
|
||||
beforeEach(() => {
|
||||
expandPanelIdSubject = new BehaviorSubject<string | undefined>(undefined);
|
||||
expandedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
action = new ExpandPanelAction();
|
||||
context = {
|
||||
embeddable: {
|
||||
uuid: 'superId',
|
||||
parentApi: {
|
||||
expandPanel: jest.fn(),
|
||||
expandedPanelId: expandPanelIdSubject,
|
||||
expandedPanelId$,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -43,19 +43,19 @@ describe('Expand panel action', () => {
|
|||
it('calls onChange when expandedPanelId changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
action.subscribeToCompatibilityChanges(context, onChange);
|
||||
expandPanelIdSubject.next('superPanelId');
|
||||
expandedPanelId$.next('superPanelId');
|
||||
expect(onChange).toHaveBeenCalledWith(true, action);
|
||||
});
|
||||
|
||||
it('returns the correct icon based on expanded panel id', async () => {
|
||||
expect(await action.getIconType(context)).toBe('expand');
|
||||
expandPanelIdSubject.next('superPanelId');
|
||||
expandedPanelId$.next('superPanelId');
|
||||
expect(await action.getIconType(context)).toBe('minimize');
|
||||
});
|
||||
|
||||
it('returns the correct display name based on expanded panel id', async () => {
|
||||
expect(await action.getDisplayName(context)).toBe('Maximize');
|
||||
expandPanelIdSubject.next('superPanelId');
|
||||
expandedPanelId$.next('superPanelId');
|
||||
expect(await action.getDisplayName(context)).toBe('Minimize');
|
||||
});
|
||||
|
||||
|
|
|
@ -34,14 +34,14 @@ export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
|||
|
||||
public getDisplayName({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return embeddable.parentApi.expandedPanelId.value
|
||||
return embeddable.parentApi.expandedPanelId$.value
|
||||
? dashboardExpandPanelActionStrings.getMinimizeTitle()
|
||||
: dashboardExpandPanelActionStrings.getMaximizeTitle();
|
||||
}
|
||||
|
||||
public getIconType({ embeddable }: EmbeddableApiContext) {
|
||||
if (!isApiCompatible(embeddable)) throw new IncompatibleActionError();
|
||||
return embeddable.parentApi.expandedPanelId.value ? 'minimize' : 'expand';
|
||||
return embeddable.parentApi.expandedPanelId$.value ? 'minimize' : 'expand';
|
||||
}
|
||||
|
||||
public async isCompatible({ embeddable }: EmbeddableApiContext) {
|
||||
|
@ -57,7 +57,7 @@ export class ExpandPanelAction implements Action<EmbeddableApiContext> {
|
|||
onChange: (isCompatible: boolean, action: ExpandPanelAction) => void
|
||||
) {
|
||||
if (!isApiCompatible(embeddable)) return;
|
||||
return embeddable.parentApi.expandedPanelId.pipe(skip(1)).subscribe(() => {
|
||||
return embeddable.parentApi.expandedPanelId$.pipe(skip(1)).subscribe(() => {
|
||||
onChange(isApiCompatible(embeddable), this);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,11 +18,7 @@ import {
|
|||
apiHasInspectorAdapters,
|
||||
type Adapters,
|
||||
} from '@kbn/inspector-plugin/public';
|
||||
import {
|
||||
EmbeddableApiContext,
|
||||
PublishesPanelTitle,
|
||||
getPanelTitle,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { EmbeddableApiContext, PublishesTitle, getTitle } from '@kbn/presentation-publishing';
|
||||
import { coreServices, fieldFormatService } from '../services/kibana_services';
|
||||
import { dashboardExportCsvActionStrings } from './_dashboard_actions_strings';
|
||||
import { ACTION_EXPORT_CSV } from './constants';
|
||||
|
@ -32,7 +28,7 @@ export type ExportContext = EmbeddableApiContext & {
|
|||
asString?: boolean;
|
||||
};
|
||||
|
||||
export type ExportCsvActionApi = HasInspectorAdapters & Partial<PublishesPanelTitle>;
|
||||
export type ExportCsvActionApi = HasInspectorAdapters & Partial<PublishesTitle>;
|
||||
|
||||
const isApiCompatible = (api: unknown | null): api is ExportCsvActionApi =>
|
||||
Boolean(apiHasInspectorAdapters(api));
|
||||
|
@ -90,7 +86,7 @@ export class ExportCSVAction implements Action<ExportContext> {
|
|||
const postFix = datatables.length > 1 ? `-${i + 1}` : '';
|
||||
const untitledFilename = dashboardExportCsvActionStrings.getUntitledFilename();
|
||||
|
||||
memo[`${getPanelTitle(embeddable) || untitledFilename}${postFix}.csv`] = {
|
||||
memo[`${getTitle(embeddable) || untitledFilename}${postFix}.csv`] = {
|
||||
content: exporters.datatableToCSV(datatable, {
|
||||
csvSeparator: coreServices.uiSettings.get('csv:separator', ','),
|
||||
quoteValues: coreServices.uiSettings.get('csv:quoteValues', true),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue