From 682ce63cc67bee74ddf44158332380b1883b4fda Mon Sep 17 00:00:00 2001 From: Ingrid Fielker Date: Tue, 1 Oct 2024 14:12:26 -0400 Subject: [PATCH 1/2] Adding SDK-created extensions support --- .../Details/DetailCard/Tabs/ParamList.tsx | 38 +++++++++++++------ .../Extensions/List/ExtensionsCallout.tsx | 4 +- .../ExtensionsTable/ExtensionTableRow.tsx | 5 +++ .../api/internal/useExtensionsData.tsx | 1 + .../Extensions/api/useExtensions.tsx | 7 ++++ src/components/Extensions/models.ts | 1 + 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/components/Extensions/Details/DetailCard/Tabs/ParamList.tsx b/src/components/Extensions/Details/DetailCard/Tabs/ParamList.tsx index 7b059683c..ca1ac4a51 100644 --- a/src/components/Extensions/Details/DetailCard/Tabs/ParamList.tsx +++ b/src/components/Extensions/Details/DetailCard/Tabs/ParamList.tsx @@ -24,25 +24,42 @@ import { useExtension } from '../../../api/useExtension'; import { EventsConfig } from './EventsConfig'; import styles from './ParamList.module.scss'; import { ParamValue } from './ParamValue'; +import { isDynamicExtension } from '../../../api/useExtensions'; function ParamList() { const extension = useExtension()!; + const isDynamic = isDynamicExtension(extension); + const docSuffix = isDynamic ? + 'extensions/manage-installed-extensions?interface=sdk#reconfigure' : + 'extensions/manifest'; + + const dynamicFragment = ( + + Reconfigure is not available in the emulator. You can + reconfigure parameters by updating them in the code. + Then rebuild the code if applicable and refresh this page. + + ); + + const staticFragment = ( + <> + + Reconfigure is not available in the emulator. You can + reconfigure parameters by updating your .env files with: + +
+ firebase ext:configure {extension.id} --local) + + ) + return (
- - Reconfigure is not available in the emulator. You can - reconfigure parameters by updating your .env files with: - - } - > - firebase ext:configure {extension.id} --local + {isDynamic ? dynamicFragment : staticFragment} -
{(extension.params || []).map((param) => { diff --git a/src/components/Extensions/List/ExtensionsCallout.tsx b/src/components/Extensions/List/ExtensionsCallout.tsx index 5f98ab730..2b32b3e0b 100644 --- a/src/components/Extensions/List/ExtensionsCallout.tsx +++ b/src/components/Extensions/List/ExtensionsCallout.tsx @@ -30,12 +30,12 @@ export const ExtensionCallout: React.FC< actions={
- Learn how to manage your extensions manifest + Learn how to manage your extensions
diff --git a/src/components/Extensions/List/ExtensionsTable/ExtensionTableRow.tsx b/src/components/Extensions/List/ExtensionsTable/ExtensionTableRow.tsx index 77d364d50..ebe72a2a0 100644 --- a/src/components/Extensions/List/ExtensionsTable/ExtensionTableRow.tsx +++ b/src/components/Extensions/List/ExtensionsTable/ExtensionTableRow.tsx @@ -65,6 +65,11 @@ export const ExtensionsTableRow: React.FC< {extension.ref}
+
+ + {extension.id} + +
- - + {isDynamic ? dynamicFragment : staticFragment} +
+ + + +
{(extension.params || []).map((param) => { diff --git a/src/components/Extensions/api/internal/useExtensionBackends.test.tsx b/src/components/Extensions/api/internal/useExtensionBackends.test.tsx index e6cbdeaf3..a3ab48d08 100644 --- a/src/components/Extensions/api/internal/useExtensionBackends.test.tsx +++ b/src/components/Extensions/api/internal/useExtensionBackends.test.tsx @@ -43,6 +43,10 @@ describe('useExtensionBackends', () => { await waitFor(() => result.current !== null); await waitFor(() => delay(100)); - expect(result.current).toEqual([BACKEND_LIST[0], BACKEND_LIST[1]]); + expect(result.current).toEqual([ + BACKEND_LIST[0], + BACKEND_LIST[1], + BACKEND_LIST[2], + ]); }); }); diff --git a/src/components/Extensions/api/internal/useExtensionsData.test.tsx b/src/components/Extensions/api/internal/useExtensionsData.test.tsx index 86e15dab2..807fc595e 100644 --- a/src/components/Extensions/api/internal/useExtensionsData.test.tsx +++ b/src/components/Extensions/api/internal/useExtensionsData.test.tsx @@ -44,6 +44,7 @@ describe('useExtensionsData', () => { expect(result.current).toEqual([ { + id: 'pirojok-the-published-extension', authorName: 'Awesome Inc', authorUrl: 'https://google.com/awesome', params: [], @@ -93,7 +94,7 @@ describe('useExtensionsData', () => { sourceUrl: '', extensionDetailsUrl: 'https://firebase.google.com/products/extensions/good-tool', - id: 'pirojok-the-published-extension', + ref: 'awesome-inc/good-tool@0.0.1', iconUri: 'https://www.gstatic.com/mobilesdk/211001_mobilesdk/google-pay-logo.svg', @@ -101,6 +102,7 @@ describe('useExtensionsData', () => { 'https://www.gstatic.com/mobilesdk/160503_mobilesdk/logo/2x/firebase_128dp.png', }, { + id: 'pirojok-the-local-extension', authorName: 'Awesome Inc', authorUrl: 'https://google.com/awesome', params: [], @@ -150,7 +152,65 @@ describe('useExtensionsData', () => { sourceUrl: '', extensionDetailsUrl: 'https://firebase.google.com/products/extensions/good-tool', - id: 'pirojok-the-local-extension', + }, + { + id: 'pirojok-the-dynamic-extension', + authorName: 'Awesome Inc', + authorUrl: 'https://google.com/awesome', + params: [], + name: 'good-tool', + displayName: 'Pirojok-the-tool', + specVersion: 'v1beta', + env: { + ALLOWED_EVENT_TYPES: 'google.firebase.v1.custom-event-occurred', + EVENTARC_CHANNEL: + 'projects/test-project/locations/us-west1/channels/firebase', + }, + allowedEventTypes: ['google.firebase.v1.custom-event-occurred'], + eventarcChannel: + 'projects/test-project/locations/us-west1/channels/firebase', + events: [ + { + type: 'google.firebase.v1.custom-event-occurred', + description: 'A custom event occurred', + }, + ], + apis: [ + { + apiName: 'storage-component.googleapis.com', + reason: 'Needed to use Cloud Storage', + }, + ], + resources: [ + { + type: 'firebaseextensions.v1beta.function', + description: + 'Listens for new images uploaded to your specified Cloud Storage bucket, resizes the images, then stores the resized images in the same bucket. Optionally keeps or deletes the original images.', + name: 'generateResizedImage', + propertiesYaml: + // eslint-disable-next-line no-template-curly-in-string + 'availableMemoryMb: 1024\neventTrigger:\n eventType: google.storage.object.finalize\n resource: projects/_/buckets/${param:IMG_BUCKET}\nlocation: ${param:LOCATION}\nruntime: nodejs14\n', + }, + ], + roles: [ + { + role: 'storage.admin', + reason: + 'Allows the extension to store resized images in Cloud Storage', + }, + ], + readmeContent: '', + postinstallContent: '### See it in action', + sourceUrl: '', + extensionDetailsUrl: + 'https://firebase.google.com/products/extensions/good-tool', + + ref: 'awesome-inc/good-tool@0.0.1', + iconUri: + 'https://www.gstatic.com/mobilesdk/211001_mobilesdk/google-pay-logo.svg', + publisherIconUri: + 'https://www.gstatic.com/mobilesdk/160503_mobilesdk/logo/2x/firebase_128dp.png', + labels: { createdBy: 'SDK', codebase: 'default' }, }, ]); }); diff --git a/src/components/Extensions/api/internal/useExtensionsData.tsx b/src/components/Extensions/api/internal/useExtensionsData.tsx index d621ea773..231074431 100644 --- a/src/components/Extensions/api/internal/useExtensionsData.tsx +++ b/src/components/Extensions/api/internal/useExtensionsData.tsx @@ -14,7 +14,11 @@ * limitations under the License. */ import { Extension, ExtensionResource, Resource } from '../../models'; -import { ExtensionBackend, isLocalExtension } from '../useExtensions'; +import { + ExtensionBackend, + isDynamicExtension, + isLocalExtension, +} from '../useExtensions'; import { useExtensionBackends } from './useExtensionBackends'; const EXTENSION_DETAILS_URL_BASE = @@ -46,7 +50,8 @@ export function convertBackendToExtension( : r; }; - const shared = { + const shared: Omit = { + id: backend.extensionInstanceId, authorUrl: spec.author?.url ?? '', params: spec.params.map((p) => { return { @@ -68,20 +73,21 @@ export function convertBackendToExtension( postinstallContent: spec.postinstallContent ?? '', sourceUrl: spec.sourceUrl ?? '', extensionDetailsUrl: EXTENSION_DETAILS_URL_BASE + spec.name, - labels: backend.labels, }; + if (isDynamicExtension(backend)) { + shared.labels = backend.labels; + } + if (isLocalExtension(backend)) { return { ...shared, authorName: spec.author?.authorName ?? '', - id: backend.extensionInstanceId, }; } return { ...shared, - id: backend.extensionInstanceId, ref: backend.extensionVersion.ref, authorName: spec.author?.authorName ?? diff --git a/src/components/Extensions/api/useExtensions.test.tsx b/src/components/Extensions/api/useExtensions.test.tsx index 0813e48b0..26b8a1e9e 100644 --- a/src/components/Extensions/api/useExtensions.test.tsx +++ b/src/components/Extensions/api/useExtensions.test.tsx @@ -16,11 +16,11 @@ import { renderHook } from '@testing-library/react'; -import { EXTENSION } from '../testing/utils'; +import { DYNAMIC_EXTENSION, EXTENSION } from '../testing/utils'; import { ExtensionsProvider, useExtensions } from './useExtensions'; describe('useExtensions', () => { - it('returns the list of extension backends', () => { + it('returns the list of static extension backends', () => { const wrapper: React.FC> = ({ children, }) => ( @@ -33,4 +33,18 @@ describe('useExtensions', () => { expect(result.current).toEqual([EXTENSION]); }); + + it('returns the list of dynamic extension backends', () => { + const wrapper: React.FC> = ({ + children, + }) => ( + + {children} + + ); + + const { result } = renderHook(() => useExtensions(), { wrapper }); + + expect(result.current).toEqual([DYNAMIC_EXTENSION]); + }); }); diff --git a/src/components/Extensions/api/useExtensions.tsx b/src/components/Extensions/api/useExtensions.tsx index 06d8fa780..72e257179 100644 --- a/src/components/Extensions/api/useExtensions.tsx +++ b/src/components/Extensions/api/useExtensions.tsx @@ -75,7 +75,7 @@ export function isLocalExtension( } export function isDynamicExtension( - extension: Extension + extension: Extension | ExtensionBackend ): boolean { - return extension.labels?.createdBy === "SDK"; + return extension.labels?.createdBy === 'SDK'; } diff --git a/src/components/Extensions/models.ts b/src/components/Extensions/models.ts index 4cdc9e122..c508fb1db 100644 --- a/src/components/Extensions/models.ts +++ b/src/components/Extensions/models.ts @@ -162,6 +162,8 @@ export interface Extension { sourceUrl: string; postinstallContent: string; extensionDetailsUrl: string; + name?: string; + env?: Record; labels?: Record; } diff --git a/src/components/Extensions/testing/utils.ts b/src/components/Extensions/testing/utils.ts index 16ed240b8..d0b700835 100644 --- a/src/components/Extensions/testing/utils.ts +++ b/src/components/Extensions/testing/utils.ts @@ -109,17 +109,18 @@ export const CONFIG_WITH_EXTENSION = { host: 'pirojok', port: 689, }, + experiments: [], }; export const BACKEND_LIST: ExtensionBackend[] = [ // published extension { + extensionInstanceId: 'pirojok-the-published-extension', env: { ALLOWED_EVENT_TYPES: 'google.firebase.v1.custom-event-occurred', EVENTARC_CHANNEL: 'projects/test-project/locations/us-west1/channels/firebase', }, - extensionInstanceId: 'pirojok-the-published-extension', extension: BACKEND_EXTENSION, extensionVersion: EXTENSION_VERSION, functionTriggers: [], @@ -135,7 +136,20 @@ export const BACKEND_LIST: ExtensionBackend[] = [ extensionSpec: EXTENSION_SPEC, functionTriggers: [], }, - // Not and extension back-end + // dynamic (published) extension + { + extensionInstanceId: 'pirojok-the-dynamic-extension', + env: { + ALLOWED_EVENT_TYPES: 'google.firebase.v1.custom-event-occurred', + EVENTARC_CHANNEL: + 'projects/test-project/locations/us-west1/channels/firebase', + }, + extension: BACKEND_EXTENSION, + extensionVersion: EXTENSION_VERSION, + functionTriggers: [], + labels: { createdBy: 'SDK', codebase: 'default' }, + }, + // Not an extension back-end { env: {}, extensionSpec: EXTENSION_SPEC, @@ -165,3 +179,8 @@ export const EXTENSION: Extension = { resources: [{ name: 'resource', description: 'description', type: '' }], apis: [{ apiName: 'api-name', reason: 'reason' }], }; + +export const DYNAMIC_EXTENSION: Extension = { + ...EXTENSION, + labels: { createdBy: 'SDK', codebase: 'default' }, +};