Skip to content

Commit

Permalink
Merge pull request #19 from nreese/publishing_subject
Browse files Browse the repository at this point in the history
isolate publishing subject logic and rename methods
  • Loading branch information
ThomThomson authored Jan 9, 2024
2 parents 0bb4f3e + 1e85dce commit ffd63bf
Show file tree
Hide file tree
Showing 15 changed files with 95 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import {
PublishingSubject,
useReactiveVarFromSubject,
} from '@kbn/presentation-publishing/publishing_utils';
useStateFromPublishingSubject,
} from '@kbn/presentation-publishing/publishing_subject';

export interface CanDuplicatePanels {
duplicatePanel: (panelId: string) => void;
Expand All @@ -34,6 +34,6 @@ export const apiCanExpandPanels = (unknownApi: unknown | null): unknownApi is Ca
* Gets this API's expanded panel state as a reactive variable which will cause re-renders on change.
*/
export const useExpandedPanelId = (api: Partial<CanExpandPanels> | undefined) =>
useReactiveVarFromSubject<string | undefined, CanExpandPanels['expandedPanelId']>(
useStateFromPublishingSubject<string | undefined, CanExpandPanels['expandedPanelId']>(
apiCanExpandPanels(api) ? api.expandedPanelId : undefined
);
8 changes: 4 additions & 4 deletions packages/presentation/presentation_publishing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ export {
} from './interfaces/publishes_view_mode';
export { useBatchedPublishingSubjects } from './publishing_batcher';
export {
useApiPublisher,
useReactiveVarFromSubject,
useSubjectFromReactiveVar,
useStateFromPublishingSubject,
usePublishingSubject,
type PublishingSubject,
} from './publishing_utils';
} from './publishing_subject';
export { useApiPublisher } from './publishing_utils';
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesBlockingError {
blockingError: PublishingSubject<Error | undefined>;
Expand All @@ -22,6 +22,6 @@ export const apiPublishesBlockingError = (
* Gets this API's fatal error as a reactive variable which will cause re-renders on change.
*/
export const useBlockingError = (api: Partial<PublishesBlockingError> | undefined) =>
useReactiveVarFromSubject<Error | undefined, PublishesBlockingError['blockingError']>(
useStateFromPublishingSubject<Error | undefined, PublishesBlockingError['blockingError']>(
api?.blockingError
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesDataLoading {
dataLoading: PublishingSubject<boolean | undefined>;
Expand All @@ -22,6 +22,6 @@ export const apiPublishesDataLoading = (
* Gets this API's data loading state as a reactive variable which will cause re-renders on change.
*/
export const useDataLoading = (api: Partial<PublishesDataLoading> | undefined) =>
useReactiveVarFromSubject<boolean | undefined, PublishesDataLoading['dataLoading']>(
useStateFromPublishingSubject<boolean | undefined, PublishesDataLoading['dataLoading']>(
apiPublishesDataLoading(api) ? api.dataLoading : undefined
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { DataView } from '@kbn/data-views-plugin/common';
import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesDataViews {
dataViews: PublishingSubject<DataView[] | undefined>;
Expand All @@ -23,6 +23,6 @@ export const apiPublishesDataViews = (
* Gets this API's data views as a reactive variable which will cause re-renders on change.
*/
export const useDataViews = (api: Partial<PublishesDataViews> | undefined) =>
useReactiveVarFromSubject<DataView[] | undefined, PublishesDataViews['dataViews']>(
useStateFromPublishingSubject<DataView[] | undefined, PublishesDataViews['dataViews']>(
apiPublishesDataViews(api) ? api.dataViews : undefined
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesDisabledActionIds {
disabledActionIds: PublishingSubject<string[] | undefined>;
Expand All @@ -29,6 +29,6 @@ export const apiPublishesDisabledActionIds = (
* Gets this API's disabled action IDs as a reactive variable which will cause re-renders on change.
*/
export const useDisabledActionIds = (api: Partial<PublishesDisabledActionIds> | undefined) =>
useReactiveVarFromSubject<string[] | undefined, PublishesDisabledActionIds['disabledActionIds']>(
useStateFromPublishingSubject<string[] | undefined, PublishesDisabledActionIds['disabledActionIds']>(
api?.disabledActionIds
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { TimeRange, Filter, Query, AggregateQuery } from '@kbn/es-query';
import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesLocalUnifiedSearch {
isCompatibleWithLocalUnifiedSearch?: () => boolean;
Expand Down Expand Up @@ -62,16 +62,16 @@ export const apiPublishesWritableLocalUnifiedSearch = (
* A hook that gets this API's local time range as a reactive variable which will cause re-renders on change.
*/
export const useLocalTimeRange = (api: Partial<PublishesLocalUnifiedSearch> | undefined) =>
useReactiveVarFromSubject<TimeRange | undefined>(api?.localTimeRange);
useStateFromPublishingSubject<TimeRange | undefined>(api?.localTimeRange);

/**
* A hook that gets this API's local filters as a reactive variable which will cause re-renders on change.
*/
export const useLocalFilters = (api: Partial<PublishesLocalUnifiedSearch> | undefined) =>
useReactiveVarFromSubject<Filter[] | undefined>(api?.localFilters);
useStateFromPublishingSubject<Filter[] | undefined>(api?.localFilters);

/**
* A hook that gets this API's local query as a reactive variable which will cause re-renders on change.
*/
export const useLocalQuery = (api: Partial<PublishesLocalUnifiedSearch> | undefined) =>
useReactiveVarFromSubject<Query | AggregateQuery | undefined>(api?.localQuery);
useStateFromPublishingSubject<Query | AggregateQuery | undefined>(api?.localQuery);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesPanelDescription {
panelDescription: PublishingSubject<string | undefined>;
Expand Down Expand Up @@ -39,15 +39,15 @@ export const apiPublishesWritablePanelDescription = (
* A hook that gets this API's panel description as a reactive variable which will cause re-renders on change.
*/
export const usePanelDescription = (api: Partial<PublishesPanelDescription> | undefined) =>
useReactiveVarFromSubject<string | undefined, PublishesPanelDescription['panelDescription']>(
useStateFromPublishingSubject<string | undefined, PublishesPanelDescription['panelDescription']>(
api?.panelDescription
);

/**
* A hook that gets this API's default panel description as a reactive variable which will cause re-renders on change.
*/
export const useDefaultPanelDescription = (api: Partial<PublishesPanelDescription> | undefined) =>
useReactiveVarFromSubject<
useStateFromPublishingSubject<
string | undefined,
PublishesPanelDescription['defaultPanelDescription']
>(api?.defaultPanelDescription);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesPanelTitle {
panelTitle: PublishingSubject<string | undefined>;
Expand Down Expand Up @@ -46,16 +46,16 @@ export const apiPublishesWritablePanelTitle = (
* A hook that gets this API's panel title as a reactive variable which will cause re-renders on change.
*/
export const usePanelTitle = (api: Partial<PublishesPanelTitle> | undefined) =>
useReactiveVarFromSubject<string | undefined>(api?.panelTitle);
useStateFromPublishingSubject<string | undefined>(api?.panelTitle);

/**
* A hook that gets this API's hide panel title setting as a reactive variable which will cause re-renders on change.
*/
export const useHidePanelTitle = (api: Partial<PublishesPanelTitle> | undefined) =>
useReactiveVarFromSubject<boolean | undefined>(api?.hidePanelTitle);
useStateFromPublishingSubject<boolean | undefined>(api?.hidePanelTitle);

/**
* A hook that gets this API's default title as a reactive variable which will cause re-renders on change.
*/
export const useDefaultPanelTitle = (api: Partial<PublishesPanelTitle> | undefined) =>
useReactiveVarFromSubject<string | undefined>(api?.defaultPanelTitle);
useStateFromPublishingSubject<string | undefined>(api?.defaultPanelTitle);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesParentApi<ParentApiType extends unknown = unknown> {
parentApi: PublishingSubject<ParentApiType>;
Expand All @@ -30,6 +30,6 @@ export const useParentApi = <
>(
api: ApiType
): UnwrapParent<ApiType> =>
useReactiveVarFromSubject<unknown, ApiType['parentApi']>(
useStateFromPublishingSubject<unknown, ApiType['parentApi']>(
apiPublishesParentApi(api) ? api.parentApi : undefined
) as UnwrapParent<ApiType>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

/**
* This API publishes a saved object id which can be used to determine which saved object this API is linked to.
Expand All @@ -28,4 +28,4 @@ export const apiPublishesSavedObjectId = (
* A hook that gets this API's saved object ID as a reactive variable which will cause re-renders on change.
*/
export const useSavedObjectId = (api: PublishesSavedObjectId | undefined) =>
useReactiveVarFromSubject<string | undefined>(api?.savedObjectId);
useStateFromPublishingSubject<string | undefined>(api?.savedObjectId);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export interface PublishesUniqueId {
uuid: PublishingSubject<string>;
Expand All @@ -26,6 +26,6 @@ export const useUniqueId = <
>(
api: ApiType
) =>
useReactiveVarFromSubject<string, ApiType['uuid']>(
useStateFromPublishingSubject<string, ApiType['uuid']>(
apiPublishesUniqueId(api) ? api.uuid : undefined
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { PublishingSubject, useReactiveVarFromSubject } from '../publishing_utils';
import { PublishingSubject, useStateFromPublishingSubject } from '../publishing_subject';

export type ViewMode = 'view' | 'edit' | 'print' | 'preview';

Expand Down Expand Up @@ -48,4 +48,4 @@ export const useViewMode = <
ApiType extends Partial<PublishesViewMode> = Partial<PublishesViewMode>
>(
api: ApiType | undefined
) => useReactiveVarFromSubject<ViewMode, ApiType['viewMode']>(api?.viewMode);
) => useStateFromPublishingSubject<ViewMode, ApiType['viewMode']>(api?.viewMode);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { useEffect, useMemo, useState } from 'react';
import { BehaviorSubject } from 'rxjs';

/**
* A publishing subject is a RxJS subject that can be used to listen to value changes, but does not allow pushing values via the Next method.
*/
export type PublishingSubject<T extends unknown = unknown> = Omit<BehaviorSubject<T>, 'next'>;

/**
* A utility type that makes a type optional if another passed in type is optional.
*/
type OptionalIfOptional<TestType, Type> = undefined extends TestType
? Type | undefined
: Type;

/**
* Declares a publishing subject, allowing external code to subscribe to react state changes.
* Changes to state fire subject.next
* @param state React state from useState hook.
*/
export const usePublishingSubject = <T extends unknown = unknown>(
state: T
): PublishingSubject<T> => {
const subject = useMemo<BehaviorSubject<T>>(
() => new BehaviorSubject<T>(state),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
useEffect(() => subject.next(state), [subject, state]);
return subject;
};

/**
* Declares a state variable that is synced with a publishing subject value.
* @param subject Publishing subject.
*/
export const useStateFromPublishingSubject = <
ValueType extends unknown = unknown,
SubjectType extends PublishingSubject<ValueType> | undefined =
| PublishingSubject<ValueType>
| undefined
>(
subject?: SubjectType
): OptionalIfOptional<SubjectType, ValueType> => {
const [value, setValue] = useState<ValueType | undefined>(subject?.getValue());
useEffect(() => {
if (!subject) return;
const subscription = subject.subscribe((newValue) => setValue(newValue));
return () => subscription.unsubscribe();
}, [subject]);
return value as OptionalIfOptional<SubjectType, ValueType>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,7 @@
* Side Public License, v 1.
*/

import { useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { BehaviorSubject } from 'rxjs';

/**
* A publishing subject is a subject that can be used to listen to value changes, but does not allow pushing values via the Next method.
*/
export type PublishingSubject<T extends unknown = unknown> = Omit<BehaviorSubject<T>, 'next'>;

/**
* A utility type that makes a type optional if another passed in type is optional.
*/
export type OptionalIfOptional<TestType, Type> = undefined extends TestType
? Type | undefined
: Type;

/**
* Transforms any reactive variable into a publishing subject that can be used by other components
* or actions to subscribe to changes in this piece of state.
*/
export const useSubjectFromReactiveVar = <T extends unknown = unknown>(
value: T
): PublishingSubject<T> => {
const subject = useMemo<BehaviorSubject<T>>(
() => new BehaviorSubject<T>(value),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
useEffect(() => subject.next(value), [subject, value]);
return subject;
};

/**
* Extracts a reactive variable from a publishing subject. If the type of the provided subject extends undefined,
* the returned value will be optional.
*/
export const useReactiveVarFromSubject = <
ValueType extends unknown = unknown,
SubjectType extends PublishingSubject<ValueType> | undefined =
| PublishingSubject<ValueType>
| undefined
>(
subject?: SubjectType
): OptionalIfOptional<SubjectType, ValueType> => {
const [value, setValue] = useState<ValueType | undefined>(subject?.getValue());
useEffect(() => {
if (!subject) return;
const subscription = subject.subscribe((newValue) => setValue(newValue));
return () => subscription.unsubscribe();
}, [subject]);
return value as OptionalIfOptional<SubjectType, ValueType>;
};
import { useImperativeHandle, useMemo } from 'react';

/**
* Publishes any API to the passed in ref. Note that any API passed in will not be rebuilt on
Expand Down

0 comments on commit ffd63bf

Please sign in to comment.