Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 62 additions & 120 deletions packages/framework/esm-extensions/src/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,12 @@ const slotsConfigStore = getExtensionSlotsConfigStore();
const extensionsConfigStore = getExtensionsConfigStore();

// Keep the output store updated
function updateExtensionOutputStore(
internalState: ExtensionInternalStore,
extensionSlotConfigs: ExtensionSlotsConfigStore,
extensionsConfigStore: ExtensionsConfigStore,
featureFlagStore: FeatureFlagsStore,
sessionStore: SessionStore,
) {
function updateExtensionOutputStore() {
const slots: Record<string, ExtensionSlotState> = {};

const isOnline = isOnlineFn();
const enabledFeatureFlags = Object.entries(featureFlagStore.flags)
.filter(([, { enabled }]) => enabled)
.map(([name]) => name);

const internalState = extensionInternalStore.getState();
for (let [slotName, slot] of Object.entries(internalState.slots)) {
const { config } = getExtensionSlotConfigFromStore(extensionSlotConfigs, slot.name);
const assignedExtensions = getAssignedExtensionsFromSlotData(
slotName,
internalState,
config,
extensionsConfigStore,
enabledFeatureFlags,
isOnline,
sessionStore.session,
);
const assignedExtensions = getAssignedExtensions(slotName);
slots[slotName] = { moduleName: slot.moduleName, assignedExtensions };
}

Expand All @@ -76,68 +57,14 @@ function updateExtensionOutputStore(
}
}

extensionInternalStore.subscribe((internalStore) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern of passing in the new store state to the updateExtensionOutputStore should be unnecessary. As a test, I toggled the rde feature flag and confirmed that the patient charts visit notes workspace shows / hides the visit context switcher accordingly.

updateExtensionOutputStore(
internalStore,
slotsConfigStore.getState(),
extensionsConfigStore.getState(),
featureFlagsStore.getState(),
sessionStore.getState(),
);
});

slotsConfigStore.subscribe((slotConfigs) => {
updateExtensionOutputStore(
extensionInternalStore.getState(),
slotConfigs,
extensionsConfigStore.getState(),
featureFlagsStore.getState(),
sessionStore.getState(),
);
});

extensionsConfigStore.subscribe((extensionConfigs) => {
updateExtensionOutputStore(
extensionInternalStore.getState(),
slotsConfigStore.getState(),
extensionConfigs,
featureFlagsStore.getState(),
sessionStore.getState(),
);
});

featureFlagsStore.subscribe((featureFlagStore) => {
updateExtensionOutputStore(
extensionInternalStore.getState(),
slotsConfigStore.getState(),
extensionsConfigStore.getState(),
featureFlagStore,
sessionStore.getState(),
);
});

sessionStore.subscribe((session) => {
updateExtensionOutputStore(
extensionInternalStore.getState(),
slotsConfigStore.getState(),
extensionsConfigStore.getState(),
featureFlagsStore.getState(),
session,
);
});

function updateOutputStoreToCurrent() {
updateExtensionOutputStore(
extensionInternalStore.getState(),
slotsConfigStore.getState(),
extensionsConfigStore.getState(),
featureFlagsStore.getState(),
sessionStore.getState(),
);
}
extensionInternalStore.subscribe(updateExtensionOutputStore);
slotsConfigStore.subscribe(updateExtensionOutputStore);
extensionsConfigStore.subscribe(updateExtensionOutputStore);
featureFlagsStore.subscribe(updateExtensionOutputStore);
sessionStore.subscribe(updateExtensionOutputStore);

updateOutputStoreToCurrent();
subscribeConnectivityChanged(updateOutputStoreToCurrent);
updateExtensionOutputStore();
subscribeConnectivityChanged(updateExtensionOutputStore);

