diff --git a/.changeset/warm-rivers-dance.md b/.changeset/warm-rivers-dance.md new file mode 100644 index 0000000000..d128ddeb9e --- /dev/null +++ b/.changeset/warm-rivers-dance.md @@ -0,0 +1,188 @@ +-- +'@backstage/create-app': patch + +--- + +**Fully migrated the template to the new composability API** + +The `create-app` template is now fully migrated to the new composability API, see [Composability System Migration Documentation](https://backstage.io/docs/plugins/composability) for explanations and more details. The final change which is now done was to migrate the `EntityPage` from being a component built on top of the `EntityPageLayout` and several more custom components, to an element tree built with `EntitySwitch` and `EntityLayout`. + +To apply this change to an existing plugin, it is important that all plugins that you are using have already been migrated. In this case the most crucial piece is that no entity page cards of contents may require the `entity` prop, and they must instead consume the entity from context using `useEntity`. + +Since this change is large with a lot of repeated changes, we'll describe a couple of common cases rather than the entire change. If your entity pages are unchanged from the `create-app` template, you can also just bring in the latest version directly from the [template itself](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx). + +The first step of the change is to change the `packages/app/src/components/catalog/EntityPage.tsx` export to `entityPage` rather than `EntityPage`. This will require an update to `App.tsx`, which is the only change we need to do outside of `EntityPage.tsx`: + +```diff +-import { EntityPage } from './components/catalog/EntityPage'; ++import { entityPage } from './components/catalog/EntityPage'; + + } + > +- ++ {entityPage} + +``` + +The rest of the changes happen within `EntityPage.tsx`, and can be split into two broad categories, updating page components, and updating switch components. + +#### Migrating Page Components + +Let's start with an example of migrating a user page component. The following is the old code in the template: + +```tsx +const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( + + + + + + + + +); + +const UserEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + +); +``` + +There's the main `UserEntityPage` component, and the `UserOverviewContent` component. Let's start with migrating the page contents, which we do by rendering an element rather than creating a component, as well as replace the cards with their new composability compatible variants. The new cards and content components can be identified by the `Entity` prefix. + +```tsx +const userOverviewContent = ( + + + + + + + + +); +``` + +Now let's migrate the page component, again by converting it into a rendered element instead of a component, as well as replacing the use of `EntityPageLayout` with `EntityLayout`. + +```tsx +const userPage = ( + + + {userOverviewContent} + + +); +``` + +At this point the `userPage` is quite small, so throughout this migration we have inlined the page contents for all pages. This is an optional step, but may help reduce noise. The final page now looks like this: + +```tsx +const userPage = ( + + + + + + + + + + + + +); +``` + +#### Migrating Switch Components + +Switch components were used to select what entity page components or cards to render, based on for example the kind of entity. For this example we'll focus on the root `EntityPage` switch component, but the process is the same for example for the CI/CD switcher. + +The old `EntityPage` looked like this: + +```tsx +export const EntityPage = () => { + const { entity } = useEntity(); + + switch (entity?.kind?.toLocaleLowerCase('en-US')) { + case 'component': + return ; + case 'api': + return ; + case 'group': + return ; + case 'user': + return ; + case 'system': + return ; + case 'domain': + return ; + case 'location': + case 'resource': + case 'template': + default: + return ; + } +}; +``` + +In order to migrate to the composability API, we need to make this an element instead of a component, which means we're unable to keep the switch statement as is. To help with this, the catalog plugin provides an `EntitySwitch` component, which functions similar to a regular `switch` statement, which the first match being the one that is rendered. The catalog plugin also provides a number of built-in filter functions to use, such as `isKind` and `isComponentType`. + +To migrate the `EntityPage`, we convert the `switch` statement into an `EntitySwitch` element, and each `case` statement into an `EntitySwitch.Case` element. We also move over to use our new element version of the page components, with the result looking like this: + +```tsx +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + +); +``` + +Another example is the `ComponentEntityPage`, which is migrated from this: + +```tsx +export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { + switch (entity?.spec?.type) { + case 'service': + return ; + case 'website': + return ; + default: + return ; + } +}; +``` + +To this: + +```tsx +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); +``` + +Note that if you want to conditionally render some piece of content, you can omit the default `EntitySwitch.Case`. If no case is matched in an `EntitySwitch`, nothing will be rendered. diff --git a/packages/app/package.json b/packages/app/package.json index e5dbbaa5ff..d00ddefc76 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -42,10 +42,10 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.12", - "@roadiehq/backstage-plugin-buildkite": "^0.1.3", - "@roadiehq/backstage-plugin-github-insights": "^0.3.2", - "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6", - "@roadiehq/backstage-plugin-travis-ci": "^0.4.5", + "@roadiehq/backstage-plugin-buildkite": "^0.3.0", + "@roadiehq/backstage-plugin-github-insights": "^0.3.3", + "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.9", + "@roadiehq/backstage-plugin-travis-ci": "^0.4.7", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index defa70dbb7..379fc7e52d 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -48,8 +48,8 @@ import React from 'react'; import { hot } from 'react-hot-loader/root'; import { Navigate, Route } from 'react-router'; import { apis } from './apis'; -import { EntityPage } from './components/catalog/EntityPage'; import { Root } from './components/Root'; +import { entityPage } from './components/catalog/EntityPage'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -96,7 +96,7 @@ const routes = ( path="/catalog/:namespace/:kind/:name" element={} > - + {entityPage} } /> } /> diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx index 1392f77a6d..9860792ea0 100644 --- a/packages/app/src/components/catalog/EntityPage.test.tsx +++ b/packages/app/src/components/catalog/EntityPage.test.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; -import { CICDSwitcher } from './EntityPage'; -import { UrlPatternDiscovery, ApiProvider, ApiRegistry } from '@backstage/core'; -import { - buildKiteApiRef, - BuildkiteApi, -} from '@roadiehq/backstage-plugin-buildkite'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { EntityLayout } from '@backstage/plugin-catalog'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { cicdContent } from './EntityPage'; +import { githubActionsApiRef } from '@backstage/plugin-github-actions'; describe('EntityPage Test', () => { const entity = { @@ -29,7 +29,7 @@ describe('EntityPage Test', () => { metadata: { name: 'ExampleComponent', annotations: { - 'buildkite.com/project-slug': 'exampleProject/examplePipeline', + 'github.com/project-slug': 'example/project', }, }, spec: { @@ -39,24 +39,36 @@ describe('EntityPage Test', () => { }, }; - const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com'); + const mockedApi = { + listWorkflowRuns: jest.fn().mockResolvedValue([]), + getWorkflow: jest.fn(), + getWorkflowRun: jest.fn(), + reRunWorkflow: jest.fn(), + downloadJobLogsForWorkflowRun: jest.fn(), + } as jest.Mocked; - const apis = ApiRegistry.from([ - [buildKiteApiRef, new BuildkiteApi({ discoveryApi })], - ]); + const apis = ApiRegistry.with(githubActionsApiRef, mockedApi); - describe('CICDSwitcher Test', () => { - it('Should render Buildkite View', async () => { - const renderedComponent = await renderWithEffects( - wrapInTestApp( - - - , - ), + describe('cicdContent', () => { + it('Should render GitHub Actions View', async () => { + const rendered = await renderInTestApp( + + + + + {cicdContent} + + + + , ); - expect( - renderedComponent.getByText(/exampleProject\/examplePipeline/), - ).toBeInTheDocument(); + + expect(rendered.getByText('ExampleComponent')).toBeInTheDocument(); + + await expect( + rendered.findByText('No Workflow Data'), + ).resolves.toBeInTheDocument(); + expect(rendered.getByText('Create new Workflow')).toBeInTheDocument(); }); }); }); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 5dc4f4c570..d8ee62a12c 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -14,174 +14,96 @@ * limitations under the License. */ -import { - ApiEntity, - DomainEntity, - Entity, - GroupEntity, - SystemEntity, - UserEntity, -} from '@backstage/catalog-model'; +import React, { ReactNode, useMemo, useState } from 'react'; +import BadgeIcon from '@material-ui/icons/CallToAction'; import { EmptyState } from '@backstage/core'; import { - ApiDefinitionCard, - ConsumedApisCard, - ConsumingComponentsCard, + EntityApiDefinitionCard, + EntityConsumingComponentsCard, EntityHasApisCard, - ProvidedApisCard, - ProvidingComponentsCard, + EntityProvidingComponentsCard, + EntityProvidedApisCard, + EntityConsumedApisCard, } from '@backstage/plugin-api-docs'; import { EntityBadgesDialog } from '@backstage/plugin-badges'; import { - AboutCard, + EntityAboutCard, EntityHasComponentsCard, EntityHasSubcomponentsCard, EntityHasSystemsCard, + EntityLayout, EntityLinksCard, - EntityPageLayout, EntitySystemDiagramCard, + EntitySwitch, + isComponentType, + isKind, } from '@backstage/plugin-catalog'; -import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react'; import { - isPluginApplicableToEntity as isCircleCIAvailable, - Router as CircleCIRouter, + EntityCircleCIContent, + isCircleCIAvailable, } from '@backstage/plugin-circleci'; import { - isPluginApplicableToEntity as isCloudbuildAvailable, - Router as CloudbuildRouter, + EntityCloudbuildContent, + isCloudbuildAvailable, } from '@backstage/plugin-cloudbuild'; import { - isPluginApplicableToEntity as isGitHubActionsAvailable, - RecentWorkflowRunsCard, - Router as GitHubActionsRouter, + EntityGithubActionsContent, + EntityRecentGithubActionsRunsCard, + isGithubActionsAvailable, } from '@backstage/plugin-github-actions'; import { - isPluginApplicableToEntity as isJenkinsAvailable, - LatestRunCard as JenkinsLatestRunCard, - Router as JenkinsRouter, + EntityJenkinsContent, + EntityLatestJenkinsRunCard, + isJenkinsAvailable, } from '@backstage/plugin-jenkins'; -import { Router as KafkaRouter } from '@backstage/plugin-kafka'; -import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; +import { EntityKafkaContent } from '@backstage/plugin-kafka'; +import { EntityKubernetesContent } from '@backstage/plugin-kubernetes'; import { - EmbeddedRouter as LighthouseRouter, - isPluginApplicableToEntity as isLighthouseAvailable, - LastLighthouseAuditCard, + EntityLastLighthouseAuditCard, + EntityLighthouseContent, + isLighthouseAvailable, } from '@backstage/plugin-lighthouse'; import { - GroupProfileCard, - MembersListCard, - OwnershipCard, - UserProfileCard, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, + EntityUserProfileCard, } from '@backstage/plugin-org'; import { - isPluginApplicableToEntity as isPagerDutyAvailable, - PagerDutyCard, + EntityPagerDutyCard, + isPagerDutyAvailable, } from '@backstage/plugin-pagerduty'; import { + EntityRollbarContent, isRollbarAvailable, - Router as RollbarRouter, } from '@backstage/plugin-rollbar'; -import { Router as SentryRouter } from '@backstage/plugin-sentry'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +import { EntitySentryContent } from '@backstage/plugin-sentry'; +import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { EntityTodoContent } from '@backstage/plugin-todo'; import { Button, Grid } from '@material-ui/core'; -import { - isPluginApplicableToEntity as isBuildkiteAvailable, - Router as BuildkiteRouter, -} from '@roadiehq/backstage-plugin-buildkite'; -import { - isPluginApplicableToEntity as isGitHubAvailable, - LanguagesCard, - ReadMeCard, - ReleasesCard, - Router as GitHubInsightsRouter, -} from '@roadiehq/backstage-plugin-github-insights'; -import { - isPluginApplicableToEntity as isPullRequestsAvailable, - PullRequestsStatsCard, - Router as PullRequestsRouter, -} from '@roadiehq/backstage-plugin-github-pull-requests'; -import { - isPluginApplicableToEntity as isTravisCIAvailable, - RecentTravisCIBuildsWidget, - Router as TravisCIRouter, -} from '@roadiehq/backstage-plugin-travis-ci'; -import React, { ReactNode, useMemo, useState } from 'react'; -import BadgeIcon from '@material-ui/icons/CallToAction'; +// import { +// EntityBuildkiteContent, +// isBuildkiteAvailable, +// } from '@roadiehq/backstage-plugin-buildkite'; +// import { +// EntityGitHubInsightsContent, +// EntityLanguagesCard, +// EntityReadMeCard, +// EntityReleasesCard, +// isGithubInsightsAvailable, +// } from '@roadiehq/backstage-plugin-github-insights'; +// import { +// EntityGithubPullRequestsContent, +// EntityGithubPullRequestsOverviewCard, +// isGithubPullRequestsAvailable, +// } from '@roadiehq/backstage-plugin-github-pull-requests'; +// import { +// EntityTravisCIContent, +// EntityTravisCIOverviewCard, +// isTravisciAvailable, +// } from '@roadiehq/backstage-plugin-travis-ci'; -export const CICDSwitcher = ({ entity }: { entity: Entity }) => { - // This component is just an example of how you can implement your company's logic in entity page. - // You can for example enforce that all components of type 'service' should use GitHubActions - switch (true) { - case isJenkinsAvailable(entity): - return ; - case isBuildkiteAvailable(entity): - return ; - case isCircleCIAvailable(entity): - return ; - case isCloudbuildAvailable(entity): - return ; - case isTravisCIAvailable(entity): - return ; - case isGitHubActionsAvailable(entity): - return ; - default: - return ( - - Read more - - } - /> - ); - } -}; - -const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => { - let content: ReactNode; - switch (true) { - case isJenkinsAvailable(entity): - content = ; - break; - case isTravisCIAvailable(entity): - content = ; - break; - case isGitHubActionsAvailable(entity): - content = ( - - ); - break; - default: - content = null; - } - if (!content) { - return null; - } - return ( - - {content} - - ); -}; - -export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => { - switch (true) { - case isRollbarAvailable(entity): - return ; - default: - return ; - } -}; - -const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { +const EntityLayoutWrapper = (props: { children?: ReactNode }) => { const [badgesDialogOpen, setBadgesDialogOpen] = useState(false); const extraMenuItems = useMemo(() => { @@ -196,9 +118,9 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { return ( <> - + {props.children} - + setBadgesDialogOpen(false)} @@ -207,350 +129,367 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { ); }; -const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( +export const cicdContent = ( + + + + + + {/* + + */} + + + + + + + + + + {/* + + */} + + + + + + + + Read more + + } + /> + + +); + +const cicdCard = ( + + + + + + + + {/* + + + + */} + + + + + + + +); + +const errorsContent = ( + + + + + + + + + +); + +const overviewContent = ( - - + + - {isPagerDutyAvailable(entity) && ( - - - - - - )} - - + + + + + + + + + + + - - {isGitHubAvailable(entity) && ( - <> - - - + + {cicdCard} + + {/* + Boolean(isGithubInsightsAvailable(e))}> + + + - - + + - - )} - {isLighthouseAvailable(entity) && ( - - - - )} - {isPullRequestsAvailable(entity) && ( - - - - )} - + + */} + + + + + + + + + + {/* + Boolean(isGithubPullRequestsAvailable(e))}> + + + + + */} + + ); -const ComponentApisContent = ({ entity }: { entity: Entity }) => ( - - - - - - - - -); +const serviceEntityPage = ( + + + {overviewContent} + -const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - -); + + {cicdContent} + -const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - -); + + {errorsContent} + -const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - -); - -export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { - switch (entity?.spec?.type) { - case 'service': - return ; - case 'website': - return ; - default: - return ; - } -}; - -const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( - - - - - - - + + + + + + + + - - + + + + + + + + + + + {/* + + */} + + {/* + + */} + + + + + + + + + +); + +const websiteEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + + + {errorsContent} + + + + + + + + + + + {/* + + */} + + {/* + + */} + + + + + +); + +const defaultEntityPage = ( + + + {overviewContent} + + + + + + + + + + +); + +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); + +const apiPage = ( + + + + + + + + + + + + + + - - + + + + + + + + + + ); -const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( - - - - - +const userPage = ( + + + + + + + + + + + + ); -const ApiEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - +const groupPage = ( + + + + + + + + + + + + + + + + + + + ); -const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( - - - - - - - - +const systemPage = ( + + + + + + + + + + + + + + + ); -const UserEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - +const domainPage = ( + + + + + + + + + + + + ); -const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => ( - - - - - - - - - - - +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + ); - -const GroupEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => ( - - - - - - - - - - - -); - -const SystemEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - -); - -const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => ( - - - - - - - - -); - -const DomainEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -export const EntityPage = () => { - const { entity } = useEntity(); - - switch (entity?.kind?.toLocaleLowerCase('en-US')) { - case 'component': - return ; - case 'api': - return ; - case 'group': - return ; - case 'user': - return ; - case 'system': - return ; - case 'domain': - return ; - case 'location': - case 'resource': - case 'template': - default: - return ; - } -}; diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 1cd7b77965..7323faeef2 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -19,7 +19,7 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechdocsPage } from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; import { apis } from './apis'; -import { EntityPage } from './components/catalog/EntityPage'; +import { entityPage } from './components/catalog/EntityPage'; import { Root } from './components/Root'; import * as plugins from './plugins'; @@ -47,7 +47,7 @@ const routes = ( path="/catalog/:namespace/:kind/:name" element={} > - + {entityPage} } /> } /> diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index d05f6bfa8d..f1aba46c52 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -15,308 +15,248 @@ */ import React from 'react'; import { Button, Grid } from '@material-ui/core'; -import { - ApiEntity, - DomainEntity, - Entity, - GroupEntity, - SystemEntity, - UserEntity, -} from '@backstage/catalog-model'; import { EmptyState } from '@backstage/core'; import { - ApiDefinitionCard, - ConsumedApisCard, - ConsumingComponentsCard, + EntityApiDefinitionCard, + EntityConsumedApisCard, + EntityConsumingComponentsCard, EntityHasApisCard, - ProvidedApisCard, - ProvidingComponentsCard, + EntityProvidedApisCard, + EntityProvidingComponentsCard, } from '@backstage/plugin-api-docs'; import { - AboutCard, + EntityAboutCard, + EntitySystemDiagramCard, EntityHasComponentsCard, EntityHasSystemsCard, - EntityPageLayout, + EntityLayout, + EntitySwitch, + isComponentType, + isKind, } from '@backstage/plugin-catalog'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { - isPluginApplicableToEntity as isGitHubActionsAvailable, - Router as GitHubActionsRouter, + isGithubActionsAvailable, + EntityGithubActionsContent, } from '@backstage/plugin-github-actions'; import { - GroupProfileCard, - MembersListCard, - OwnershipCard, - UserProfileCard, + EntityUserProfileCard, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, } from '@backstage/plugin-org'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; - -const CICDSwitcher = ({ entity }: { entity: Entity }) => { - // This component is just an example of how you can implement your company's logic in entity page. +const cicdContent = ( + // This is an example of how you can implement your company's logic in entity page. // You can for example enforce that all components of type 'service' should use GitHubActions - switch (true) { - case isGitHubActionsAvailable(entity): - return ; - default: - return ( - - Read more - - } - /> - ); - } -}; + + + + -const OverviewContent = ({ entity }: { entity: Entity }) => ( + + + Read more + + } + /> + + +); + +const overviewContent = ( - + ); -const ComponentApisContent = ({ entity }: { entity: Entity }) => ( - - - - - - - - -); +const serviceEntityPage = ( + + + {overviewContent} + -const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - -); + + {cicdContent} + -const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - -); - -const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - -); - -export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { - switch (entity?.spec?.type) { - case 'service': - return ; - case 'website': - return ; - default: - return ; - } -}; - -const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( - - - - - - - + + + + + + + + - - + + + + + + +); + +const websiteEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + +); + +const defaultEntityPage = ( + + + {overviewContent} + + + + + + +); + +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); + +const apiPage = ( + + + + + + + + + + + + + + - - + + + + + + + + + + ); -const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( - - - - - +const userPage = ( + + + + + + + + + + + + ); -const ApiEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - +const groupPage = ( + + + + + + + + + + + + + + + + + + + ); -const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( - - - - - - - - +const systemPage = ( + + + + + + + + + + + + + + + ); -const UserEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - +const domainPage = ( + + + + + + + + + + + + ); -const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => ( - - - - - - - - - - - +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + ); - -const GroupEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => ( - - - - - - - - - - - -); - -const SystemEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => ( - - - - - - - - -); - -const DomainEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -export const EntityPage = () => { - const { entity } = useEntity(); - - switch (entity?.kind?.toLocaleLowerCase('en-US')) { - case 'component': - return ; - case 'api': - return ; - case 'group': - return ; - case 'user': - return ; - case 'system': - return ; - case 'domain': - return ; - case 'location': - case 'resource': - case 'template': - default: - return ; - } -}; diff --git a/yarn.lock b/yarn.lock index e335f31c18..94a60e108f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1834,194 +1834,6 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@backstage/catalog-model@^0.2.0": - version "0.7.5" - dependencies: - "@backstage/config" "^0.1.3" - "@types/json-schema" "^7.0.5" - "@types/yup" "^0.29.8" - ajv "^7.0.3" - json-schema "^0.2.5" - lodash "^4.17.15" - uuid "^8.0.0" - yup "^0.29.3" - -"@backstage/core@^0.3.0": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.6.0": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.6.1": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/plugin-catalog@^0.2.1": - version "0.5.2" - dependencies: - "@backstage/catalog-client" "^0.3.9" - "@backstage/catalog-model" "^0.7.5" - "@backstage/core" "^0.7.3" - "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.1" - "@backstage/integration-react" "^0.1.1" - "@backstage/plugin-catalog-react" "^0.1.4" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - -"@backstage/plugin-catalog@^0.3.1": - version "0.5.2" - dependencies: - "@backstage/catalog-client" "^0.3.9" - "@backstage/catalog-model" "^0.7.5" - "@backstage/core" "^0.7.3" - "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.1" - "@backstage/integration-react" "^0.1.1" - "@backstage/plugin-catalog-react" "^0.1.4" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -4329,6 +4141,11 @@ resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-5.3.2.tgz#b8ac43c5c3d00aef61a34cf744e315110c78deb4" integrity sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA== +"@octokit/openapi-types@^6.0.0": + version "6.0.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d" + integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ== + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -4354,6 +4171,14 @@ "@octokit/types" "^6.12.2" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98" + integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg== + dependencies: + "@octokit/types" "^6.13.0" + deprecation "^2.3.1" + "@octokit/request-error@^2.0.0": version "2.0.2" resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" @@ -4377,7 +4202,7 @@ once "^1.4.0" universal-user-agent "^6.0.0" -"@octokit/rest@^18.0.0", "@octokit/rest@^18.0.12", "@octokit/rest@^18.1.0": +"@octokit/rest@^18.0.12", "@octokit/rest@^18.1.0": version "18.3.5" resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.3.5.tgz#a89903d46e0b4273bd3234674ec2777a651d68ab" integrity sha512-ZPeRms3WhWxQBEvoIh0zzf8xdU2FX0Capa7+lTca8YHmRsO3QNJzf1H3PcuKKsfgp91/xVDRtX91sTe1kexlbw== @@ -4387,6 +4212,16 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "4.13.5" +"@octokit/rest@^18.1.1": + version "18.5.2" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b" + integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw== + dependencies: + "@octokit/core" "^3.2.3" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.0.0" + "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": version "5.5.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" @@ -4394,13 +4229,20 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.12.2", "@octokit/types@^6.8.2": +"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.12.2": version "6.12.2" resolved "https://registry.npmjs.org/@octokit/types/-/types-6.12.2.tgz#5b44add079a478b8eb27d78cf384cc47e4411362" integrity sha512-kCkiN8scbCmSq+gwdJV0iLgHc0O/GTPY1/cffo9kECu1MvatLPh9E+qFhfRIktKfHEA6ZYvv6S1B4Wnv3bi3pA== dependencies: "@octokit/openapi-types" "^5.3.2" +"@octokit/types@^6.13.0": + version "6.13.0" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" + integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== + dependencies: + "@octokit/openapi-types" "^6.0.0" + "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" @@ -4538,55 +4380,57 @@ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.4.1.tgz#b0dedff8877b114147e298966ca3faba895a7a62" integrity sha512-pZaWL5Dw+km8S0hFLIK1lRHaeNtheMxTF2mZrWhf6HlGWCTGkQJnXta2UgshJN/nKtZPgO1L4FKz42Eun9nnhg== -"@roadiehq/backstage-plugin-buildkite@^0.1.3": - version "0.1.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.1.3.tgz#5a116bf677dfad22088212dde398cf3e9b48d6e4" - integrity sha512-q+cnAvZmjLu0DcqRKJZJhWTVcKaA9j7cM44tx6JcshEyb3UgzrlECLvx4ethnYyrmhM1/FSpMLa+t3N5A1Xz4g== +"@roadiehq/backstage-plugin-buildkite@^0.3.0": + version "0.3.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.3.0.tgz#60db5e712b88bc9cf3476737a1835f4fc26ae616" + integrity sha512-YeATwsR9G93/l3zZmzDN2k0vZzo0Tw0G0pp16B6p1Tz4lBUxiQ40D0anSsVrzep4NR3rxbzqzh7OGZ8Pihfcpw== dependencies: - "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.3.0" - "@backstage/plugin-catalog" "^0.2.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog" "^0.5.1" + "@backstage/theme" "^0.2.5" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" + history "^5.0.0" moment "^2.27.0" react "^16.13.1" react-dom "^16.13.1" react-lazylog "^4.5.2" react-router "6.0.0-beta.0" react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" + react-use "^15.3.6" -"@roadiehq/backstage-plugin-github-insights@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.3.2.tgz#2db83e172ae34738677bd47c3b754c31bb2b5987" - integrity sha512-eIhqQS0jTRcdTCu+GxrRCH1tq+kDrTb6mMJpD/6sGnE9QXgj5nKHIm6KHmFYEfIiukQrGxXY4lABNWstvi7hgA== +"@roadiehq/backstage-plugin-github-insights@^0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.3.3.tgz#f2b4aeff76930d4ea4f5ca80bf1ad6f407dd6b74" + integrity sha512-sRczrC4JRli8Po1FXL5M6qiCJiUUWpin+kH7XSlo5JJoK5+UdNP05l1qFkMkeCyPdkJp/iVvXArkHPyWnm361g== dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.0" - "@backstage/theme" "^0.2.3" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/theme" "^0.2.5" "@date-io/core" "2.10.7" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.57" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^6.8.2" + "@octokit/rest" "^18.1.1" + "@octokit/types" "^6.13.0" history "^5.0.0" react "^16.13.1" react-dom "^16.13.1" react-router "^6.0.0-beta.0" - react-use "^17.1.0" + react-use "^17.2.1" -"@roadiehq/backstage-plugin-github-pull-requests@^0.7.6": - version "0.7.8" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.7.8.tgz#14a8e512b83de6cb5304b2a8e62c447b2bd6e311" - integrity sha512-sgBzIYC4GfZGbFpRixQBinf62laVqefRLHPLaoF2LuTMStXGcjUIGlqySmWNOXvpmGjCv3QIxh1NaxZBp6Z1Vw== +"@roadiehq/backstage-plugin-github-pull-requests@^0.7.9": + version "0.7.9" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.7.9.tgz#61ea6bc6a50defe0223ef8dff124379673f54c55" + integrity sha512-qUyCShhtV4x3QaZewiw13lQBmWLTjybOTsy7x+j4iY6IXkOhol5E2uWVTWifJLxDXg76FsXKWw9eRIqBSOrTRQ== dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" - "@octokit/rest" "^18.0.0" + "@octokit/rest" "^18.1.1" "@octokit/types" "^5.0.1" "@types/node-fetch" "^2.5.7" history "^5.0.0" @@ -4597,20 +4441,20 @@ react-router "6.0.0-beta.0" react-use "^15.3.6" -"@roadiehq/backstage-plugin-travis-ci@^0.4.5": - version "0.4.5" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.4.5.tgz#c80eb972951badd110173f5521aae65b8a5cfc53" - integrity sha512-+y5kFtVaPjXgf6naGVU2E3C33nvoXuaBbfYEmCeByg2t0b5jHMHzsTv2tV5/8zeAcus/5+yIkRCq+F1Tcbwutw== +"@roadiehq/backstage-plugin-travis-ci@^0.4.7": + version "0.4.7" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.4.7.tgz#ce37ff8b3124975999f0cfe01170b8b5aee55fb3" + integrity sha512-hDyOND6/gpCy0GpHLwTDXwNfVWVIBpjh5Ipac0bBa7Yyyj0MKI4UVmLu1K99fRZk1aI6n6TIyLYAJTabLmuYuQ== dependencies: - "@backstage/catalog-model" "^0.7.0" - "@backstage/core" "^0.6.0" - "@backstage/plugin-catalog" "^0.3.1" - "@backstage/theme" "^0.2.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog" "^0.5.1" + "@backstage/theme" "^0.2.5" "@material-ui/core" "^4.9.1" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" cross-fetch "^3.0.6" - date-fns "^2.16.1" + date-fns "^2.18.0" history "^5.0.0" moment "^2.29.1" react "^16.13.1" @@ -11435,6 +11279,11 @@ date-fns@^2.0.0-alpha.27, date-fns@^2.16.1: resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.18.0.tgz#08e50aee300ad0d2c5e054e3f0d10d8f9cdfe09e" integrity sha512-NYyAg4wRmGVU4miKq5ivRACOODdZRY3q5WLmOJSq8djyzftYphU7dTHLcEtLqEvfqMKQ0jVv91P4BAwIjsXIcw== +date-fns@^2.18.0: + version "2.19.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" + integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -22525,7 +22374,7 @@ react-use@^15.3.3, react-use@^15.3.6: ts-easing "^0.2.0" tslib "^2.0.0" -react-use@^17.1.0: +react-use@^17.2.1: version "17.2.1" resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.1.tgz#c81e12544115ed049c7deba1e3bb3d977dfee9b8" integrity sha512-9r51/at7/Nr/nEP4CsHz+pl800EAqhIY9R6O68m68kaWc8slDAfx1UrIedQqpsb4ImddFYb+6hF1i5Vj4u4Cnw==