-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Share] Convert Share registry to async registry #224139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| /* | ||
| * 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 { useEffect, useState } from 'react'; | ||
| import type { DiscoverServices } from '../../../build_services'; | ||
|
|
||
| export function useHasShareIntegration({ share }: DiscoverServices) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const [hasShareIntegration, setHasShareIntegration] = useState<boolean>(false); | ||
|
|
||
| useEffect(() => { | ||
| let canceled = false; | ||
| if (!share) return; | ||
| const checkShareIntegration = async () => { | ||
| const integrations = await share.availableIntegrations('search', 'export'); | ||
| if (!canceled) { | ||
| setHasShareIntegration(integrations.length > 0); | ||
| } | ||
| }; | ||
|
|
||
| checkShareIntegration(); | ||
|
|
||
| return () => { | ||
| canceled = true; | ||
| }; | ||
| }, [share]); | ||
|
|
||
| return hasShareIntegration; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,9 +19,12 @@ import type { | |
| ShareIntegration, | ||
| ShareRegistryApiStart, | ||
| ShareMenuProviderLegacy, | ||
| ShareIntegrationMapKey, | ||
| } from '../types'; | ||
| import type { AnonymousAccessServiceContract } from '../../common/anonymous_access'; | ||
|
|
||
| type ShareContextMapKey = InternalShareActionIntent | ShareIntegrationMapKey | 'legacy'; | ||
|
|
||
| export class ShareRegistry implements ShareRegistryPublicApi { | ||
| private urlService?: BrowserUrlService; | ||
| private anonymousAccessServiceProvider?: () => AnonymousAccessServiceContract; | ||
|
|
@@ -35,10 +38,9 @@ export class ShareRegistry implements ShareRegistryPublicApi { | |
| this.registerLinkShareAction(); | ||
| this.registerEmbedShareAction(); | ||
| } | ||
|
|
||
| private readonly shareOptionsStore: Record< | ||
| string, | ||
| Map<InternalShareActionIntent | `integration-${string}` | 'legacy', ShareActionIntents> | ||
| Map<ShareContextMapKey, () => Promise<ShareActionIntents>> | ||
| > = { | ||
| [this.globalMarker]: new Map(), | ||
| }; | ||
|
|
@@ -70,94 +72,98 @@ export class ShareRegistry implements ShareRegistryPublicApi { | |
| }; | ||
| } | ||
|
|
||
| private registerShareIntentAction( | ||
| // Async registration for share actions | ||
| private async registerShareIntentAction( | ||
| shareObject: string, | ||
| shareActionIntent: ShareActionIntents | ||
| ): void { | ||
| key: ShareContextMapKey, | ||
| getShareActionIntent: () => Promise<ShareActionIntents> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be better for this API we were to actually accept the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another advantage to this approach would mean that we'll be able to check available integrations synchronously rather than async, and not have to modify functions returning nav config for other apps becoming async, with the resolution of the share implementation still async. |
||
| ): Promise<void> { | ||
| if (!this.shareOptionsStore[shareObject]) { | ||
| this.shareOptionsStore[shareObject] = new Map(); | ||
| } | ||
|
|
||
| const shareContextMap = this.shareOptionsStore[shareObject]; | ||
|
|
||
| const recordKey = | ||
| shareActionIntent.shareType === 'integration' | ||
| ? (`integration-${shareActionIntent.groupId || 'unknown'}-${shareActionIntent.id}` as const) | ||
| : shareActionIntent.shareType; | ||
|
|
||
| if (shareContextMap.has(recordKey)) { | ||
| if (shareContextMap.has(key)) { | ||
| throw new Error( | ||
| `Share action with type [${shareActionIntent.shareType}] for app [${shareObject}] has already been registered.` | ||
| `Share action with key [${key}] for app [${shareObject}] has already been registered.` | ||
| ); | ||
| } | ||
|
|
||
| shareContextMap.set(recordKey, shareActionIntent); | ||
| shareContextMap.set(key, getShareActionIntent); | ||
| } | ||
|
|
||
| private registerLinkShareAction(): void { | ||
| this.registerShareIntentAction(this.globalMarker, { | ||
| this.registerShareIntentAction(this.globalMarker, 'link', async () => ({ | ||
| shareType: 'link', | ||
| config: ({ urlService }) => ({ | ||
| shortUrlService: urlService?.shortUrls.get(null)!, | ||
| }), | ||
| }); | ||
| })); | ||
| } | ||
|
|
||
| private registerEmbedShareAction(): void { | ||
| this.registerShareIntentAction(this.globalMarker, { | ||
| this.registerShareIntentAction(this.globalMarker, 'embed', async () => ({ | ||
| shareType: 'embed', | ||
| config: ({ urlService, anonymousAccessServiceProvider }) => ({ | ||
| anonymousAccess: anonymousAccessServiceProvider!(), | ||
| shortUrlService: urlService.shortUrls.get(null), | ||
| }), | ||
| }); | ||
| })); | ||
| } | ||
|
|
||
| /** | ||
| * @description provides an escape hatch to support allowing legacy share menu items to be registered | ||
| */ | ||
| private register(value: ShareMenuProviderLegacy) { | ||
| // implement backwards compatibility for the share plugin | ||
| this.registerShareIntentAction(this.globalMarker, { | ||
| shareType: 'legacy', | ||
| id: value.id, | ||
| config: value.getShareMenuItemsLegacy, | ||
| private register(value: ShareMenuProviderLegacy | Promise<ShareMenuProviderLegacy>) { | ||
| this.registerShareIntentAction(this.globalMarker, 'legacy', async () => { | ||
| const resolvedValue = await Promise.resolve(value); | ||
| return { | ||
| shareType: 'legacy', | ||
| id: resolvedValue.id, | ||
| config: resolvedValue.getShareMenuItemsLegacy, | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| private registerShareIntegration<I extends ShareIntegration>( | ||
| ...args: [string, Omit<I, 'shareType'>] | [Omit<I, 'shareType'>] | ||
| shareObject: string, | ||
| key: ShareIntegrationMapKey, | ||
| getShareActionIntent: () => Promise<Omit<I, 'shareType'>> | ||
|
Comment on lines
+130
to
+132
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This forces users to specify a share object, we should at the very least specify that |
||
| ): void { | ||
| const [shareObject, shareActionIntent] = | ||
| args.length === 1 ? [this.globalMarker, args[0]] : args; | ||
| this.registerShareIntentAction(shareObject, { | ||
| this.registerShareIntentAction(shareObject, key, async () => ({ | ||
| shareType: 'integration', | ||
| ...shareActionIntent, | ||
| }); | ||
| ...(await getShareActionIntent()), | ||
| })); | ||
| } | ||
|
|
||
| private getShareConfigOptionsForObject( | ||
| private async getShareConfigOptionsForObject( | ||
| objectType: ShareContext['objectType'] | ||
| ): ShareActionIntents[] { | ||
| ): Promise<ShareActionIntents[]> { | ||
| const shareContextMap = this.shareOptionsStore[objectType]; | ||
| const globalOptions = Array.from(this.shareOptionsStore[this.globalMarker].values()); | ||
|
|
||
| if (!shareContextMap) { | ||
| return globalOptions; | ||
| } | ||
| const allFactories = shareContextMap | ||
| ? [...globalOptions, ...Array.from(shareContextMap.values())] | ||
| : globalOptions; | ||
|
|
||
| return globalOptions.concat(Array.from(shareContextMap.values())); | ||
| return Promise.all(allFactories.map((factory) => factory())); | ||
| } | ||
|
|
||
| /** | ||
| * Returns all share actions that are available for the given object type. | ||
| */ | ||
| private availableIntegrations(objectType: string, groupId?: string): ShareActionIntents[] { | ||
| private async availableIntegrations( | ||
| objectType: string, | ||
| groupId?: string | ||
| ): Promise<ShareActionIntents[]> { | ||
| if (!this.capabilities || !this.getLicense) { | ||
| throw new Error('ShareOptionsManager#start was not invoked'); | ||
| } | ||
|
|
||
| return this.getShareConfigOptionsForObject(objectType).filter((share) => { | ||
| const shareActions = await this.getShareConfigOptionsForObject(objectType); | ||
|
|
||
| return shareActions.filter((share) => { | ||
| if ( | ||
| groupId && | ||
| (share.shareType !== 'integration' || | ||
|
|
@@ -179,16 +185,18 @@ export class ShareRegistry implements ShareRegistryPublicApi { | |
| }); | ||
| } | ||
|
|
||
| private resolveShareItemsForShareContext({ | ||
| private async resolveShareItemsForShareContext({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this signature changed we'll need to in turn change the signature of |
||
| objectType, | ||
| isServerless, | ||
| ...shareContext | ||
| }: ShareContext & { isServerless: boolean }): ShareConfigs[] { | ||
| }: ShareContext & { isServerless: boolean }): Promise<ShareConfigs[]> { | ||
| if (!this.urlService || !this.anonymousAccessServiceProvider) { | ||
| throw new Error('ShareOptionsManager#start was not invoked'); | ||
| } | ||
|
|
||
| return this.availableIntegrations(objectType) | ||
| const shareActions = await this.availableIntegrations(objectType); | ||
|
|
||
| return shareActions | ||
| .map((shareAction) => { | ||
| let config: ShareConfigs['config'] | null; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also https://github.com/elastic/kibana/pull/224139/files#r2152953864