function createNewExtensionSlotInfo(slotName: string, moduleName?: string): ExtensionSlotInfo {
return {
Expand Down Expand Up @@ -320,22 +247,31 @@ function getOrder(
}
}

function getAssignedExtensionsFromSlotData(
slotName: string,
internalState: ExtensionInternalStore,
config: ExtensionSlotConfig,
extensionConfigStoreState: ExtensionsConfigStore,
enabledFeatureFlags: Array<string>,
isOnline: boolean,
session: Session | null,
): Array<AssignedExtension> {
const attachedIds = internalState.slots[slotName].attachedIds;
const assignedIds = calculateAssignedIds(config, attachedIds);
const extensions: Array<AssignedExtension> = [];
/**
* Create a list of AssignedExtensions for a given slot. This function further
* filters out extensions that fail 'Display conditions', privilege, feature flag or
* online / offline checks
* @see AssignedExtension
* @param slotName the name of the slot
* @param extensionIds a list of extensionIds that are slotted into the slot
* @returns a list of AssignedExtensions
*/
function getAssignedExtensionsByExtensionIds(slotName: string, extensionIds: string[]): Array<AssignedExtension> {
const internalState = extensionInternalStore.getState();
const extensionConfigStoreState = extensionsConfigStore.getState();
const featureFlagState = featureFlagsStore.getState();
const { session } = sessionStore.getState();
const isOnline = isOnlineFn();
const { config: slotConfig } = getExtensionSlotConfig(slotName);
const enabledFeatureFlags = Object.entries(featureFlagState.flags)
.filter(([, { enabled }]) => enabled)
.map(([name]) => name);

for (let id of assignedIds) {
const assignedExtensions: Array<AssignedExtension> = [];

for (let id of extensionIds) {
const { config: rawExtensionConfig } = getExtensionConfigFromStore(extensionConfigStoreState, slotName, id);
const rawExtensionSlotExtensionConfig = getExtensionConfigFromExtensionSlotStore(config, slotName, id);
const rawExtensionSlotExtensionConfig = getExtensionConfigFromExtensionSlotStore(slotConfig, slotName, id);
const extensionConfig = merge(rawExtensionConfig, rawExtensionSlotExtensionConfig);

const name = getExtensionNameFromId(id);
Expand Down Expand Up @@ -378,7 +314,7 @@ function getAssignedExtensionsFromSlotData(
continue;
}

extensions.push({
assignedExtensions.push({
id,
name,
moduleName: extension.moduleName,
Expand All @@ -391,38 +327,44 @@ function getAssignedExtensionsFromSlotData(
}
}

return extensions;
return assignedExtensions;
}

/**
* Gets the list of extensions assigned to a given slot
*
* Gets the list of AssignedExtensions for a given slot
* @see AssignedExtension
* @param slotName The slot to load the assigned extensions for
* @returns An array of extensions assigned to the named slot
*/
export function getAssignedExtensions(slotName: string): Array<AssignedExtension> {
const internalState = extensionInternalStore.getState();
const { config: slotConfig } = getExtensionSlotConfig(slotName);
const extensionStoreState = extensionsConfigStore.getState();
const featureFlagState = featureFlagsStore.getState();
const sessionState = sessionStore.getState();
const isOnline = isOnlineFn();
const enabledFeatureFlags = Object.entries(featureFlagState.flags)
.filter(([, { enabled }]) => enabled)
.map(([name]) => name);
const extensionIds = getExtensionIds(slotName);
return getAssignedExtensionsByExtensionIds(slotName, extensionIds);
}

return getAssignedExtensionsFromSlotData(
slotName,
internalState,
slotConfig,
extensionStoreState,
enabledFeatureFlags,
isOnline,
sessionState.session,
);
/**
* Get an single assigned extension, as if it is assigned to the 'global' extension slot, by extensionId
* @see AssignedExtension
* @param extensionId
*/
export function getSingleAssignedExtension(extensionId: string): AssignedExtension | null {
const ret = getAssignedExtensionsByExtensionIds('global', [extensionId]);
return ret.length > 0 ? ret[0] : null;
}

function calculateAssignedIds(config: ExtensionSlotConfig, attachedIds: Array<string>) {
/**
* Calculate a list of extension ids that are slotted into the specified slot
* either via:
* - the "slot" attribute in the extension declaration in routes.json, or
* - calling attach(), or
* - the "add" attribute in the configuration of the slot
* @param slotName the name of the slot
* @returns a list of extensions ids slotted into the specified slot
*/
function getExtensionIds(slotName: string) {
const internalState = extensionInternalStore.getState();
const { config } = getExtensionSlotConfig(slotName);

const attachedIds = internalState.slots[slotName].attachedIds;
const addedIds = config.add || [];
const removedIds = config.remove || [];
const idOrder = config.order || [];
Expand Down
5 changes: 3 additions & 2 deletions packages/framework/esm-extensions/src/public.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export {
getExtensionNameFromId,
registerExtension,
attach,
detach,
detachAll,
getAssignedExtensions,
getExtensionNameFromId,
getSingleAssignedExtension,
registerExtension,
registerExtensionSlot,
} from './extensions';
export { type LeftNavStore, setLeftNav, unsetLeftNav, type SetLeftNavParams } from './left-nav';
Expand Down
10 changes: 10 additions & 0 deletions packages/framework/esm-extensions/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ export interface ExtensionSlotState {
assignedExtensions: Array<AssignedExtension>;
}

/**
* An AssignedExtension is instantiated when it is determined that an extension
* should be rendered for a particular extension slot, due to all of the following:
* - the extension declaration in routes.json specifies the slot, or
* the `attach()` function is called to attach the extension to the slot, or
* the configuration of the slot adds the extension
* - the configuration of the slot does not remove the extension
* - the extension is not filtered by featureFlag, online / offline, privileges
* (defined in extension declaration) or 'Display Condition' (defined in configuration)
*/
export interface AssignedExtension {
readonly id: string;
readonly name: string;
Expand Down
Loading
Loading