diff --git a/.changeset/blue-dingos-repeat.md b/.changeset/blue-dingos-repeat.md new file mode 100644 index 0000000000..111a94a00b --- /dev/null +++ b/.changeset/blue-dingos-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Require that audience URLs for Okta authentication start with https diff --git a/.changeset/brave-spoons-grow.md b/.changeset/brave-spoons-grow.md new file mode 100644 index 0000000000..d1bd166510 --- /dev/null +++ b/.changeset/brave-spoons-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Switched to relying on the built-in support for async loading in `react-syntax-highlighter`. This should provide further improvements to async rendering and lazy loading, and avoid test flakiness that was happening because of the significant number or resources being loaded in lazily all at once. diff --git a/.changeset/brave-waves-explain.md b/.changeset/brave-waves-explain.md new file mode 100644 index 0000000000..5e76f192e0 --- /dev/null +++ b/.changeset/brave-waves-explain.md @@ -0,0 +1,15 @@ +--- +'@backstage/create-app': patch +--- + +Removed the included `jest` configuration from the root `package.json` as the `transformModules` option no longer exists. + +To apply this change to an existing app, make the follow change to the root `package.json`: + +```diff +- "jest": { +- "transformModules": [ +- "@asyncapi/react-component" +- ] +- } +``` diff --git a/.changeset/breezy-dolphins-lay.md b/.changeset/breezy-dolphins-lay.md deleted file mode 100644 index 0197979531..0000000000 --- a/.changeset/breezy-dolphins-lay.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -AWS-ALB: update provider to the latest changes described [here](https://backstage.io/docs/auth/identity-resolver). - -This removes the `ExperimentalIdentityResolver` type in favor of `SignInResolver` and `AuthHandler`. -The AWS ALB provider can now be configured in the same way as the Google provider in the example. diff --git a/.changeset/brown-paws-marry.md b/.changeset/brown-paws-marry.md new file mode 100644 index 0000000000..c771d5e1f4 --- /dev/null +++ b/.changeset/brown-paws-marry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Fix a bug preventing an access token to be refreshed a second time with the GitHub provider. diff --git a/.changeset/chilled-tools-worry.md b/.changeset/chilled-tools-worry.md new file mode 100644 index 0000000000..58f89ef503 --- /dev/null +++ b/.changeset/chilled-tools-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Configuration schema errors are now filtered using the provided visibility option. This means that schema errors due to missing backend configuration will no longer break frontend builds. diff --git a/.changeset/dirty-balloons-develop.md b/.changeset/dirty-balloons-develop.md deleted file mode 100644 index 6dd9005cca..0000000000 --- a/.changeset/dirty-balloons-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Bump `swagger-ui-react` to `^4.0.0-rc.3`. diff --git a/.changeset/eighty-forks-fry.md b/.changeset/eighty-forks-fry.md new file mode 100644 index 0000000000..4a25dd128a --- /dev/null +++ b/.changeset/eighty-forks-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Use docs/README.md or README.md as fallback if docs/index.md is missing diff --git a/.changeset/few-fishes-heal.md b/.changeset/few-fishes-heal.md new file mode 100644 index 0000000000..9fc9e0d17f --- /dev/null +++ b/.changeset/few-fishes-heal.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add `` as an alternative to `` that +shows a grid of card instead of table. + +Extend `` to display the entity title of the entity instead of the +name if available. diff --git a/.changeset/four-days-sneeze.md b/.changeset/four-days-sneeze.md new file mode 100644 index 0000000000..93517d3a73 --- /dev/null +++ b/.changeset/four-days-sneeze.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Display entity title (if defined) in titles of TechDocs search results diff --git a/.changeset/funny-dolls-draw.md b/.changeset/funny-dolls-draw.md deleted file mode 100644 index f194b30750..0000000000 --- a/.changeset/funny-dolls-draw.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -The scaffolder plugin has just released the beta 3 version of software templates, which replaces the handlebars templating syntax. As part of this change, the template entity schema is no longer included in the core catalog-model as with previous versions. The decoupling of the template entities version will allow us to more easily make updates in the future. - -In order to use the new beta 3 templates, the following changes are **required** for any existing installation, inside `packages/backend/src/plugins/catalog.ts`: - -```diff -+import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; - -... - - const builder = await CatalogBuilder.create(env); -+ builder.addProcessor(new ScaffolderEntitiesProcessor()); - const { processingEngine, router } = await builder.build(); -``` - -If you're interested in learning more about creating custom kinds, please check out the [extending the model](https://backstage.io/docs/features/software-catalog/extending-the-model) documentation. diff --git a/.changeset/giant-phones-exercise.md b/.changeset/giant-phones-exercise.md new file mode 100644 index 0000000000..b190c2e7f0 --- /dev/null +++ b/.changeset/giant-phones-exercise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-fossa': patch +--- + +Add `entitiesFilter` option to `FossaPage` to filter entities displayed in the table. diff --git a/.changeset/happy-hotels-explain.md b/.changeset/happy-hotels-explain.md deleted file mode 100644 index ede18c3187..0000000000 --- a/.changeset/happy-hotels-explain.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/core-app-api': patch -'@backstage/core-components': patch -'@backstage/theme': patch ---- - -Internal refactor to avoid importing all of `@material-ui/core`. diff --git a/.changeset/hip-pens-love.md b/.changeset/hip-pens-love.md new file mode 100644 index 0000000000..d1549789bf --- /dev/null +++ b/.changeset/hip-pens-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Take CatalogParser in account when processing file locations. diff --git a/.changeset/hip-suns-fix.md b/.changeset/hip-suns-fix.md deleted file mode 100644 index 4555c7b179..0000000000 --- a/.changeset/hip-suns-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -update the null check to use the optional chaining operator in case of non-null assertion operator is not working in function extractInitials(values: string) diff --git a/.changeset/honest-drinks-eat.md b/.changeset/honest-drinks-eat.md deleted file mode 100644 index b6cb168e00..0000000000 --- a/.changeset/honest-drinks-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-graphiql': patch ---- - -Add experimental `experimentalInstallationRecipe` to `package.json`. diff --git a/.changeset/hot-wolves-share.md b/.changeset/hot-wolves-share.md new file mode 100644 index 0000000000..a6294b8c9c --- /dev/null +++ b/.changeset/hot-wolves-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Minor rearrangement of `Stitcher` to clarify the scope of one stitch round diff --git a/.changeset/hungry-bugs-drop.md b/.changeset/hungry-bugs-drop.md deleted file mode 100644 index d13e21d446..0000000000 --- a/.changeset/hungry-bugs-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Resolve a warning in ` + + + ); +}; diff --git a/plugins/bazaar/src/components/DeleteProjectDialog/index.ts b/plugins/bazaar/src/components/DeleteProjectDialog/index.ts new file mode 100644 index 0000000000..fdd488aeb9 --- /dev/null +++ b/plugins/bazaar/src/components/DeleteProjectDialog/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { DeleteProjectDialog } from './DeleteProjectDialog'; diff --git a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx new file mode 100644 index 0000000000..172bdf548d --- /dev/null +++ b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { ProjectDialog } from '../ProjectDialog'; +import { BazaarProject, FormValues } from '../../types'; +import { bazaarApiRef } from '../../api'; + +type Props = { + entity: Entity; + bazaarProject: BazaarProject; + fetchBazaarProject: () => Promise; + open: boolean; + handleClose: () => void; + isAddForm: boolean; +}; + +export const EditProjectDialog = ({ + entity, + bazaarProject, + fetchBazaarProject, + open, + handleClose, +}: Props) => { + const [defaultValues, setDefaultValues] = useState({ + announcement: bazaarProject.announcement, + community: bazaarProject.community, + status: bazaarProject.status, + }); + + const bazaarApi = useApi(bazaarApiRef); + + useEffect(() => { + setDefaultValues({ + announcement: bazaarProject.announcement, + community: bazaarProject.community, + status: bazaarProject.status, + }); + }, [bazaarProject]); + + const handleSave: any = async (getValues: any, _: any) => { + const formValues = getValues(); + + const updateResponse = await bazaarApi.updateMetadata({ + name: entity.metadata.name, + entityRef: stringifyEntityRef(entity), + announcement: formValues.announcement, + status: formValues.status, + community: formValues.community, + membersCount: bazaarProject.membersCount, + }); + + if (updateResponse.status === 'ok') fetchBazaarProject(); + handleClose(); + }; + + return ( + + ); +}; diff --git a/plugins/bazaar/src/components/EditProjectDialog/index.ts b/plugins/bazaar/src/components/EditProjectDialog/index.ts new file mode 100644 index 0000000000..4c48c375ce --- /dev/null +++ b/plugins/bazaar/src/components/EditProjectDialog/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EditProjectDialog } from './EditProjectDialog'; diff --git a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx new file mode 100644 index 0000000000..6bf844d592 --- /dev/null +++ b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx @@ -0,0 +1,324 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { + Grid, + makeStyles, + Card, + CardContent, + CardHeader, + Typography, + Divider, + IconButton, + Popover, + MenuList, + MenuItem, + ListItemText, + Link, +} from '@material-ui/core'; +import { + Progress, + HeaderIconLinkRow, + IconLinkVerticalProps, + Avatar, +} from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { AboutField } from '@backstage/plugin-catalog'; +import { StatusTag } from '../StatusTag'; +import EditIcon from '@material-ui/icons/Edit'; +import ChatIcon from '@material-ui/icons/Chat'; +import PersonAddIcon from '@material-ui/icons/PersonAdd'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { EditProjectDialog } from '../EditProjectDialog'; +import { DeleteProjectDialog } from '../DeleteProjectDialog'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import { useApi, identityApiRef } from '@backstage/core-plugin-api'; +import { Member, BazaarProject } from '../../types'; +import { bazaarApiRef } from '../../api'; +import { Alert } from '@material-ui/lab'; +import { useAsyncFn } from 'react-use'; + +const useStyles = makeStyles({ + description: { + wordBreak: 'break-word', + }, + icon: { + marginRight: '1.75rem', + }, + link: { + color: '#9cc9ff', + '&:hover': { + textDecoration: 'underline', + }, + }, + memberLink: { + display: 'block', + marginBottom: '0.3rem', + }, +}); + +const sortMembers = (m1: Member, m2: Member) => { + return new Date(m2.joinDate!).getTime() - new Date(m1.joinDate!).getTime(); +}; + +export const EntityBazaarInfoCard = () => { + const { entity } = useEntity(); + const classes = useStyles(); + const bazaarApi = useApi(bazaarApiRef); + const identity = useApi(identityApiRef); + const [anchorEl, setAnchorEl] = useState(); + const [open, setOpen] = useState(false); + const [popoverOpen, setPopoverOpen] = useState(false); + const [openDelete, setOpenDelete] = useState(false); + const [isMember, setIsMember] = useState(false); + const [isBazaar, setIsBazaar] = useState(false); + const [members, fetchMembers] = useAsyncFn(async () => { + const response = await bazaarApi.getMembers(entity); + + const dbMembers = response.data.map((obj: any) => { + const member: Member = { + userId: obj.user_id, + entityRef: obj.entity_ref, + joinDate: obj.join_date, + picture: obj.picture, + }; + + return member; + }); + + dbMembers.sort(sortMembers); + + return dbMembers; + }); + + const [bazaarProject, fetchBazaarProject] = useAsyncFn(async () => { + const response = await bazaarApi.getMetadata(entity); + + if (response) { + const metadata = await response.json().then((resp: any) => resp.data[0]); + + if (metadata) { + return { + entityRef: metadata.entity_ref, + name: metadata.name, + community: metadata.community, + announcement: metadata.announcement, + status: metadata.status, + updatedAt: metadata.updated_at, + membersCount: metadata.members_count, + } as BazaarProject; + } + } + return null; + }); + + useEffect(() => { + fetchMembers(); + fetchBazaarProject(); + }, [fetchMembers, fetchBazaarProject]); + + useEffect(() => { + const isBazaarMember = + members?.value + ?.map((member: Member) => member.userId) + .indexOf(identity.getUserId()) >= 0; + const isBazaarProject = bazaarProject.value !== null; + + setIsMember(isBazaarMember); + setIsBazaar(isBazaarProject); + }, [bazaarProject, members, identity]); + + const onOpen = (event: React.SyntheticEvent) => { + setAnchorEl(event.currentTarget); + setPopoverOpen(true); + }; + + const closeEdit = () => { + setOpen(false); + }; + + const closeDelete = () => { + setOpenDelete(false); + }; + + const popoverCloseHandler = () => { + setPopoverOpen(false); + }; + + const handleMembersClick = async () => { + if (!isMember) { + await bazaarApi.addMember(entity); + } else { + await bazaarApi.deleteMember(entity); + } + + fetchMembers(); + fetchBazaarProject(); + }; + + const links: IconLinkVerticalProps[] = [ + { + label: isMember ? 'Leave' : 'Join', + icon: isMember ? : , + href: '', + onClick: async () => { + handleMembersClick(); + }, + }, + { + label: 'Community', + icon: , + href: bazaarProject?.value?.community, + disabled: !bazaarProject?.value?.community || !isMember, + }, + ]; + + if (!isBazaar) { + return null; + } else if (bazaarProject.loading || members.loading) { + return ; + } else if (bazaarProject.error) { + return {bazaarProject?.error?.message}; + } else if (members.error) { + return {members?.error?.message}; + } + return ( + + {bazaarProject?.value && ( + + )} + + {bazaarProject?.value && ( + + )} + + + + } + subheader={} + /> + + + + + { + setOpen(true); + setPopoverOpen(false); + }} + > + + + + + { + setOpenDelete(true); + setPopoverOpen(false); + }} + > + + + + + + + + + {bazaarProject?.value?.announcement + ? bazaarProject?.value?.announcement + .split('\n') + .map((str: string, i: number) => ( + + {str} + + )) + : 'No announcement'} + + + + + + + + + + + {' '} + + {members?.value?.length ? ( + members.value.slice(0, 3).map((member: Member) => { + return ( +
+ + + {member?.userId} + +
+ ); + }) + ) : ( +
+ )} + + + + + + ); +}; diff --git a/plugins/bazaar/src/components/EntityBazaarInfoCard/index.ts b/plugins/bazaar/src/components/EntityBazaarInfoCard/index.ts new file mode 100644 index 0000000000..89eee92f11 --- /dev/null +++ b/plugins/bazaar/src/components/EntityBazaarInfoCard/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityBazaarInfoCard } from './EntityBazaarInfoCard'; diff --git a/plugins/bazaar/src/components/HomePage/HomePage.tsx b/plugins/bazaar/src/components/HomePage/HomePage.tsx new file mode 100644 index 0000000000..32a16291fc --- /dev/null +++ b/plugins/bazaar/src/components/HomePage/HomePage.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Header, RoutedTabs } from '@backstage/core-components'; +import { Route } from 'react-router-dom'; +import { SortView } from '../SortView'; +import { About } from '../About'; + +export const HomePage = () => { + const tabContent = [ + { + path: '/', + title: 'Home', + children: } />, + }, + { + path: '/about', + title: 'About', + children: } />, + }, + ]; + + return ( +
+
+ +
+ ); +}; diff --git a/plugins/bazaar/src/components/HomePage/index.ts b/plugins/bazaar/src/components/HomePage/index.ts new file mode 100644 index 0000000000..41d7bc5d03 --- /dev/null +++ b/plugins/bazaar/src/components/HomePage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { HomePage } from './HomePage'; diff --git a/plugins/bazaar/src/components/InputField/InputField.tsx b/plugins/bazaar/src/components/InputField/InputField.tsx new file mode 100644 index 0000000000..4d284ff82c --- /dev/null +++ b/plugins/bazaar/src/components/InputField/InputField.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Controller, Control, FieldError } from 'react-hook-form'; +import { TextField } from '@material-ui/core'; +import { FormValues } from '../../types'; + +type Props = { + inputType: 'announcement' | 'community'; + error?: FieldError | undefined; + control: Control; + helperText?: string; + placeholder?: string; + rules?: Object; +}; + +export const InputField = ({ + inputType, + error, + control, + helperText, + placeholder, + rules, +}: Props) => { + const label = + inputType.charAt(0).toLocaleUpperCase('en-US') + inputType.slice(1); + + return ( + ( + + )} + /> + ); +}; diff --git a/plugins/bazaar/src/components/InputField/index.ts b/plugins/bazaar/src/components/InputField/index.ts new file mode 100644 index 0000000000..d8c82eecbd --- /dev/null +++ b/plugins/bazaar/src/components/InputField/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { InputField } from './InputField'; diff --git a/plugins/bazaar/src/components/InputSelector/InputSelector.tsx b/plugins/bazaar/src/components/InputSelector/InputSelector.tsx new file mode 100644 index 0000000000..a41cedfcf3 --- /dev/null +++ b/plugins/bazaar/src/components/InputSelector/InputSelector.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import InputLabel from '@material-ui/core/InputLabel'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import { Controller, Control, FieldError } from 'react-hook-form'; +import { FormValues } from '../../types'; + +type Props = { + options: string[]; + control: Control; + name: 'announcement' | 'status'; + error?: FieldError | undefined; +}; + +export const InputSelector = ({ name, options, control, error }: Props) => { + const label = name.charAt(0).toLocaleUpperCase('en-US') + name.slice(1); + + return ( + ( + + + {label} + + + + )} + /> + ); +}; diff --git a/plugins/bazaar/src/components/InputSelector/index.ts b/plugins/bazaar/src/components/InputSelector/index.ts new file mode 100644 index 0000000000..a4327991b0 --- /dev/null +++ b/plugins/bazaar/src/components/InputSelector/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { InputSelector } from './InputSelector'; diff --git a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx new file mode 100644 index 0000000000..b3c8b3e2e7 --- /dev/null +++ b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { ItemCardHeader } from '@backstage/core-components'; +import { + Card, + CardActionArea, + CardContent, + makeStyles, + Typography, +} from '@material-ui/core'; +import { StatusTag } from '../StatusTag/StatusTag'; +import { Link as RouterLink } from 'react-router-dom'; +import { catalogRouteRef } from '@backstage/plugin-catalog-react'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { BazaarProject } from '../../types'; +import { parseEntityName } from '@backstage/catalog-model'; +import { DateTime } from 'luxon'; + +const useStyles = makeStyles({ + statusTag: { + display: 'inline-block', + whiteSpace: 'nowrap', + marginBottom: '0.5rem', + }, + announcement: { + display: '-webkit-box', + WebkitLineClamp: 5, + WebkitBoxOrient: 'vertical', + marginBottom: '0.8rem', + overflow: 'hidden', + }, + memberCount: { + float: 'right', + }, +}); + +type Props = { + bazaarProject: BazaarProject; +}; + +export const ProjectCard = ({ bazaarProject }: Props) => { + const classes = useStyles(); + const { entityRef, name, status, updatedAt, announcement, membersCount } = + bazaarProject; + const catalogLink = useRouteRef(catalogRouteRef); + const { namespace, kind } = parseEntityName(entityRef); + + return ( + + + + + + + {membersCount === 1 + ? `${membersCount} member` + : `${membersCount} members`} + +
+ + {announcement} + +
+
+
+
+ ); +}; diff --git a/plugins/bazaar/src/components/ProjectCard/index.ts b/plugins/bazaar/src/components/ProjectCard/index.ts new file mode 100644 index 0000000000..00e62bebba --- /dev/null +++ b/plugins/bazaar/src/components/ProjectCard/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ProjectCard } from './ProjectCard'; diff --git a/plugins/bazaar/src/components/ProjectDialog/ProjectDialog.tsx b/plugins/bazaar/src/components/ProjectDialog/ProjectDialog.tsx new file mode 100644 index 0000000000..cb76984f44 --- /dev/null +++ b/plugins/bazaar/src/components/ProjectDialog/ProjectDialog.tsx @@ -0,0 +1,186 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + createStyles, + Theme, + withStyles, + WithStyles, +} from '@material-ui/core/styles'; +import MuiDialogTitle from '@material-ui/core/DialogTitle'; +import MuiDialogContent from '@material-ui/core/DialogContent'; +import MuiDialogActions from '@material-ui/core/DialogActions'; +import CloseIcon from '@material-ui/icons/Close'; +import { Button, Dialog, Typography, IconButton } from '@material-ui/core'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { InputField } from '../InputField/InputField'; +import { InputSelector } from '../InputSelector/InputSelector'; +import { FormValues } from '../../types'; + +const styles = (theme: Theme) => + createStyles({ + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + }); + +/* + DialogTitleProps, DialogTitle, DialogContent and DialogActions + are copied from the git-release plugin +*/ +export interface DialogTitleProps extends WithStyles { + id: string; + children: React.ReactNode; + onClose: () => void; +} + +const DialogTitle = withStyles(styles)((props: DialogTitleProps) => { + const { children, classes, onClose, ...other } = props; + return ( + + {children} + {onClose ? ( + + + + ) : null} + + ); +}); + +const DialogContent = withStyles((theme: Theme) => ({ + root: { + padding: theme.spacing(2), + }, +}))(MuiDialogContent); + +const DialogActions = withStyles((theme: Theme) => ({ + root: { + margin: 0, + padding: theme.spacing(1), + }, +}))(MuiDialogActions); + +type Props = { + handleSave: (getValues: any, reset: any) => SubmitHandler; + isAddForm: boolean; + title: string; + defaultValues: FormValues; + open: boolean; + projectSelector?: JSX.Element; + handleClose: () => void; +}; + +export const ProjectDialog = ({ + handleSave, + isAddForm, + title, + defaultValues, + open, + projectSelector, + handleClose, +}: Props) => { + const { + handleSubmit, + reset, + control, + getValues, + formState: { errors }, + } = useForm({ + mode: 'onChange', + defaultValues: defaultValues, + }); + + const handleCloseAndClear = () => { + handleClose(); + reset(defaultValues); + }; + + const handleSaveProject = () => { + handleSave(getValues, reset); + }; + + return ( +
+ + + {title} + + + + {isAddForm && projectSelector} + + + + + + + + + + + + +
+ ); +}; diff --git a/plugins/bazaar/src/components/ProjectDialog/index.ts b/plugins/bazaar/src/components/ProjectDialog/index.ts new file mode 100644 index 0000000000..bd85b6576b --- /dev/null +++ b/plugins/bazaar/src/components/ProjectDialog/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ProjectDialog } from './ProjectDialog'; diff --git a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx new file mode 100644 index 0000000000..0b01b86088 --- /dev/null +++ b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx @@ -0,0 +1,109 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import { Content } from '@backstage/core-components'; +import { ProjectCard } from '../ProjectCard/ProjectCard'; +import { makeStyles, Grid } from '@material-ui/core'; +import Pagination from '@material-ui/lab/Pagination'; +import { BazaarProject } from '../../types'; + +type Props = { + bazaarProjects: BazaarProject[]; + sortingMethod: (arg0: BazaarProject, arg1: BazaarProject) => number; +}; + +const useStyles = makeStyles({ + content: { + width: '100%', + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + }, + item: { + minWidth: '20.5rem', + maxWidth: '20.5rem', + width: '20%', + }, +}); + +export const ProjectPreview = ({ bazaarProjects, sortingMethod }: Props) => { + const classes = useStyles(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; + const pageCount = Math.ceil(bazaarProjects.length / pageSize); + + const handlePageClick = (_: any, pageIndex: number) => { + setCurrentPage(pageIndex); + }; + + if (!bazaarProjects.length) { + return ( +
+ Please add projects to the Bazaar. +
+ ); + } + + bazaarProjects.sort(sortingMethod); + + return ( + + + {bazaarProjects + .slice(pageSize * (currentPage - 1), pageSize * currentPage) + .map((bazaarProject: BazaarProject) => { + const entityRef = bazaarProject.entityRef; + + return ( + + + + ); + })} + + + + ); +}; diff --git a/plugins/bazaar/src/components/ProjectPreview/index.ts b/plugins/bazaar/src/components/ProjectPreview/index.ts new file mode 100644 index 0000000000..74a954c595 --- /dev/null +++ b/plugins/bazaar/src/components/ProjectPreview/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ProjectPreview } from './ProjectPreview'; diff --git a/plugins/bazaar/src/components/ProjectSelector/ProjectSelector.tsx b/plugins/bazaar/src/components/ProjectSelector/ProjectSelector.tsx new file mode 100644 index 0000000000..1c2718d403 --- /dev/null +++ b/plugins/bazaar/src/components/ProjectSelector/ProjectSelector.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { Autocomplete } from '@material-ui/lab'; +import { TextField } from '@material-ui/core'; + +type Props = { + entities: Entity[]; + value: string; + onChange: (entity: Entity) => void; + isFormInvalid: boolean; +}; + +export const ProjectSelector = ({ + entities, + value, + onChange, + isFormInvalid, +}: Props) => { + return ( + option?.metadata?.name} + renderOption={option => {option?.metadata?.name}} + renderInput={params => ( + + )} + onChange={(_, data) => onChange(data!)} + /> + ); +}; diff --git a/plugins/bazaar/src/components/ProjectSelector/index.ts b/plugins/bazaar/src/components/ProjectSelector/index.ts new file mode 100644 index 0000000000..e81b825dbf --- /dev/null +++ b/plugins/bazaar/src/components/ProjectSelector/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ProjectSelector } from './ProjectSelector'; diff --git a/plugins/bazaar/src/components/SortView/SortView.tsx b/plugins/bazaar/src/components/SortView/SortView.tsx new file mode 100644 index 0000000000..99d3f84ac3 --- /dev/null +++ b/plugins/bazaar/src/components/SortView/SortView.tsx @@ -0,0 +1,183 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useEffect, useState } from 'react'; +import { + Content, + ContentHeader, + SupportButton, + Progress, +} from '@backstage/core-components'; +import { AddProjectDialog } from '../AddProjectDialog'; +import { AlertBanner } from '../AlertBanner'; +import { ProjectPreview } from '../ProjectPreview/ProjectPreview'; +import { Button, makeStyles, Link } from '@material-ui/core'; +import { useAsyncFn } from 'react-use'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { BazaarProject } from '../../types'; +import { bazaarApiRef } from '../../api'; +import { Alert } from '@material-ui/lab'; + +const useStyles = makeStyles({ + container: { + marginTop: '2rem', + }, +}); + +const filterCatalogEntities = ( + bazaarProjects: BazaarProject[], + catalogEntities: Entity[], +) => { + const bazaarProjectRefs = bazaarProjects.map( + (project: BazaarProject) => project.entityRef, + ); + + const filtered = catalogEntities.filter((entity: Entity) => { + return !bazaarProjectRefs?.includes(stringifyEntityRef(entity)); + }); + + return filtered; +}; + +export const SortView = () => { + const classes = useStyles(); + const [openAdd, setOpenAdd] = useState(false); + const [openNoProjects, setOpenNoProjects] = useState(false); + const bazaarApi = useApi(bazaarApiRef); + const catalogApi = useApi(catalogApiRef); + const [filteredCatalogEntites, setFilteredCatalogEntities] = + useState(); + + const compareProjectsByDate = ( + a: BazaarProject, + b: BazaarProject, + ): number => { + const dateA = new Date(a.updatedAt!).getTime(); + const dateB = new Date(b.updatedAt!).getTime(); + return dateB - dateA; + }; + + const handleCloseNoProjects = () => { + setOpenNoProjects(false); + }; + + const [catalogEntities, fetchCatalogEntities] = useAsyncFn(async () => { + const entities = await catalogApi.getEntities({ + filter: { + kind: ['Component', 'Resource'], + }, + fields: ['kind', 'metadata.name', 'metadata.namespace'], + }); + + return entities.items; + }); + + const [bazaarProjects, fetchBazaarProjects] = useAsyncFn(async () => { + const response = await bazaarApi.getEntities(); + const dbProjects: BazaarProject[] = []; + + response.data.forEach((project: any) => { + dbProjects.push({ + entityRef: project.entity_ref, + name: project.name, + status: project.status, + announcement: project.announcement, + community: project.community, + updatedAt: project.updated_at, + membersCount: project.members_count, + }); + }); + + return dbProjects; + }); + + useEffect(() => { + fetchCatalogEntities(); + fetchBazaarProjects(); + }, [fetchBazaarProjects, fetchCatalogEntities]); + + useEffect(() => { + const filteredCatalogEntities = filterCatalogEntities( + bazaarProjects.value || [], + catalogEntities.value || [], + ); + + if (filteredCatalogEntities) { + setFilteredCatalogEntities(filteredCatalogEntities); + } + }, [bazaarProjects, catalogEntities]); + + if (catalogEntities.loading || bazaarProjects.loading) return ; + + if (catalogEntities.error) + return {catalogEntities.error.message}; + + if (bazaarProjects.error) + return {bazaarProjects.error.message}; + + return ( + + + No project available. Please{' '} + + create a project + {' '} + from a template first. +
+ } + handleClose={handleCloseNoProjects} + /> + + + { + setOpenAdd(false); + }} + open={openAdd} + fetchBazaarProjects={fetchBazaarProjects} + fetchCatalogEntities={fetchCatalogEntities} + /> + + + + + + ); +}; diff --git a/plugins/bazaar/src/components/SortView/index.ts b/plugins/bazaar/src/components/SortView/index.ts new file mode 100644 index 0000000000..086c7c29d1 --- /dev/null +++ b/plugins/bazaar/src/components/SortView/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SortView } from './SortView'; diff --git a/plugins/bazaar/src/components/StatusTag/StatusTag.tsx b/plugins/bazaar/src/components/StatusTag/StatusTag.tsx new file mode 100644 index 0000000000..eb01ea5350 --- /dev/null +++ b/plugins/bazaar/src/components/StatusTag/StatusTag.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { StatusOK, StatusWarning } from '@backstage/core-components'; +import { Status } from '../../types'; + +interface StatusComponent { + [key: string]: JSX.Element | undefined; +} + +const statuses: StatusComponent = { + proposed: proposed, + ongoing: ongoing, +}; + +type Props = { + status: Status; + styles?: string; +}; + +export const StatusTag = ({ status, styles }: Props) => { + return
{statuses[status]}
; +}; diff --git a/plugins/bazaar/src/components/StatusTag/index.ts b/plugins/bazaar/src/components/StatusTag/index.ts new file mode 100644 index 0000000000..33886382f8 --- /dev/null +++ b/plugins/bazaar/src/components/StatusTag/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { StatusTag } from './StatusTag'; diff --git a/plugins/bazaar/src/index.ts b/plugins/bazaar/src/index.ts new file mode 100644 index 0000000000..e9b388d736 --- /dev/null +++ b/plugins/bazaar/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { bazaarPlugin, BazaarPage } from './plugin'; +export { EntityBazaarInfoCard } from './components/EntityBazaarInfoCard'; diff --git a/plugins/welcome/src/plugin.test.ts b/plugins/bazaar/src/plugin.test.ts similarity index 80% rename from plugins/welcome/src/plugin.test.ts rename to plugins/bazaar/src/plugin.test.ts index 8b429f0526..4d9964d1dd 100644 --- a/plugins/welcome/src/plugin.test.ts +++ b/plugins/bazaar/src/plugin.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,10 @@ * limitations under the License. */ -import { welcomePlugin } from './plugin'; +import { bazaarPlugin } from './plugin'; -describe('welcome', () => { +describe('bazaar', () => { it('should export plugin', () => { - expect(welcomePlugin).toBeDefined(); + expect(bazaarPlugin).toBeDefined(); }); }); diff --git a/plugins/bazaar/src/plugin.ts b/plugins/bazaar/src/plugin.ts new file mode 100644 index 0000000000..f96eaf522d --- /dev/null +++ b/plugins/bazaar/src/plugin.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { rootRouteRef } from './routes'; +import { + createApiFactory, + createPlugin, + createRoutableExtension, + identityApiRef, + discoveryApiRef, +} from '@backstage/core-plugin-api'; +import { bazaarApiRef, BazaarClient } from './api'; + +export const bazaarPlugin = createPlugin({ + id: 'bazaar', + routes: { + root: rootRouteRef, + }, + apis: [ + createApiFactory({ + api: bazaarApiRef, + deps: { + identityApi: identityApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ identityApi, discoveryApi }) => + new BazaarClient({ identityApi, discoveryApi }), + }), + ], +}); + +export const BazaarPage = bazaarPlugin.provide( + createRoutableExtension({ + component: () => import('./components/HomePage').then(m => m.HomePage), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/bazaar/src/routes.ts b/plugins/bazaar/src/routes.ts new file mode 100644 index 0000000000..a3cc88d86f --- /dev/null +++ b/plugins/bazaar/src/routes.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + title: 'bazaar', +}); diff --git a/plugins/bazaar/src/setupTests.ts b/plugins/bazaar/src/setupTests.ts new file mode 100644 index 0000000000..427556fe26 --- /dev/null +++ b/plugins/bazaar/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/bazaar/src/types.ts b/plugins/bazaar/src/types.ts new file mode 100644 index 0000000000..4050c6a2ee --- /dev/null +++ b/plugins/bazaar/src/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EntityRef } from '@backstage/catalog-model'; + +export type Member = { + entityRef: EntityRef; + userId: string; + joinDate?: string; + picture?: string; +}; + +export type Status = 'ongoing' | 'proposed'; + +export type BazaarProject = { + name: string; + entityRef: EntityRef; + community: string; + status: Status; + announcement: string; + updatedAt?: string; + membersCount: number; +}; + +export type FormValues = { + announcement: string; + community: string; + status: string; +}; diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 802e0a4abc..7e364ca294 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-bitrise +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.15 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index c581071c1a..cfaa081f49 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.15", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +39,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 6e1a819d78..849ca9309e 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.17.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index bc71ca4688..9884a55bcc 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend modules that helps integrate towards LDAP", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,14 +31,15 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/plugin-catalog-backend": "^0.16.0", + "@backstage/errors": "^0.1.2", + "@backstage/plugin-catalog-backend": "^0.17.0", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", "lodash": "^4.17.21", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.15", + "@backstage/cli": "^0.7.16", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 048aa40dc1..553081f4b0 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ForwardedError } from '@backstage/errors'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { Logger } from 'winston'; import { BindConfig } from './config'; @@ -120,7 +121,7 @@ export class LdapClient { clearInterval(logInterval); }); } catch (e) { - throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`); + throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e); } } @@ -167,7 +168,7 @@ export class LdapClient { }); }); } catch (e) { - throw new Error(`LDAP search at DN "${dn}" failed, ${e.message}`); + throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e); } } diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 0fa13b2018..749525abac 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.17.0 + ## 0.2.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index febaabe219..3e0a83a396 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend modules that helps integrate towards Microsoft Graph", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@azure/msal-node": "^1.1.0", "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/plugin-catalog-backend": "^0.16.0", + "@backstage/plugin-catalog-backend": "^0.17.0", "@microsoft/microsoft-graph-types": "^2.6.0", "cross-fetch": "^3.0.6", "lodash": "^4.17.21", @@ -42,8 +42,8 @@ }, "devDependencies": { "@backstage/backend-common": "^0.9.6", - "@backstage/cli": "^0.7.15", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/test-utils": "^0.1.19", "@types/lodash": "^4.14.151", "msw": "^0.29.0" }, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 846fba5d01..4aac915289 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend +## 0.17.0 + +### Minor Changes + +- 9fb9256e50: This continues the deprecation of classes used by the legacy catalog engine. New deprecations can be viewed in this [PR](https://github.com/backstage/backstage/pull/7500) or in the API reference documentation. + + The `batchAddOrUpdateEntities` method of the `EntitiesCatalog` interface has been marked as optional and is being deprecated. It is still implemented and required to be implemented by the legacy catalog classes, but was never implemented in the new catalog. + + This change is only relevant if you are consuming the `EntitiesCatalog` interface directly, in which case you will get a type error that you need to resolve. It can otherwise be ignored. + +### Patch Changes + +- 3b59bb915e: Fixes a bug in the catalog where entities were not being marked as orphaned. +- 55ff928d50: This change refactors the internal package structure to remove the `next` catalog folder that was used during the implementation and testing phase of the new catalog engine. The implementation is now the default and is therefore restructured to no longer be packaged under `next/`. This refactor does not change catalog imports from other parts of the project. +- Updated dependencies + - @backstage/integration@0.6.8 + ## 0.16.0 ### Minor Changes diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 6b7d966b28..a3860d2dca 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -966,6 +966,7 @@ export class FileReaderProcessor implements CatalogProcessor { location: LocationSpec, optional: boolean, emit: CatalogProcessorEmit, + parser: CatalogProcessorParser, ): Promise; } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8a3e6dc432..4375687365 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "0.16.0", + "version": "0.17.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", "@backstage/errors": "^0.1.2", - "@backstage/integration": "^0.6.7", + "@backstage/integration": "^0.6.8", "@backstage/search-common": "^0.2.0", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", @@ -62,8 +62,8 @@ }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.7", - "@backstage/cli": "^0.7.15", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/test-utils": "^0.1.19", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 0b9d3653a0..f2f478c481 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -15,7 +15,7 @@ */ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; -import { ConflictError, NotFoundError } from '@backstage/errors'; +import { ConflictError, isError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import lodash from 'lodash'; import { v4 as uuid } from 'uuid'; @@ -550,7 +550,10 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { return result.rowCount === 1 || result.length === 1; } catch (error) { // SQLite reached this rather than the rowCount check above - if (error.message.includes('UNIQUE constraint failed')) { + if ( + isError(error) && + error.message.includes('UNIQUE constraint failed') + ) { return false; } throw error; diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts index c394e9b047..63bc45726d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts @@ -16,6 +16,7 @@ import { UrlReader } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; +import { isError } from '@backstage/errors'; import limiterFactory from 'p-limit'; import * as result from './results'; import { @@ -50,7 +51,7 @@ export class AwsS3DiscoveryProcessor implements CatalogProcessor { } catch (error) { const message = `Unable to read ${location.type}, ${error}`; - if (error.name === 'NotFoundError') { + if (isError(error) && error.name === 'NotFoundError') { if (!optional) { emit(result.notFoundError(location, message)); } diff --git a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.test.ts index 8a5f202e5e..c2cce66bc6 100644 --- a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.test.ts @@ -21,6 +21,7 @@ import { CatalogProcessorResult, } from './types'; import path from 'path'; +import { defaultEntityDataParser } from './util/parse'; describe('FileReaderProcessor', () => { const fixturesRoot = path.join(__dirname, '__fixtures__/fileReaderProcessor'); @@ -33,7 +34,7 @@ describe('FileReaderProcessor', () => { }; const generated = (await new Promise(emit => - processor.readLocation(spec, false, emit), + processor.readLocation(spec, false, emit, defaultEntityDataParser), )) as CatalogProcessorEntityResult; expect(generated.type).toBe('entity'); @@ -49,7 +50,7 @@ describe('FileReaderProcessor', () => { }; const generated = (await new Promise(emit => - processor.readLocation(spec, false, emit), + processor.readLocation(spec, false, emit, defaultEntityDataParser), )) as CatalogProcessorErrorResult; expect(generated.type).toBe('error'); @@ -69,6 +70,7 @@ describe('FileReaderProcessor', () => { { type: 'file', target: `${path.join(fixturesRoot, '**', '*.yaml')}` }, false, emit, + defaultEntityDataParser, ); expect(emit).toBeCalledTimes(2); diff --git a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts index 5f6e739bdd..9ff3f52ba0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts @@ -20,8 +20,11 @@ import g from 'glob'; import path from 'path'; import { promisify } from 'util'; import * as result from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import { parseEntityYaml } from './util/parse'; +import { + CatalogProcessor, + CatalogProcessorEmit, + CatalogProcessorParser, +} from './types'; const glob = promisify(g); @@ -30,6 +33,7 @@ export class FileReaderProcessor implements CatalogProcessor { location: LocationSpec, optional: boolean, emit: CatalogProcessorEmit, + parser: CatalogProcessorParser, ): Promise { if (location.type !== 'file') { return false; @@ -44,9 +48,12 @@ export class FileReaderProcessor implements CatalogProcessor { // The normalize converts to native slashes; the glob library returns // forward slashes even on windows - for (const parseResult of parseEntityYaml(data, { - type: 'file', - target: path.normalize(fileMatch), + for await (const parseResult of parser({ + data: data, + location: { + type: 'file', + target: path.normalize(fileMatch), + }, })) { emit(parseResult); } diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts index cf889eb577..6a85cc07c7 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts @@ -16,6 +16,7 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { assertError } from '@backstage/errors'; import parseGitUrl from 'git-url-parse'; import limiterFactory from 'p-limit'; import { Logger } from 'winston'; @@ -91,6 +92,7 @@ export class UrlReaderProcessor implements CatalogProcessor { }); } } catch (error) { + assertError(error); const message = `Unable to read ${location.type}, ${error}`; if (error.name === 'NotModifiedError' && cacheItem) { for (const parseResult of cacheItem.value) { diff --git a/plugins/catalog-backend/src/legacy/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/legacy/ingestion/LocationReaders.ts index 91902ce5be..04347615e5 100644 --- a/plugins/catalog-backend/src/legacy/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/legacy/ingestion/LocationReaders.ts @@ -255,7 +255,7 @@ export class LocationReaders implements LocationReader { const message = `Policy check failed while analyzing entity ${kind}:${namespace}/${name} at ${stringifyLocationReference( item.location, )}, ${e}`; - emit(result.inputError(item.location, e.message)); + emit(result.inputError(item.location, message)); logger.warn(message); return undefined; } diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 59cd4d94ae..1ce32a01e1 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -19,7 +19,7 @@ import { entityEnvelopeSchemaValidator, stringifyEntityRef, } from '@backstage/catalog-model'; -import { serializeError } from '@backstage/errors'; +import { assertError, serializeError } from '@backstage/errors'; import { Hash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Logger } from 'winston'; @@ -257,6 +257,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { track.markSuccessfulWithChanges(setOfThingsToStitch.size); } catch (error) { + assertError(error); track.markFailed(error); } }, diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index e973dcbf0c..d3ec162216 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -23,7 +23,12 @@ import { stringifyEntityRef, stringifyLocationReference, } from '@backstage/catalog-model'; -import { ConflictError, InputError, NotAllowedError } from '@backstage/errors'; +import { + assertError, + ConflictError, + InputError, + NotAllowedError, +} from '@backstage/errors'; import { JsonValue } from '@backstage/config'; import { ScmIntegrationRegistry } from '@backstage/integration'; import path from 'path'; @@ -160,6 +165,7 @@ export class DefaultCatalogProcessingOrchestrator ok: collectorResults.errors.length === 0, }; } catch (error) { + assertError(error); return { ok: false, errors: collector.results().errors.concat(error), diff --git a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index dfaa25418b..436038db66 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -21,6 +21,7 @@ import { ORIGIN_LOCATION_ANNOTATION, stringifyLocationReference, } from '@backstage/catalog-model'; +import { assertError } from '@backstage/errors'; import { Logger } from 'winston'; import { CatalogProcessorResult } from '../ingestion'; import { locationSpecToLocationEntity } from '../util/conversion'; @@ -73,6 +74,7 @@ export class ProcessorOutputCollector { try { entity = validateEntityEnvelope(i.entity); } catch (e) { + assertError(e); this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`); this.errors.push(e); return; diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts index bb556fc49f..20e141b921 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts @@ -37,6 +37,20 @@ const expectedEntities: Entity[] = [ owner: 'someone', }, }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + title: 'Test Entity', + name: 'test-entity-2', + description: 'The expected description 2', + }, + spec: { + type: 'some-type', + lifecycle: 'experimental', + owner: 'someone', + }, + }, ]; describe('DefaultCatalogCollator', () => { @@ -89,6 +103,15 @@ describe('DefaultCatalogCollator', () => { lifecycle: expectedEntities[0]!.spec!.lifecycle, owner: expectedEntities[0]!.spec!.owner, }); + expect(documents[1]).toMatchObject({ + title: expectedEntities[1].metadata.title, + location: '/catalog/default/component/test-entity-2', + text: expectedEntities[1].metadata.description, + namespace: 'default', + componentType: expectedEntities[1]!.spec!.type, + lifecycle: expectedEntities[1]!.spec!.lifecycle, + owner: expectedEntities[1]!.spec!.owner, + }); }); it('maps a returned entity with a custom locationTemplate', async () => { diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index 6292b2a833..a28a7645c4 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -87,7 +87,7 @@ export class DefaultCatalogCollator implements DocumentCollator { }); return response.items.map((entity: Entity): CatalogEntityDocument => { return { - title: entity.metadata.name, + title: entity.metadata.title ?? entity.metadata.name, location: this.applyArgsToFormat(this.locationTemplate, { namespace: entity.metadata.namespace || 'default', kind: entity.kind, diff --git a/plugins/catalog-backend/src/stitching/Stitcher.ts b/plugins/catalog-backend/src/stitching/Stitcher.ts index 0aa39f5ac8..1d40340b3e 100644 --- a/plugins/catalog-backend/src/stitching/Stitcher.ts +++ b/plugins/catalog-backend/src/stitching/Stitcher.ts @@ -47,194 +47,196 @@ export class Stitcher { async stitch(entityRefs: Set) { for (const entityRef of entityRefs) { try { - const entityResult = await this.database( - 'refresh_state', - ) - .where({ entity_ref: entityRef }) - .limit(1) - .select('entity_id'); - if (!entityResult.length) { - // Entity does no exist in refresh state table, no stitching required. - continue; - } - - // Insert stitching ticket that will be compared before inserting the final entity. - const ticket = uuid(); - await this.database('final_entities') - .insert({ - entity_id: entityResult[0].entity_id, - hash: '', - stitch_ticket: ticket, - }) - .onConflict('entity_id') - .merge(['stitch_ticket']); - - // Selecting from refresh_state and final_entities should yield exactly - // one row (except in abnormal cases where the stitch was invoked for - // something that didn't exist at all, in which case it's zero rows). - // The join with the temporary incoming_references still gives one row. - // The only result set "expanding" join is the one with relations, so - // the output should be at least one row (if zero or one relations were - // found), or at most the same number of rows as relations. - const result: Array<{ - entityId: string; - processedEntity?: string; - errors: string; - incomingReferenceCount: string | number; - previousHash?: string; - relationType?: string; - relationTarget?: string; - }> = await this.database - .with('incoming_references', function incomingReferences(builder) { - return builder - .from('refresh_state_references') - .where({ target_entity_ref: entityRef }) - .count({ count: '*' }); - }) - .select({ - entityId: 'refresh_state.entity_id', - processedEntity: 'refresh_state.processed_entity', - errors: 'refresh_state.errors', - incomingReferenceCount: 'incoming_references.count', - previousHash: 'final_entities.hash', - relationType: 'relations.type', - relationTarget: 'relations.target_entity_ref', - }) - .from('refresh_state') - .where({ 'refresh_state.entity_ref': entityRef }) - .crossJoin(this.database.raw('incoming_references')) - .leftOuterJoin('final_entities', { - 'final_entities.entity_id': 'refresh_state.entity_id', - }) - .leftOuterJoin('relations', { - 'relations.source_entity_ref': 'refresh_state.entity_ref', - }) - .orderBy('relationType', 'asc') - .orderBy('relationTarget', 'asc'); - - // If there were no rows returned, it would mean that there was no - // matching row even in the refresh_state. This can happen for example - // if we emit a relation to something that hasn't been ingested yet. - // It's safe to ignore this stitch attempt in that case. - if (!result.length) { - this.logger.error( - `Unable to stitch ${entityRef}, item does not exist in refresh state table`, - ); - continue; - } - - const { - entityId, - processedEntity, - errors, - incomingReferenceCount, - previousHash, - } = result[0]; - - // If there was no processed entity in place, the target hasn't been - // through the processing steps yet. It's safe to ignore this stitch - // attempt in that case, since another stitch will be triggered when - // that processing has finished. - if (!processedEntity) { - this.logger.debug( - `Unable to stitch ${entityRef}, the entity has not yet been processed`, - ); - continue; - } - - // Grab the processed entity and stitch all of the relevant data into - // it - const entity = JSON.parse(processedEntity) as Entity; - const isOrphan = Number(incomingReferenceCount) === 0; - let statusItems: UNSTABLE_EntityStatusItem[] = []; - - if (isOrphan) { - this.logger.debug(`${entityRef} is an orphan`); - entity.metadata.annotations = { - ...entity.metadata.annotations, - ['backstage.io/orphan']: 'true', - }; - } - if (errors) { - const parsedErrors = JSON.parse(errors) as SerializedError[]; - if (Array.isArray(parsedErrors) && parsedErrors.length) { - statusItems = parsedErrors.map(e => ({ - type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE, - level: 'error', - message: `${e.name}: ${e.message}`, - error: e, - })); - } - } - - // TODO: entityRef is lower case and should be uppercase in the final - // result - const uniqueRelationRows = uniqBy( - result, - r => `${r.relationType}:${r.relationTarget}`, - ); - entity.relations = uniqueRelationRows - .filter(row => row.relationType /* exclude null row, if relevant */) - .map(row => ({ - type: row.relationType!, - target: parseEntityRef(row.relationTarget!), - })); - if (statusItems.length) { - entity.status = { - ...entity.status, - items: [...(entity.status?.items ?? []), ...statusItems], - }; - } - - // If the output entity was actually not changed, just abort - const hash = generateStableHash(entity); - if (hash === previousHash) { - this.logger.debug(`Skipped stitching of ${entityRef}, no changes`); - continue; - } - - entity.metadata.uid = entityId; - entity.metadata.generation = 1; - if (!entity.metadata.etag) { - // If the original data source did not have its own etag handling, - // use the hash as a good-quality etag - entity.metadata.etag = hash; - } - - const rowsChanged = await this.database( - 'final_entities', - ) - .update({ - final_entity: JSON.stringify(entity), - hash, - }) - .where('entity_id', entityId) - .where('stitch_ticket', ticket) - .onConflict('entity_id') - .merge(['final_entity', 'hash']); - - if (rowsChanged.length === 0) { - this.logger.debug( - `Entity ${entityRef} is already processed, skipping write.`, - ); - continue; - } - - // TODO(freben): Search will probably need a similar safeguard against - // race conditions like the final_entities ticket handling above. - // Otherwise, it can be the case that: - // A writes the entity -> - // B writes the entity -> - // B writes search -> - // A writes search - const searchEntries = buildEntitySearch(entityId, entity); - await this.database('search') - .where({ entity_id: entityId }) - .delete(); - await this.database.batchInsert('search', searchEntries, BATCH_SIZE); + await this.stitchOne(entityRef); } catch (error) { this.logger.error(`Failed to stitch ${entityRef}, ${error}`); } } } + + private async stitchOne(entityRef: string): Promise { + const entityResult = await this.database('refresh_state') + .where({ entity_ref: entityRef }) + .limit(1) + .select('entity_id'); + if (!entityResult.length) { + // Entity does no exist in refresh state table, no stitching required. + return; + } + + // Insert stitching ticket that will be compared before inserting the final entity. + const ticket = uuid(); + await this.database('final_entities') + .insert({ + entity_id: entityResult[0].entity_id, + hash: '', + stitch_ticket: ticket, + }) + .onConflict('entity_id') + .merge(['stitch_ticket']); + + // Selecting from refresh_state and final_entities should yield exactly + // one row (except in abnormal cases where the stitch was invoked for + // something that didn't exist at all, in which case it's zero rows). + // The join with the temporary incoming_references still gives one row. + // The only result set "expanding" join is the one with relations, so + // the output should be at least one row (if zero or one relations were + // found), or at most the same number of rows as relations. + const result: Array<{ + entityId: string; + processedEntity?: string; + errors: string; + incomingReferenceCount: string | number; + previousHash?: string; + relationType?: string; + relationTarget?: string; + }> = await this.database + .with('incoming_references', function incomingReferences(builder) { + return builder + .from('refresh_state_references') + .where({ target_entity_ref: entityRef }) + .count({ count: '*' }); + }) + .select({ + entityId: 'refresh_state.entity_id', + processedEntity: 'refresh_state.processed_entity', + errors: 'refresh_state.errors', + incomingReferenceCount: 'incoming_references.count', + previousHash: 'final_entities.hash', + relationType: 'relations.type', + relationTarget: 'relations.target_entity_ref', + }) + .from('refresh_state') + .where({ 'refresh_state.entity_ref': entityRef }) + .crossJoin(this.database.raw('incoming_references')) + .leftOuterJoin('final_entities', { + 'final_entities.entity_id': 'refresh_state.entity_id', + }) + .leftOuterJoin('relations', { + 'relations.source_entity_ref': 'refresh_state.entity_ref', + }) + .orderBy('relationType', 'asc') + .orderBy('relationTarget', 'asc'); + + // If there were no rows returned, it would mean that there was no + // matching row even in the refresh_state. This can happen for example + // if we emit a relation to something that hasn't been ingested yet. + // It's safe to ignore this stitch attempt in that case. + if (!result.length) { + this.logger.error( + `Unable to stitch ${entityRef}, item does not exist in refresh state table`, + ); + return; + } + + const { + entityId, + processedEntity, + errors, + incomingReferenceCount, + previousHash, + } = result[0]; + + // If there was no processed entity in place, the target hasn't been + // through the processing steps yet. It's safe to ignore this stitch + // attempt in that case, since another stitch will be triggered when + // that processing has finished. + if (!processedEntity) { + this.logger.debug( + `Unable to stitch ${entityRef}, the entity has not yet been processed`, + ); + return; + } + + // Grab the processed entity and stitch all of the relevant data into + // it + const entity = JSON.parse(processedEntity) as Entity; + const isOrphan = Number(incomingReferenceCount) === 0; + let statusItems: UNSTABLE_EntityStatusItem[] = []; + + if (isOrphan) { + this.logger.debug(`${entityRef} is an orphan`); + entity.metadata.annotations = { + ...entity.metadata.annotations, + ['backstage.io/orphan']: 'true', + }; + } + if (errors) { + const parsedErrors = JSON.parse(errors) as SerializedError[]; + if (Array.isArray(parsedErrors) && parsedErrors.length) { + statusItems = parsedErrors.map(e => ({ + type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE, + level: 'error', + message: `${e.name}: ${e.message}`, + error: e, + })); + } + } + + // TODO: entityRef is lower case and should be uppercase in the final + // result + const uniqueRelationRows = uniqBy( + result, + r => `${r.relationType}:${r.relationTarget}`, + ); + entity.relations = uniqueRelationRows + .filter(row => row.relationType /* exclude null row, if relevant */) + .map(row => ({ + type: row.relationType!, + target: parseEntityRef(row.relationTarget!), + })); + if (statusItems.length) { + entity.status = { + ...entity.status, + items: [...(entity.status?.items ?? []), ...statusItems], + }; + } + + // If the output entity was actually not changed, just abort + const hash = generateStableHash(entity); + if (hash === previousHash) { + this.logger.debug(`Skipped stitching of ${entityRef}, no changes`); + return; + } + + entity.metadata.uid = entityId; + entity.metadata.generation = 1; + if (!entity.metadata.etag) { + // If the original data source did not have its own etag handling, + // use the hash as a good-quality etag + entity.metadata.etag = hash; + } + + const rowsChanged = await this.database( + 'final_entities', + ) + .update({ + final_entity: JSON.stringify(entity), + hash, + }) + .where('entity_id', entityId) + .where('stitch_ticket', ticket) + .onConflict('entity_id') + .merge(['final_entity', 'hash']); + + if (rowsChanged.length === 0) { + this.logger.debug( + `Entity ${entityRef} is already processed, skipping write.`, + ); + return; + } + + // TODO(freben): Search will probably need a similar safeguard against + // race conditions like the final_entities ticket handling above. + // Otherwise, it can be the case that: + // A writes the entity -> + // B writes the entity -> + // B writes search -> + // A writes search + const searchEntries = buildEntitySearch(entityId, entity); + await this.database('search') + .where({ entity_id: entityId }) + .delete(); + await this.database.batchInsert('search', searchEntries, BATCH_SIZE); + } } diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 20eb85af21..79910921ca 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-graph +## 0.2.0 + +### Minor Changes + +- 5c42360577: Add documentation and more type safety around DependencyGraph + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index e2de70e510..29fd6876e4 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,10 +23,10 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "p-limit": "^3.1.0" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", - "@backstage/core-app-api": "^0.1.16", + "@backstage/cli": "^0.7.16", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", + "@backstage/core-app-api": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index 7e3149fef7..9fe482a30b 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -15,12 +15,14 @@ */ import { Entity } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { analyticsApiRef } from '@backstage/core-plugin-api'; import { CatalogApi, catalogApiRef, EntityProvider, } from '@backstage/plugin-catalog-react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { catalogEntityRouteRef, catalogGraphRouteRef } from '../../routes'; import { CatalogGraphCard } from './CatalogGraphCard'; @@ -117,4 +119,30 @@ describe('', () => { '/catalog-graph?rootEntityRefs%5B%5D=b%3Ad%2Fc', ); }); + + test('captures analytics event on click', async () => { + const analyticsSpy = new MockAnalyticsApi(); + const { findByText } = await renderInTestApp( + + {wrapper} + , + { + mountedRoutes: { + '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef, + '/catalog-graph': catalogGraphRouteRef, + }, + }, + ); + + expect(await findByText('b:d/c')).toBeInTheDocument(); + userEvent.click(await findByText('b:d/c')); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'b:d/c', + attributes: { + to: '/entity/{kind}/{namespace}/{name}', + }, + }); + }); }); diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index 5ac8a89539..42e85a75ba 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -19,8 +19,11 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { InfoCard, InfoCardVariants } from '@backstage/core-components'; -import { useRouteRef } from '@backstage/core-plugin-api'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api'; +import { + formatEntityRefTitle, + useEntity, +} from '@backstage/plugin-catalog-react'; import { makeStyles, Theme } from '@material-ui/core'; import qs from 'qs'; import React, { MouseEvent, useCallback } from 'react'; @@ -78,6 +81,7 @@ export const CatalogGraphCard = ({ const catalogGraphRoute = useRouteRef(catalogGraphRouteRef); const navigate = useNavigate(); const classes = useStyles({ height }); + const analytics = useAnalytics(); const onNodeClick = useCallback( (node: EntityNode, _: MouseEvent) => { @@ -87,9 +91,14 @@ export const CatalogGraphCard = ({ namespace: nodeEntityName.namespace.toLocaleLowerCase('en-US'), name: nodeEntityName.name, }); + analytics.captureEvent( + 'click', + node.title ?? formatEntityRefTitle(nodeEntityName), + { attributes: { to: path } }, + ); navigate(path); }, - [catalogEntityRoute, navigate], + [catalogEntityRoute, navigate, analytics], ); const catalogGraphParams = qs.stringify( diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx index 8b467a017e..b992d0c976 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx @@ -15,8 +15,9 @@ */ import { RELATION_HAS_PART, RELATION_PART_OF } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { analyticsApiRef } from '@backstage/core-plugin-api'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { catalogEntityRouteRef } from '../../routes'; @@ -167,4 +168,53 @@ describe('', () => { expect(navigate).toBeCalledWith('/entity/{kind}/{namespace}/{name}'); }); + + test('should capture analytics event when selecting other entity', async () => { + const analyticsSpy = new MockAnalyticsApi(); + const { getByText, findAllByTestId } = await renderInTestApp( + + {wrapper} + , + { + mountedRoutes: { + '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef, + }, + }, + ); + + expect(await findAllByTestId('node')).toHaveLength(2); + + userEvent.click(getByText('b:d/e')); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'b:d/e', + }); + }); + + test('should capture analytics event when navigating to entity', async () => { + const analyticsSpy = new MockAnalyticsApi(); + const { getByText, findAllByTestId } = await renderInTestApp( + + {wrapper} + , + { + mountedRoutes: { + '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef, + }, + }, + ); + + expect(await findAllByTestId('node')).toHaveLength(2); + + userEvent.click(getByText('b:d/e'), { shiftKey: true }); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'b:d/e', + attributes: { + to: '/entity/{kind}/{namespace}/{name}', + }, + }); + }); }); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index f5f1ced541..fdcb71d3ad 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -21,7 +21,7 @@ import { Page, SupportButton, } from '@backstage/core-components'; -import { useRouteRef } from '@backstage/core-plugin-api'; +import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api'; import { formatEntityRefTitle } from '@backstage/plugin-catalog-react'; import { Grid, makeStyles, Paper, Typography } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; @@ -31,11 +31,11 @@ import React, { MouseEvent, useCallback } from 'react'; import { useNavigate } from 'react-router'; import { catalogEntityRouteRef } from '../../routes'; import { + ALL_RELATION_PAIRS, Direction, EntityNode, EntityRelationsGraph, RelationPairs, - ALL_RELATION_PAIRS, } from '../EntityRelationsGraph'; import { DirectionFilter } from './DirectionFilter'; import { MaxDepthFilter } from './MaxDepthFilter'; @@ -133,6 +133,7 @@ export const CatalogGraphPage = ({ showFilters, toggleShowFilters, } = useCatalogGraphPage({ initialState }); + const analytics = useAnalytics(); const onNodeClick = useCallback( (node: EntityNode, event: MouseEvent) => { const nodeEntityName = parseEntityRef(node.id); @@ -143,12 +144,22 @@ export const CatalogGraphPage = ({ namespace: nodeEntityName.namespace.toLocaleLowerCase('en-US'), name: nodeEntityName.name, }); + + analytics.captureEvent( + 'click', + node.title ?? formatEntityRefTitle(nodeEntityName), + { attributes: { to: path } }, + ); navigate(path); } else { + analytics.captureEvent( + 'click', + node.title ?? formatEntityRefTitle(nodeEntityName), + ); setRootEntityNames([nodeEntityName]); } }, - [catalogEntityRoute, navigate, setRootEntityNames], + [catalogEntityRoute, navigate, setRootEntityNames, analytics], ); return ( diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 9b13184018..4f903bd731 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-import +## 0.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/integration-react@0.1.12 + ## 0.7.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index f571b5df31..d443d3cb22 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.7.1", + "version": "0.7.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,12 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/integration": "^0.6.7", - "@backstage/integration-react": "^0.1.11", - "@backstage/plugin-catalog-react": "^0.5.2", + "@backstage/errors": "^0.1.2", + "@backstage/integration": "^0.6.8", + "@backstage/integration-react": "^0.1.12", + "@backstage/plugin-catalog-react": "^0.6.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -54,10 +55,10 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 75910db5a2..7784aa7284 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -113,8 +113,8 @@ export const StepInitAnalyzeUrl = ({ break; } } - } catch (e) { - setError(e.data?.error?.message ?? e.message); + } catch (e: any) { + setError(e?.data?.error?.message ?? e.message); setSubmitted(false); } }, diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx index 2618fab57a..fe5e76c968 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { makeStyles } from '@material-ui/core'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; import { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent'; @@ -45,26 +45,28 @@ const entities: Entity[] = [ ]; describe('', () => { - it('renders without exploding', async () => { - const { getByText, findByText } = render( + it('renders without exploding', () => { + render( , ); - const repositoryUrl = getByText('http://my-repository/a/catalog-info.yaml'); - const kindText = await findByText('Kind_2'); + const repositoryUrl = screen.getByText( + 'http://my-repository/a/catalog-info.yaml', + ); + const kindText = screen.getByText(/Kind_2/); expect(repositoryUrl).toBeInTheDocument(); expect(repositoryUrl).toBeVisible(); expect(kindText).toBeInTheDocument(); expect(kindText).toBeVisible(); }); - it('renders card with custom styles', async () => { + it('renders card with custom styles', () => { const { result } = renderHook(() => useStyles()); - const { getByText } = render( + render( ', () => { />, ); - const repositoryUrl = getByText('http://my-repository/a/catalog-info.yaml'); - const kindText = getByText('Kind_2'); + const repositoryUrl = screen.getByText( + 'http://my-repository/a/catalog-info.yaml', + ); + const kindText = screen.getByText(/Kind_2/); expect(repositoryUrl).toBeInTheDocument(); expect(repositoryUrl).not.toBeVisible(); expect(kindText).toBeInTheDocument(); expect(kindText).not.toBeVisible(); }); - it('renders with custom styles', async () => { + it('renders with custom styles', () => { const { result } = renderHook(() => useStyles()); - const { getByText } = render( + render( ', () => { />, ); - const repositoryUrl = getByText('http://my-repository/a/catalog-info.yaml'); - const kindText = getByText('Kind_2'); + const repositoryUrl = screen.getByText( + 'http://my-repository/a/catalog-info.yaml', + ); + const kindText = screen.getByText(/Kind_2/); expect(repositoryUrl).toBeInTheDocument(); expect(repositoryUrl).toBeVisible(); expect(kindText).toBeInTheDocument(); diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 6cabc52ddb..f441554119 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { assertError } from '@backstage/errors'; import { catalogApiRef, formatEntityRefTitle, @@ -174,6 +175,7 @@ export const StepPrepareCreatePullRequest = ({ { notRepeatable: true }, ); } catch (e) { + assertError(e); setError(e.message); setSubmitted(false); } diff --git a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx index 99dc972678..5c4f9b9594 100644 --- a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx +++ b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx @@ -25,6 +25,7 @@ import { PrepareResult, ReviewResult } from '../useImportState'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { assertError } from '@backstage/errors'; type Props = { prepareResult: PrepareResult; @@ -88,6 +89,7 @@ export const StepReviewLocation = ({ locations, }); } catch (e) { + assertError(e); // TODO: this error should be handled differently. We add it as 'optional' and // it is not uncommon that a PR has not been merged yet. if ( diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index ef2dd15f93..3ea6c26ae0 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-react +## 0.6.0 + +### Minor Changes + +- 82fbda923e: Introduce a new `StarredEntitiesApi` that is used in the `useStarredEntities` hook. + The `@backstage/plugin-catalog` installs a default implementation that is backed by the `StorageApi`, but one can also override the `starredEntitiesApiRef`. + + This change also updates the storage format from a custom string to an entity reference and moves the location in the local storage. + A migration will convert the previously starred entities to the location on the first load of Backstage. + +### Patch Changes + +- 0366c9b667: Introduce a `useStarredEntity` hook to check if a single entity is starred. + It provides a more efficient implementation compared to the `useStarredEntities` hook, because the rendering is only triggered if the selected entity is starred, not if _any_ entity is starred. +- 4aca84f86b: Support `material-ui` overrides in plugin-catalog-react components +- b03b9f19e0: added sorting in entity `Name` column by `metadata.title` if present +- Updated dependencies + - @backstage/integration@0.6.8 + - @backstage/core-app-api@0.1.17 + - @backstage/core-components@0.7.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index ce7282276e..e28b8b0d32 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -16,11 +16,13 @@ import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { IconButton } from '@material-ui/core'; import { LinkProps } from '@backstage/core-components'; +import { Observable } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { StorageApi } from '@backstage/core-plugin-api'; import { SystemEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core-components'; import { UserEntity } from '@backstage/catalog-model'; @@ -131,6 +133,17 @@ export type DefaultEntityFilters = { text?: EntityTextFilter; }; +// @public +export class DefaultStarredEntitiesApi implements StarredEntitiesApi { + constructor(opts: { storageApi: StorageApi }); + // (undocumented) + isStarred(entityRef: string): boolean; + // (undocumented) + starredEntitie$(): Observable>; + // (undocumented) + toggleStarred(entityRef: string): Promise; +} + // Warning: (ae-forgotten-export) The symbol "EntityLoadingStatus" needs to be exported by the entry point index.d.ts // // @public @deprecated (undocumented) @@ -742,6 +755,15 @@ export function reduceEntityFilters( // @public (undocumented) export const rootRoute: RouteRef; +// @public +export interface StarredEntitiesApi { + starredEntitie$(): Observable>; + toggleStarred(entityRef: string): Promise; +} + +// @public +export const starredEntitiesApiRef: ApiRef; + // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "UnregisterEntityDialog" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -866,10 +888,18 @@ export const UserListPicker: ({ // Warning: (ae-missing-release-tag) "useStarredEntities" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const useStarredEntities: () => { +export function useStarredEntities(): { starredEntities: Set; - toggleStarredEntity: (entity: Entity) => void; - isStarredEntity: (entity: Entity) => boolean; + toggleStarredEntity: (entityOrRef: Entity | EntityName | string) => void; + isStarredEntity: (entityOrRef: Entity | EntityName | string) => boolean; +}; + +// Warning: (ae-missing-release-tag) "useStarredEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function useStarredEntity(entityOrRef: Entity | EntityName | string): { + toggleStarredEntity: () => void; + isStarredEntity: boolean; }; // Warnings were encountered during analysis: diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index f6b36a7191..2e9fa8fdd6 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "0.5.2", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,11 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.4", - "@backstage/core-app-api": "^0.1.16", - "@backstage/core-components": "^0.6.1", + "@backstage/core-app-api": "^0.1.17", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/integration": "^0.6.7", + "@backstage/errors": "^0.1.2", + "@backstage/integration": "^0.6.8", "@backstage/version-bridge": "^0.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -45,17 +46,19 @@ "qs": "^6.9.4", "react": "^16.13.1", "react-router": "6.0.0-beta.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/jwt-decode": "^3.1.0", + "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", "react-test-renderer": "^16.13.1" }, diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.test.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.test.ts new file mode 100644 index 0000000000..55b992fadf --- /dev/null +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { StorageApi } from '@backstage/core-plugin-api'; +import { MockStorageApi } from '@backstage/test-utils'; +import { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi'; +import { performMigrationToTheNewBucket } from './migration'; + +jest.mock('./migration'); + +describe('DefaultStarredEntitiesApi', () => { + let mockStorage: StorageApi; + let starredEntitiesApi: DefaultStarredEntitiesApi; + + const mockEntityRef = stringifyEntityRef({ + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock', + }, + }); + + beforeEach(() => { + (performMigrationToTheNewBucket as jest.Mock).mockResolvedValue(undefined); + + mockStorage = MockStorageApi.create(); + starredEntitiesApi = new DefaultStarredEntitiesApi({ + storageApi: mockStorage, + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('constructor', () => { + it('should call migration', () => { + expect(performMigrationToTheNewBucket).toBeCalledTimes(1); + }); + }); + + describe('toggleStarred', () => { + it('should star unstarred entity', async () => { + expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(false); + + await starredEntitiesApi.toggleStarred(mockEntityRef); + + expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(true); + }); + + it('should unstar starred entity', async () => { + const bucket = mockStorage.forBucket('starredEntities'); + await bucket.set('entityRefs', ['component:default/mock']); + + expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(true); + + await starredEntitiesApi.toggleStarred(mockEntityRef); + + expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(false); + }); + }); + + describe('starredEntities$', () => { + const handler = jest.fn(); + + beforeEach(async () => { + await new Promise(resolve => { + starredEntitiesApi.starredEntitie$().subscribe({ + next: (...args) => { + handler(...args); + + if (handler.mock.calls.length >= 2) { + resolve(); + } + }, + }); + + const bucket = mockStorage.forBucket('starredEntities'); + bucket.set('entityRefs', ['component:default/mock']).then(); + }); + }); + + it('should receive updates', async () => { + expect(handler).toBeCalledTimes(2); + expect(handler).toBeCalledWith(new Set()); + expect(handler).toBeCalledWith(new Set(['component:default/mock'])); + }); + }); +}); diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts new file mode 100644 index 0000000000..87d99c8904 --- /dev/null +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Observable, StorageApi } from '@backstage/core-plugin-api'; +import ObservableImpl from 'zen-observable'; +import { performMigrationToTheNewBucket } from './migration'; +import { StarredEntitiesApi } from './StarredEntitiesApi'; + +/** + * Default implementation of the StarredEntitiesApi that is backed by the StorageApi. + * + * @public + */ +export class DefaultStarredEntitiesApi implements StarredEntitiesApi { + private readonly settingsStore: StorageApi; + private starredEntities: Set; + + constructor(opts: { storageApi: StorageApi }) { + // no need to await. The updated content will be caught by the observe$ + performMigrationToTheNewBucket(opts).then(); + + this.settingsStore = opts.storageApi.forBucket('starredEntities'); + + this.starredEntities = new Set( + this.settingsStore.get('entityRefs') ?? [], + ); + + this.settingsStore.observe$('entityRefs').subscribe({ + next: next => { + this.starredEntities = new Set(next.newValue ?? []); + this.notifyChanges(); + }, + }); + } + + async toggleStarred(entityRef: string): Promise { + if (this.starredEntities.has(entityRef)) { + this.starredEntities.delete(entityRef); + } else { + this.starredEntities.add(entityRef); + } + + await this.settingsStore.set( + 'entityRefs', + Array.from(this.starredEntities), + ); + } + + starredEntitie$(): Observable> { + return this.observable; + } + + isStarred(entityRef: string): boolean { + return this.starredEntities.has(entityRef); + } + + private readonly subscribers = new Set< + ZenObservable.SubscriptionObserver> + >(); + + private readonly observable = new ObservableImpl>(subscriber => { + // forward the the latest value + subscriber.next(this.starredEntities); + + this.subscribers.add(subscriber); + return () => { + this.subscribers.delete(subscriber); + }; + }); + + private notifyChanges() { + for (const subscription of this.subscribers) { + subscription.next(this.starredEntities); + } + } +} diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts new file mode 100644 index 0000000000..4c4c68d66a --- /dev/null +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef, Observable } from '@backstage/core-plugin-api'; + +/** + * An API to store starred entities + * + * @public + */ +export const starredEntitiesApiRef: ApiRef = createApiRef({ + id: 'catalog-react.starred-entities', +}); + +/** + * An API to store and retrieve starred entities + * + * @public + */ +export interface StarredEntitiesApi { + /** + * Toggle the star state of the entity + * + * @param entityRef - an entity reference to toggle + */ + toggleStarred(entityRef: string): Promise; + + /** + * Observe the set of starred entity references. + */ + starredEntitie$(): Observable>; +} diff --git a/plugins/welcome/src/index.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/index.ts similarity index 70% rename from plugins/welcome/src/index.ts rename to plugins/catalog-react/src/apis/StarredEntitiesApi/index.ts index df8f99b6ed..e9f9c8923a 100644 --- a/plugins/welcome/src/index.ts +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,6 @@ * limitations under the License. */ -/** - * An old Backstage plugin that provides a welcome page - * - * @packageDocumentation - */ - -export { welcomePlugin, welcomePlugin as plugin, WelcomePage } from './plugin'; +export { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi'; +export { starredEntitiesApiRef } from './StarredEntitiesApi'; +export type { StarredEntitiesApi } from './StarredEntitiesApi'; diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.test.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.test.ts new file mode 100644 index 0000000000..1ff3be811f --- /dev/null +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StorageApi } from '@backstage/core-plugin-api'; +import { MockStorageApi } from '@backstage/test-utils'; +import { performMigrationToTheNewBucket } from './migration'; + +describe('performMigrationToTheNewBucket', () => { + let mockStorage: StorageApi; + + beforeEach(() => { + mockStorage = MockStorageApi.create(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should migrate', async () => { + const oldBucket = mockStorage.forBucket('settings'); + const newBucket = mockStorage.forBucket('starredEntities'); + + // fill NEW bucket + await newBucket.set('entityRefs', ['component:default/c']); + + // fill OLD bucket + await oldBucket.set('starredEntities', [ + 'entity:Component:default:a', + 'entity:template:custom:b', + ]); + expect(oldBucket.get('starredEntities')).not.toBeUndefined(); + + await performMigrationToTheNewBucket({ storageApi: mockStorage }); + + // read NEW bucket + expect(await newBucket.get('entityRefs')).toEqual([ + 'component:default/c', + 'component:default/a', + 'template:custom/b', + ]); + + // OLD bucket should be removed + expect(oldBucket.get('starredEntities')).toBeUndefined(); + }); + + it('should ignore invalid entries', async () => { + const oldBucket = mockStorage.forBucket('settings'); + const newBucket = mockStorage.forBucket('starredEntities'); + + // fill OLD bucket + await oldBucket.set('starredEntities', [ + 'entity:Component:default:a', + 1, + 'entity:Component:a', + 'invalid', + ]); + expect(oldBucket.get('starredEntities')).not.toBeUndefined(); + + await performMigrationToTheNewBucket({ storageApi: mockStorage }); + + // read NEW bucket + expect(await newBucket.get('entityRefs')).toEqual(['component:default/a']); + + // OLD bucket should be removed + expect(oldBucket.get('starredEntities')).toBeUndefined(); + }); + + it('should skip migration without old starred entities', async () => { + const newBucket = mockStorage.forBucket('starredEntities'); + + // fill NEW bucket + const expectedEntries = ['component:default/a']; + await newBucket.set('entityRefs', expectedEntries); + + await performMigrationToTheNewBucket({ storageApi: mockStorage }); + + // read NEW bucket + expect(newBucket.get('entityRefs')).toBe(expectedEntries); + }); + + it('should skip migration with non-array old starred entities', async () => { + const oldBucket = mockStorage.forBucket('settings'); + const newBucket = mockStorage.forBucket('starredEntities'); + + // fill OLD bucket with invalid content + await oldBucket.set('starredEntities', 'invalid'); + + // fill NEW bucket + const expectedEntries = ['component:default/a']; + await newBucket.set('entityRefs', expectedEntries); + + await performMigrationToTheNewBucket({ storageApi: mockStorage }); + + // read NEW bucket + expect(newBucket.get('entityRefs')).toBe(expectedEntries); + + // OLD bucket should be unchanged + expect(oldBucket.get('starredEntities')).toBe('invalid'); + }); +}); diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.ts new file mode 100644 index 0000000000..7147d449ab --- /dev/null +++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/migration.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { StorageApi } from '@backstage/core-plugin-api'; +import { isArray, isString } from 'lodash'; + +/** + * Migrate the starred entities from the old format (entity:::) from the + * old storage location (/settings/starredEntities) to entity references in the new location + * (/starredEntities/entityRefs). + * + * This will only be executed once since the old location is cleared. + * + * @param storageApi - the StorageApi to migrate + */ +export async function performMigrationToTheNewBucket({ + storageApi, +}: { + storageApi: StorageApi; +}) { + const source = storageApi.forBucket('settings'); + const target = storageApi.forBucket('starredEntities'); + + const oldStarredEntities = source.get('starredEntities'); + + if (!isArray(oldStarredEntities)) { + // nothing to do + return; + } + + const targetEntities = new Set(target.get('entityRefs') ?? []); + + oldStarredEntities + .filter(isString) + // extract the old format 'entity:::' + .map(old => old.split(':')) + // check if the format is valid + .filter(split => split.length === 4 && split[0] === 'entity') + // convert to entity references + .map(([_, kind, namespace, name]) => + stringifyEntityRef({ kind, namespace, name }), + ) + .forEach(e => targetEntities.add(e)); + + await target.set('entityRefs', Array.from(targetEntities)); + + await source.remove('starredEntities'); +} diff --git a/plugins/catalog-react/src/apis/index.ts b/plugins/catalog-react/src/apis/index.ts new file mode 100644 index 0000000000..5c7e980890 --- /dev/null +++ b/plugins/catalog-react/src/apis/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './StarredEntitiesApi'; diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index 95e617ab7f..db60c34b66 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ -import React, { ComponentProps } from 'react'; -import { useStarredEntities } from '../../hooks/useStarredEntities'; -import { IconButton, Tooltip, withStyles } from '@material-ui/core'; -import StarBorder from '@material-ui/icons/StarBorder'; -import Star from '@material-ui/icons/Star'; import { Entity } from '@backstage/catalog-model'; +import { IconButton, Tooltip, withStyles } from '@material-ui/core'; +import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; +import React, { ComponentProps } from 'react'; +import { useStarredEntity } from '../../hooks/useStarredEntity'; type Props = ComponentProps & { entity: Entity }; @@ -40,16 +40,17 @@ export const favoriteEntityIcon = (isStarred: boolean) => * @param props MaterialUI IconButton props extended by required `entity` prop */ export const FavoriteEntity = (props: Props) => { - const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); - const isStarred = isStarredEntity(props.entity); + const { toggleStarredEntity, isStarredEntity } = useStarredEntity( + props.entity, + ); return ( toggleStarredEntity(props.entity)} + onClick={() => toggleStarredEntity()} > - - {favoriteEntityIcon(isStarred)} + + {favoriteEntityIcon(isStarredEntity)} ); diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 4dce00e7b7..5c267916ea 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -33,6 +33,7 @@ import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { assertError } from '@backstage/errors'; const useStyles = makeStyles({ advancedButton: { @@ -70,6 +71,7 @@ const Contents = ({ await state.unregisterLocation(); onConfirm(); } catch (err) { + assertError(err); alertApi.post({ message: err.message }); } finally { setBusy(false); @@ -87,6 +89,7 @@ const Contents = ({ await state.deleteEntity(); onConfirm(); } catch (err) { + assertError(err); alertApi.post({ message: err.message }); } finally { setBusy(false); diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 47bca8892a..38fc58222d 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -36,4 +36,5 @@ export { useEntityKinds } from './useEntityKinds'; export { useOwnUser } from './useOwnUser'; export { useRelatedEntities } from './useRelatedEntities'; export { useStarredEntities } from './useStarredEntities'; +export { useStarredEntity } from './useStarredEntity'; export { useEntityOwnership } from './useEntityOwnership'; diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 5d711016a4..e448ee73f1 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -14,21 +14,8 @@ * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; -import qs from 'qs'; -import { act, renderHook } from '@testing-library/react-hooks'; -import { MockStorageApi } from '@backstage/test-utils'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { - EntityListProvider, - useEntityListProvider, -} from './useEntityListProvider'; -import { catalogApiRef } from '../api'; -import { UserListFilterKind } from '../types'; -import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters'; -import { EntityKindPicker, UserListPicker } from '../components'; - import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; import { ConfigApi, @@ -37,6 +24,19 @@ import { identityApiRef, storageApiRef, } from '@backstage/core-plugin-api'; +import { MockStorageApi } from '@backstage/test-utils'; +import { act, renderHook } from '@testing-library/react-hooks'; +import qs from 'qs'; +import React, { PropsWithChildren } from 'react'; +import { catalogApiRef } from '../api'; +import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis'; +import { EntityKindPicker, UserListPicker } from '../components'; +import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters'; +import { UserListFilterKind } from '../types'; +import { + EntityListProvider, + useEntityListProvider, +} from './useEntityListProvider'; const entities: Entity[] = [ { @@ -81,6 +81,10 @@ const apis = ApiRegistry.from([ [catalogApiRef, mockCatalogApi], [identityApiRef, mockIdentityApi], [storageApiRef, MockStorageApi.create()], + [ + starredEntitiesApiRef, + new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }), + ], ]); const wrapper = ({ diff --git a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx index 50662312da..4f41ef090f 100644 --- a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx +++ b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx @@ -14,16 +14,18 @@ * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { useStarredEntities } from './useStarredEntities'; -import { storageApiRef, StorageApi } from '@backstage/core-plugin-api'; -import { MockErrorApi } from '@backstage/test-utils'; import { Entity } from '@backstage/catalog-model'; -import { ApiProvider, ApiRegistry, WebStorage } from '@backstage/core-app-api'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { StorageApi } from '@backstage/core-plugin-api'; +import { MockStorageApi } from '@backstage/test-utils'; +import { act, renderHook } from '@testing-library/react-hooks'; +import React, { PropsWithChildren } from 'react'; +import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis'; +import { useStarredEntities } from './useStarredEntities'; describe('useStarredEntities', () => { - let mockStorage: StorageApi | undefined; + let mockStorage: StorageApi; + let wrapper: React.ComponentType; const mockEntity: Entity = { apiVersion: '1', @@ -42,41 +44,56 @@ describe('useStarredEntities', () => { }, }; - const wrapper = ({ children }: PropsWithChildren<{}>) => { - return ( - + beforeEach(() => { + mockStorage = MockStorageApi.create(); + wrapper = ({ children }: PropsWithChildren<{}>) => ( + {children} ); - }; - - beforeEach(() => { - mockStorage = new WebStorage('@backstage', new MockErrorApi()).forBucket( - Date.now().toString(), // TODO(blam): need something that changes every test run for now until the MockStorage is implemented - ); }); + it('should return an empty set for when there is no items in storage', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); + + await waitForNextUpdate(); expect(result.current.starredEntities.size).toBe(0); }); + it('should return a set with the current items when there are items in storage', async () => { const expectedIds = ['i', 'am', 'some', 'test', 'ids']; - const store = mockStorage?.forBucket('settings'); - await store?.set('starredEntities', expectedIds); + const store = mockStorage?.forBucket('starredEntities'); + await store?.set('entityRefs', expectedIds); - const { result } = renderHook(() => useStarredEntities(), { wrapper }); + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); + + await waitForNextUpdate(); for (const item of expectedIds) { expect(result.current.starredEntities.has(item)).toBeTruthy(); } }); + it('should listen to changes when the storage is set elsewhere', async () => { const { result, waitForNextUpdate } = renderHook( () => useStarredEntities(), { wrapper }, ); + await waitForNextUpdate(); + expect(result.current.starredEntities.size).toBe(0); expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); @@ -91,18 +108,26 @@ describe('useStarredEntities', () => { }); it('should write new entries to the local store when adding a toggling entity', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); act(() => { result.current.toggleStarredEntity(mockEntity); }); + await waitForNextUpdate(); + expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); expect(result.current.isStarredEntity(secondMockEntity)).toBeFalsy(); }); it('should remove an existing entity when toggling entries', async () => { - const { result } = renderHook(() => useStarredEntities(), { wrapper }); + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); act(() => { result.current.toggleStarredEntity(mockEntity); @@ -110,6 +135,8 @@ describe('useStarredEntities', () => { result.current.toggleStarredEntity(mockEntity); }); + await waitForNextUpdate(); + expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); expect(result.current.isStarredEntity(secondMockEntity)).toBeTruthy(); }); diff --git a/plugins/catalog-react/src/hooks/useStarredEntities.ts b/plugins/catalog-react/src/hooks/useStarredEntities.ts index 6f7b895fc8..8f6b09614f 100644 --- a/plugins/catalog-react/src/hooks/useStarredEntities.ts +++ b/plugins/catalog-react/src/hooks/useStarredEntities.ts @@ -14,62 +14,49 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { storageApiRef, useApi } from '@backstage/core-plugin-api'; -import { useCallback, useEffect, useState } from 'react'; +import { + Entity, + EntityName, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { useCallback } from 'react'; import { useObservable } from 'react-use'; +import { starredEntitiesApiRef } from '../apis'; -const buildEntityKey = (component: Entity) => - `entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${ - component.metadata.name - }`; +function getEntityRef(entityOrRef: Entity | EntityName | string): string { + return typeof entityOrRef === 'string' + ? entityOrRef + : stringifyEntityRef(entityOrRef); +} -export const useStarredEntities = () => { - const storageApi = useApi(storageApiRef); - const settingsStore = storageApi.forBucket('settings'); - const rawStarredEntityKeys = - settingsStore.get('starredEntities') ?? []; +export function useStarredEntities(): { + starredEntities: Set; + toggleStarredEntity: (entityOrRef: Entity | EntityName | string) => void; + isStarredEntity: (entityOrRef: Entity | EntityName | string) => boolean; +} { + const starredEntitiesApi = useApi(starredEntitiesApiRef); - const [starredEntities, setStarredEntities] = useState( - new Set(rawStarredEntityKeys), - ); - - const observedItems = useObservable( - settingsStore.observe$('starredEntities'), - ); - - useEffect(() => { - if (observedItems?.newValue) { - const currentValue = observedItems?.newValue ?? []; - setStarredEntities(new Set(currentValue)); - } - }, [observedItems?.newValue]); - - const toggleStarredEntity = useCallback( - (entity: Entity) => { - const entityKey = buildEntityKey(entity); - if (starredEntities.has(entityKey)) { - starredEntities.delete(entityKey); - } else { - starredEntities.add(entityKey); - } - - settingsStore.set('starredEntities', Array.from(starredEntities)); - }, - [starredEntities, settingsStore], + const starredEntities = useObservable( + starredEntitiesApi.starredEntitie$(), + new Set(), ); const isStarredEntity = useCallback( - (entity: Entity) => { - const entityKey = buildEntityKey(entity); - return starredEntities.has(entityKey); - }, + (entityOrRef: Entity | EntityName | string) => + starredEntities.has(getEntityRef(entityOrRef)), [starredEntities], ); + const toggleStarredEntity = useCallback( + (entityOrRef: Entity | EntityName | string) => + starredEntitiesApi.toggleStarred(getEntityRef(entityOrRef)).then(), + [starredEntitiesApi], + ); + return { starredEntities, toggleStarredEntity, isStarredEntity, }; -}; +} diff --git a/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx new file mode 100644 index 0000000000..b8577aec0c --- /dev/null +++ b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity, EntityName } from '@backstage/catalog-model'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { renderHook } from '@testing-library/react-hooks'; +import React, { PropsWithChildren } from 'react'; +import Observable from 'zen-observable'; +import { StarredEntitiesApi, starredEntitiesApiRef } from '../apis'; +import { useStarredEntity } from './useStarredEntity'; + +describe('useStarredEntity', () => { + const mockStarredEntitiesApi: jest.Mocked = { + toggleStarred: jest.fn(), + starredEntitie$: jest.fn(), + }; + let wrapper: React.ComponentType; + + beforeEach(() => { + wrapper = ({ children }: PropsWithChildren<{}>) => ( + + {children} + + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe.each` + title | entityOrRef + ${'entity reference'} | ${'component:default/mock'} + ${'entity name'} | ${{ kind: 'component', namespace: 'default', name: 'mock' } as EntityName} + ${'entity'} | ${{ apiVersion: '1', kind: 'Component', metadata: { name: 'mock' } } as Entity} + `('with $title', ({ entityOrRef }) => { + describe('toggleStarredEntity', () => { + it('should toggle starred entity', () => { + mockStarredEntitiesApi.starredEntitie$.mockReturnValue(Observable.of()); + mockStarredEntitiesApi.toggleStarred.mockResolvedValue(); + + const { result } = renderHook(() => useStarredEntity(entityOrRef), { + wrapper, + }); + + result.current.toggleStarredEntity(); + + expect(mockStarredEntitiesApi.toggleStarred).toBeCalledTimes(1); + expect(mockStarredEntitiesApi.toggleStarred).toBeCalledWith( + 'component:default/mock', + ); + }); + }); + + describe('isStarredEntity', () => { + it('should return not starred entity', () => { + mockStarredEntitiesApi.starredEntitie$.mockReturnValue(Observable.of()); + mockStarredEntitiesApi.toggleStarred.mockResolvedValue(); + + const { result } = renderHook(() => useStarredEntity(entityOrRef), { + wrapper, + }); + + expect(result.current.isStarredEntity).toBe(false); + }); + + it('should return starred entity', async () => { + mockStarredEntitiesApi.starredEntitie$.mockReturnValue( + Observable.of(new Set(['component:default/mock'])), + ); + mockStarredEntitiesApi.toggleStarred.mockResolvedValue(); + + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntity(entityOrRef), + { + wrapper, + }, + ); + + // the initial value will always be false because the observable triggers async + expect(result.current.isStarredEntity).toBe(false); + await waitForNextUpdate(); + + expect(result.current.isStarredEntity).toBe(true); + }); + }); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useStarredEntity.ts b/plugins/catalog-react/src/hooks/useStarredEntity.ts new file mode 100644 index 0000000000..0233432196 --- /dev/null +++ b/plugins/catalog-react/src/hooks/useStarredEntity.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Entity, + EntityName, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { useCallback, useEffect, useState } from 'react'; +import { starredEntitiesApiRef } from '../apis'; + +function getEntityRef(entityOrRef: Entity | EntityName | string): string { + return typeof entityOrRef === 'string' + ? entityOrRef + : stringifyEntityRef(entityOrRef); +} + +export function useStarredEntity(entityOrRef: Entity | EntityName | string): { + toggleStarredEntity: () => void; + isStarredEntity: boolean; +} { + const starredEntitiesApi = useApi(starredEntitiesApiRef); + + const [isStarredEntity, setIsStarredEntity] = useState(false); + + useEffect(() => { + const subscription = starredEntitiesApi.starredEntitie$().subscribe({ + next(starredEntities: Set) { + setIsStarredEntity(starredEntities.has(getEntityRef(entityOrRef))); + }, + }); + + return () => { + subscription.unsubscribe(); + }; + }, [entityOrRef, starredEntitiesApi]); + + const toggleStarredEntity = useCallback( + () => starredEntitiesApi.toggleStarred(getEntityRef(entityOrRef)).then(), + [entityOrRef, starredEntitiesApi], + ); + + return { + toggleStarredEntity, + isStarredEntity, + }; +} diff --git a/plugins/catalog-react/src/index.ts b/plugins/catalog-react/src/index.ts index 1ac67d514b..cf7a679f7a 100644 --- a/plugins/catalog-react/src/index.ts +++ b/plugins/catalog-react/src/index.ts @@ -23,6 +23,7 @@ export type { CatalogApi } from '@backstage/catalog-client'; export { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; export { catalogApiRef } from './api'; +export * from './apis'; export * from './components'; export * from './hooks'; export * from './filters'; diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 0e00949dcd..48c7c65c70 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog +## 0.7.1 + +### Patch Changes + +- 82fbda923e: Introduce a new `StarredEntitiesApi` that is used in the `useStarredEntities` hook. + The `@backstage/plugin-catalog` installs a default implementation that is backed by the `StorageApi`, but one can also override the `starredEntitiesApiRef`. + + This change also updates the storage format from a custom string to an entity reference and moves the location in the local storage. + A migration will convert the previously starred entities to the location on the first load of Backstage. + +- 93a13dfb42: Support `material-ui` overrides in SystemDiagramCard and EmptityLinksEmptyState components +- b03b9f19e0: added sorting in entity `Name` column by `metadata.title` if present +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/integration-react@0.1.12 + ## 0.7.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 3de20d7f95..45b9549538 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "0.7.0", + "version": "0.7.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/integration-react": "^0.1.11", - "@backstage/plugin-catalog-react": "^0.5.2", + "@backstage/integration-react": "^0.1.12", + "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/errors": "^0.1.2", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 48ec1ef176..6b33b33b08 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -20,28 +20,32 @@ import { RELATION_MEMBER_OF, RELATION_OWNED_BY, } from '@backstage/catalog-model'; -import { TableColumn, TableProps } from '@backstage/core-components'; -import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react'; -import { - MockStorageApi, - renderWithEffects, - wrapInTestApp, - mockBreakpoint, -} from '@backstage/test-utils'; -import { fireEvent, screen } from '@testing-library/react'; -import React from 'react'; -import { createComponentRouteRef } from '../../routes'; -import { EntityRow } from '../CatalogTable/types'; -import { CatalogPage } from './CatalogPage'; -import DashboardIcon from '@material-ui/icons/Dashboard'; - import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { TableColumn, TableProps } from '@backstage/core-components'; import { IdentityApi, identityApiRef, ProfileInfo, storageApiRef, } from '@backstage/core-plugin-api'; +import { + catalogApiRef, + DefaultStarredEntitiesApi, + entityRouteRef, + starredEntitiesApiRef, +} from '@backstage/plugin-catalog-react'; +import { + mockBreakpoint, + MockStorageApi, + renderWithEffects, + wrapInTestApp, +} from '@backstage/test-utils'; +import DashboardIcon from '@material-ui/icons/Dashboard'; +import { fireEvent, screen } from '@testing-library/react'; +import React from 'react'; +import { createComponentRouteRef } from '../../routes'; +import { EntityRow } from '../CatalogTable'; +import { CatalogPage } from './CatalogPage'; describe('CatalogPage', () => { const origReplaceState = window.history.replaceState; @@ -120,6 +124,7 @@ describe('CatalogPage', () => { getIdToken: async () => undefined, getProfile: () => testProfile, }; + const storageApi = MockStorageApi.create(); const renderWrapped = (children: React.ReactNode) => renderWithEffects( @@ -128,7 +133,11 @@ describe('CatalogPage', () => { apis={ApiRegistry.from([ [catalogApiRef, catalogApi], [identityApiRef, identityApi], - [storageApiRef, MockStorageApi.create()], + [storageApiRef, storageApi], + [ + starredEntitiesApiRef, + new DefaultStarredEntitiesApi({ storageApi }), + ], ])} > {children} diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 6c80e64fa9..822913a385 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -15,19 +15,22 @@ */ import { + EDIT_URL_ANNOTATION, Entity, VIEW_URL_ANNOTATION, - EDIT_URL_ANNOTATION, } from '@backstage/catalog-model'; -import { act, fireEvent } from '@testing-library/react'; -import { renderInTestApp } from '@backstage/test-utils'; -import * as React from 'react'; -import { CatalogTable } from './CatalogTable'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; import { entityRouteRef, + DefaultStarredEntitiesApi, MockEntityListContextProvider, + starredEntitiesApiRef, UserListFilter, } from '@backstage/plugin-catalog-react'; +import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; +import { act, fireEvent } from '@testing-library/react'; +import * as React from 'react'; +import { CatalogTable } from './CatalogTable'; const entities: Entity[] = [ { @@ -48,6 +51,11 @@ const entities: Entity[] = [ ]; describe('CatalogTable component', () => { + const mockApis = ApiRegistry.with( + starredEntitiesApiRef, + new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }), + ); + beforeEach(() => { window.open = jest.fn(); }); @@ -58,9 +66,11 @@ describe('CatalogTable component', () => { it('should render error message', async () => { const rendered = await renderInTestApp( - - - , + + + + + , { mountedRoutes: { '/catalog/:namespace/:kind/:name': entityRouteRef, @@ -75,20 +85,22 @@ describe('CatalogTable component', () => { it('should display entity names when loading has finished and no error occurred', async () => { const rendered = await renderInTestApp( - false, - () => false, - ), - }, - }} - > - - , + + false, + () => false, + ), + }, + }} + > + + + , { mountedRoutes: { '/catalog/:namespace/:kind/:name': entityRouteRef, @@ -112,9 +124,11 @@ describe('CatalogTable component', () => { }; const { getByTitle } = await renderInTestApp( - - - , + + + + + , { mountedRoutes: { '/catalog/:namespace/:kind/:name': entityRouteRef, @@ -142,9 +156,11 @@ describe('CatalogTable component', () => { }; const { getByTitle } = await renderInTestApp( - - - , + + + + + , { mountedRoutes: { '/catalog/:namespace/:kind/:name': entityRouteRef, diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx index 7722f6ba17..2216070984 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx @@ -13,24 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { - catalogApiRef, - EntityProvider, AsyncEntityProvider, + catalogApiRef, + DefaultStarredEntitiesApi, + EntityProvider, entityRouteRef, + starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { Route, Routes } from 'react-router'; import { EntityLayout } from './EntityLayout'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; -import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; - const mockEntity = { kind: 'MyKind', metadata: { @@ -38,10 +40,12 @@ const mockEntity = { }, } as Entity; -const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi).with( - alertApiRef, - {} as AlertApi, -); +const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi) + .with(alertApiRef, {} as AlertApi) + .with( + starredEntitiesApiRef, + new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }), + ); describe('EntityLayout', () => { it('renders simplest case', async () => { diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index cb5ea98031..20aa673ea3 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -19,6 +19,7 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { Button, Dialog, DialogActions, DialogTitle } from '@material-ui/core'; import React, { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { assertError } from '@backstage/errors'; type Props = { open: boolean; @@ -44,6 +45,7 @@ export const DeleteEntityDialog = ({ await catalogApi.removeEntityByUid(uid!); onConfirm(); } catch (err) { + assertError(err); alertApi.post({ message: err.message }); } finally { setBusy(false); diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 9b90a44aff..20f937b2e7 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -18,7 +18,9 @@ import { CatalogClient } from '@backstage/catalog-client'; import { catalogApiRef, catalogRouteRef, + DefaultStarredEntitiesApi, entityRouteRef, + starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; import { CatalogClientWrapper } from './CatalogClientWrapper'; import { createComponentRouteRef, viewTechDocRouteRef } from './routes'; @@ -29,6 +31,7 @@ import { createRoutableExtension, discoveryApiRef, identityApiRef, + storageApiRef, } from '@backstage/core-plugin-api'; export const catalogPlugin = createPlugin({ @@ -43,6 +46,13 @@ export const catalogPlugin = createPlugin({ identityApi, }), }), + + createApiFactory({ + api: starredEntitiesApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => + new DefaultStarredEntitiesApi({ storageApi }), + }), ], routes: { catalogIndex: catalogRouteRef, diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 3607e9430d..a501d77ff3 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-circleci +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.26 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index edd11711ae..58b3f5ffe6 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.2.26", + "version": "0.2.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +52,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index ac9bda48e8..dfd17e9908 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cloudbuild +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.26 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 37e36df83b..127143ddb2 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.2.26", + "version": "0.2.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 33bdd55346..08a2a62e14 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-coverage-backend +## 0.1.13 + +### Patch Changes + +- da9241530a: check for existence of lines property in files +- Updated dependencies + - @backstage/integration@0.6.8 + ## 0.1.12 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 4feb1e037f..c059dd50c2 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.1.12", + "version": "0.1.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.6.7", + "@backstage/integration": "^0.6.8", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "express": "^4.17.1", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.15", + "@backstage/cli": "^0.7.16", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.29.0", diff --git a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts index b666e042de..97a99c10b2 100644 --- a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts +++ b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts @@ -208,7 +208,7 @@ describe('CodeCoverageUtils', () => { }; // @ts-ignore utils.validateRequestBody(mockRequest as Request); - } catch (error) { + } catch (error: any) { err = error; } expect(err?.message).toEqual('Content-Type missing'); @@ -226,7 +226,7 @@ describe('CodeCoverageUtils', () => { // @ts-ignore utils.validateRequestBody(mockRequest as Request); - } catch (error) { + } catch (error: any) { err = error; } expect(err?.message).toEqual('Illegal Content-Type'); diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 8e3312b169..4bf3c6ea49 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-code-coverage +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.14 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 0f1708e912..01d8c7258d 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.1.14", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,11 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index c16d6c1230..9e6e82c0c3 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-config-schema +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.10 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 272313d064..856d96940e 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.10", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +36,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index dbd0c578cf..8a7b07af3b 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-cost-insights +## 0.11.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.11.9 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index d1e8fb4c3a..d424001e27 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.9", + "version": "0.11.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -55,10 +55,10 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 7d4de81c65..f051691dc5 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-explore +## 0.3.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.19 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 400ddc42ac..9ed480b91f 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.19", + "version": "0.3.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", + "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/plugin-explore-react": "^0.0.6", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 0fc18c58f9..2343eb1c2e 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-firehydrant +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.6 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 557aa6ea9d..e199153805 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index b4ba20ad10..04dd560e50 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-fossa +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.19 ### Patch Changes diff --git a/plugins/fossa/api-report.md b/plugins/fossa/api-report.md index 50e382d3d4..d6e229d3d6 100644 --- a/plugins/fossa/api-report.md +++ b/plugins/fossa/api-report.md @@ -21,7 +21,17 @@ export const EntityFossaCard: ({ // Warning: (ae-missing-release-tag) "FossaPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const FossaPage: () => JSX.Element; +export const FossaPage: ({ entitiesFilter }: FossaPageProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "FossaPageProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type FossaPageProps = { + entitiesFilter?: + | Record[] + | Record + | undefined; +}; // Warning: (ae-missing-release-tag) "fossaPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 7d6e9bcf19..c7751b7002 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.19", + "version": "0.2.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx index 78c8b7bb40..778f5ee9c6 100644 --- a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx +++ b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx @@ -142,4 +142,38 @@ describe('', () => { expect(getByText(/0 Issues/i)).toBeInTheDocument(); expect(getByText(/10 Issues/i)).toBeInTheDocument(); }); + + it('has configurable entity filter', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'API', + metadata: { + name: 'my-name-0', + annotations: { + 'fossa.io/project-name': 'my-name-0', + }, + }, + }; + + fossaApi.getFindingSummaries.mockResolvedValue(new Map()); + catalogApi.getEntities.mockResolvedValue({ items: [entity] }); + + const { getByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(catalogApi.getEntities).toBeCalledWith( + expect.objectContaining({ + filter: { kind: 'API' }, + }), + ); + expect(getByText(/my-name-0/i)).toBeInTheDocument(); + }); }); diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.tsx index 3e16085968..fbdaf85717 100644 --- a/plugins/fossa/src/components/FossaPage/FossaPage.tsx +++ b/plugins/fossa/src/components/FossaPage/FossaPage.tsx @@ -30,8 +30,8 @@ import { Tooltip } from '@material-ui/core'; import { Skeleton } from '@material-ui/lab'; import { DateTime } from 'luxon'; import * as React from 'react'; -import { useMemo } from 'react'; -import { useAsync } from 'react-use'; +import { useMemo, useState } from 'react'; +import { useAsync, useDeepCompareEffect } from 'react-use'; import { FindingSummary, fossaApiRef } from '../../api'; import { getProjectName } from '../getProjectName'; @@ -166,14 +166,25 @@ const filters: TableFilter[] = [ { column: 'Branch', type: 'select' }, ]; -export const FossaPage = () => { +export type FossaPageProps = { + entitiesFilter?: + | Record[] + | Record + | undefined; +}; + +export const FossaPage = ({ + entitiesFilter = { kind: 'Component' }, +}: FossaPageProps) => { const catalogApi = useApi(catalogApiRef); const fossaApi = useApi(fossaApiRef); + const [filter, setFilter] = useState(entitiesFilter); + useDeepCompareEffect(() => setFilter(entitiesFilter), [entitiesFilter]); // Get a list of all relevant entities const { value: entities, loading: entitiesLoading } = useAsync(() => { return catalogApi.getEntities({ - filter: { kind: 'Component' }, + filter, fields: [ 'kind', 'metadata.namespace', @@ -182,7 +193,7 @@ export const FossaPage = () => { 'relations', ], }); - }); + }, [filter]); // get the project names of all entities. the idx of both lists match. const projectNames = useMemo( diff --git a/plugins/fossa/src/components/FossaPage/index.ts b/plugins/fossa/src/components/FossaPage/index.ts index 7be77be441..13dd680a95 100644 --- a/plugins/fossa/src/components/FossaPage/index.ts +++ b/plugins/fossa/src/components/FossaPage/index.ts @@ -15,3 +15,4 @@ */ export { FossaPage } from './FossaPage'; +export type { FossaPageProps } from './FossaPage'; diff --git a/plugins/fossa/src/components/index.ts b/plugins/fossa/src/components/index.ts index 12632d4fff..f108626e88 100644 --- a/plugins/fossa/src/components/index.ts +++ b/plugins/fossa/src/components/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './FossaCard'; +export type { FossaPageProps } from './FossaPage'; diff --git a/plugins/fossa/src/index.ts b/plugins/fossa/src/index.ts index 200b977f3b..7f75fad178 100644 --- a/plugins/fossa/src/index.ts +++ b/plugins/fossa/src/index.ts @@ -20,5 +20,6 @@ * @packageDocumentation */ -export { fossaPlugin } from './plugin'; +export * from './components'; export { EntityFossaCard, FossaPage } from './extensions'; +export { fossaPlugin } from './plugin'; diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index e0b2a84aff..28c40f6221 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-gcp-projects +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.7 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 5f71ffe4b6..e346a313fb 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index e60f641007..36eb386a9a 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-git-release-manager +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.0 ### Minor Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 74487bf0cc..d076f0312c 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/integration": "^0.6.7", - "@backstage/theme": "^0.2.10", + "@backstage/integration": "^0.6.8", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +39,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/git-release-manager/src/features/Patch/hooks/usePatchDryRun.tsx b/plugins/git-release-manager/src/features/Patch/hooks/usePatchDryRun.tsx index de3a67e874..3bd69b1028 100644 --- a/plugins/git-release-manager/src/features/Patch/hooks/usePatchDryRun.tsx +++ b/plugins/git-release-manager/src/features/Patch/hooks/usePatchDryRun.tsx @@ -391,6 +391,6 @@ export function usePatchDryRun({ asyncCatcher, abortIfError, selectedPatchCommit: latestCommitOnReleaseBranchRes.value - ?.selectedPatchCommit as any, + ?.selectedPatchCommit as GetRecentCommitsResultSingle, }; } diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index ec59cc10a6..222bc4c851 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-github-actions +## 0.4.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.4.21 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index b473f9b675..19e8548ebb 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.4.21", + "version": "0.4.22", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/integration": "^0.6.7", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/integration": "^0.6.8", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +52,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 3cebaad3ce..5259ea11b2 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-deployments +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/integration-react@0.1.12 + ## 0.1.19 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 7cd079edbd..5282ad19ea 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,13 +22,13 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.6.7", - "@backstage/integration-react": "^0.1.11", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/integration": "^0.6.8", + "@backstage/integration-react": "^0.1.12", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +39,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index d9959c7729..08f256cdb5 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-gitops-profiles +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.7 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index d728929b1d..acd19cf681 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 760c63e5d2..aa58dbd332 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-graphiql +## 0.2.20 + +### Patch Changes + +- c8215e1fe6: Add experimental `experimentalInstallationRecipe` to `package.json`. +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.19 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 3e179c75a3..8e31bb3225 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.19", + "version": "0.2.20", "private": false, "publishConfig": { "access": "public", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index a14e6e01e3..aa35cdf968 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-home +## 0.4.4 + +### Patch Changes + +- ef5bf4235a: Adds a `` component that shows a playful greeting on the home page. + To use it, pass it to the home page header: + + ```typescript + +
} pageTitleOverride="Home"> + +
+ … +
+ ``` + +- 87b2d5ad88: Fix `` to display only the selected tab, not the other way around. +- 2435d7a49b: Added HeaderWorldClock to the Home plugin which is a copy of the HomepageTimer from core-components that has been updated to use props over static config from app-config.yaml. To use HeaderWorldClock you'll need to create an array of ClockConfig like this: + + ```ts + const clockConfigs: ClockConfig[] = [ + { + label: 'NYC', + timeZone: 'America/New_York', + }, + { + label: 'UTC', + timeZone: 'UTC', + }, + { + label: 'STO', + timeZone: 'Europe/Stockholm', + }, + { + label: 'TYO', + timeZone: 'Asia/Tokyo', + }, + ]; + ``` + + Then you can pass `clockConfigs` into the HeaderWorldClock like this: + + ```ts + +
+ +
+ ... +
+ ``` + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.4.3 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index dc7e130235..9764748024 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.3", + "version": "0.4.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 406b846162..efa2d5d8f6 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-ilert +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.14 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 697f3ca790..e19f77395c 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.14", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@date-io/luxon": "2.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,10 +39,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 04cb19ab9c..ed4ccd63a8 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-jenkins +## 0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.5.9 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 5048f4c3eb..5c5aa53010 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.5.9", + "version": "0.5.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx index 0519ed3816..ee17df8533 100644 --- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -80,6 +80,12 @@ const FailSkippedWidget = ({ }; const generatedColumns: TableColumn[] = [ + { + title: 'Timestamp', + defaultSort: 'desc', + hidden: true, + field: 'lastBuild.timestamp', + }, { title: 'Build', field: 'fullName', diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 5cc277aa7e..3697f8de28 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kafka +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.18 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index d7c18cac65..125530dcc2 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.2.18", + "version": "0.2.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 004476d8c1..6b37417f72 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-kubernetes-backend +## 0.3.17 + +### Patch Changes + +- 89bcf90b66: Refactor kubernetes fetcher to reduce boilerplate code +- a982e166c5: Enable customization of services used by the kubernetes backend plugin + + The createRouter function has been deprecated in favor of a KubernetesBuilder object. + Here's how you should upgrade your projects when configuring the Kubernetes backend plugin. + in your `packages/backend/src/plugins/kubernetes.ts` file for instance: + + ```typescript + import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + config, + }: PluginEnvironment) { + const { router } = await KubernetesBuilder.createBuilder({ + logger, + config, + }).build(); + return router; + } + ``` + ## 0.3.16 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index f588771294..b2026faea6 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.3.16", + "version": "0.3.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,6 +35,7 @@ "@backstage/backend-common": "^0.9.4", "@backstage/catalog-model": "^0.9.3", "@backstage/config": "^0.1.10", + "@backstage/errors": "^0.1.2", "@backstage/plugin-kubernetes-common": "^0.1.4", "@google-cloud/container": "^2.2.0", "@kubernetes/client-node": "^0.15.0", @@ -54,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.13", + "@backstage/cli": "^0.7.16", "@types/aws4": "^1.5.1", "supertest": "^6.1.3", "aws-sdk-mock": "^5.2.1", diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts index 7b2dfbf8de..545f839907 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts @@ -213,7 +213,7 @@ describe('GkeClusterLocator', () => { } as any); await expect(sut.getClusters()).rejects.toThrow( - 'There was an error retrieving clusters from GKE for projectId=some-project region=some-region : [some error]', + 'There was an error retrieving clusters from GKE for projectId=some-project region=some-region; caused by Error: some error', ); expect(mockedListClusters).toBeCalledTimes(1); diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts index 3cc6c216ae..7492ca88d3 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts @@ -15,6 +15,7 @@ */ import { Config } from '@backstage/config'; +import { ForwardedError } from '@backstage/errors'; import * as container from '@google-cloud/container'; import { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types'; @@ -65,8 +66,9 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { skipTLSVerify, })); } catch (e) { - throw new Error( - `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region} : [${e.message}]`, + throw new ForwardedError( + `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`, + e, ); } } diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 6994729a3b..a0a4ef2a51 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes +## 0.4.17 + +### Patch Changes + +- 89bcf90b66: Refactor kubernetes fetcher to reduce boilerplate code +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.4.16 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index a8e271e85d..bdbf3f2359 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.4.16", + "version": "0.4.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", + "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/plugin-kubernetes-common": "^0.1.5", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@kubernetes/client-node": "^0.15.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index fad1da1e6e..4ff76d4f1b 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-lighthouse +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.28 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 14facb2439..a6b681d523 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.2.28", + "version": "0.2.29", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 3b8b004fc0..7ffd4b06d1 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-newrelic +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.7 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 8b50f26077..ac49033a1b 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index a9dab3d9e2..6e1cc7064f 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-org +## 0.3.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.26 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index d391bf8081..2edc9b5ffe 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.3.26", + "version": "0.3.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -37,10 +37,10 @@ "qs": "^6.10.1" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 626b7e9afd..11ae548db8 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-pagerduty +## 0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.16 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index fc213e0576..b88212a743 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.3.16", + "version": "0.3.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 76ea278858..4f7f3c9a16 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-rollbar +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.17 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 62486e915d..ea641f7f78 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.3.17", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 25ae32c5d9..160a0229d5 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-backend +## 0.15.9 + +### Patch Changes + +- 0f99f1170e: Make sure `sourcePath` of `publish:github:pull-request` can only be used to + retrieve files from the workspace. + +## 0.15.8 + +### Patch Changes + +- 42c618abf6: Use `resolveSafeChildPath` in the `fetchContents` function to forbid reading files outside the base directory when a template is registered from a `file:` location. +- 18083d1821: Introduce the new `scaffolder.backstage.io/v1beta3` template kind with nunjucks support 🥋 +- Updated dependencies + - @backstage/integration@0.6.8 + - @backstage/plugin-catalog-backend@0.17.0 + ## 0.15.7 ### Patch Changes diff --git a/plugins/scaffolder-backend/fixtures/test-v1beta3/template.yaml b/plugins/scaffolder-backend/fixtures/test-v1beta3/template.yaml new file mode 100644 index 0000000000..10d4a39ff7 --- /dev/null +++ b/plugins/scaffolder-backend/fixtures/test-v1beta3/template.yaml @@ -0,0 +1,35 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: test-v1beta3 + title: Test v1beta3 + description: Test V1 Beta 3 Demo Templates +spec: + type: website + owner: team-a + parameters: + - name: Enter some stuff + description: Enter some stuff + properties: + inputString: + type: string + title: string input test + inputObject: + type: object + title: object input test + description: a little nested thing never hurt anyone right? + properties: + first: + type: string + title: first + second: + type: number + title: second + steps: + - id: debug + if: ${{ true === true }} + name: Debug + action: debug:log + input: + message: ${{ parameters.inputString }} + extra: ${{ parameters.inputObject }} diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 98fd053771..05377123c0 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "0.15.7", + "version": "0.15.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,8 +35,8 @@ "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", "@backstage/errors": "^0.1.2", - "@backstage/integration": "^0.6.7", - "@backstage/plugin-catalog-backend": "^0.16.0", + "@backstage/integration": "^0.6.8", + "@backstage/plugin-catalog-backend": "^0.17.0", "@backstage/plugin-scaffolder-common": "^0.1.0", "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.2", "@gitbeaker/core": "^30.2.0", @@ -70,8 +70,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/test-utils": "^0.1.19", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 557190dbbc..9bf9b7fb90 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -39,10 +39,15 @@ export function createDebugLogAction() { title: 'List all files in the workspace, if true.', type: 'boolean', }, + extra: { + title: 'Extra info', + }, }, }, }, async handler(ctx) { + ctx.logger.info(JSON.stringify(ctx.input, null, 2)); + if (ctx.input?.message) { ctx.logStream.write(ctx.input.message); } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts index 7c7719cf68..a738afbe3f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts @@ -67,7 +67,19 @@ describe('fetchContent helper', () => { fetchUrl: '/etc/passwd', }), ).rejects.toThrow( - 'Fetch URL may not be absolute for file locations, /etc/passwd', + 'Relative path is not allowed to refer to a directory outside its parent', + ); + }); + + it('should reject relative file locations that exit the baseUrl', async () => { + await expect( + fetchContents({ + ...options, + baseUrl: 'file:///some/path', + fetchUrl: '../test', + }), + ).rejects.toThrow( + 'Relative path is not allowed to refer to a directory outside its parent', ); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts index 390ab7ec39..5625989417 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import { resolve as resolvePath, isAbsolute } from 'path'; -import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common'; +import { JsonValue } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { JsonValue } from '@backstage/config'; +import fs from 'fs-extra'; +import * as path from 'path'; export async function fetchContents({ reader, @@ -52,12 +52,7 @@ export async function fetchContents({ // We handle both file locations and url ones if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) { const basePath = baseUrl.slice('file://'.length); - if (isAbsolute(fetchUrl)) { - throw new InputError( - `Fetch URL may not be absolute for file locations, ${fetchUrl}`, - ); - } - const srcDir = resolvePath(basePath, '..', fetchUrl); + const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl); await fs.copy(srcDir, outputPath); } else { let readUrl; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts index 6d8324768e..0761f65f09 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts @@ -17,6 +17,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '../../createTemplateAction'; import { OctokitProvider } from './OctokitProvider'; import { emitterEventNames } from '@octokit/webhooks'; +import { assertError } from '@backstage/errors'; type ContentType = 'form' | 'json'; @@ -130,6 +131,7 @@ export function createGithubWebhookAction(options: { }); ctx.logger.info(`Webhook '${webhookUrl}' created successfully`); } catch (e) { + assertError(e); ctx.logger.warn( `Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`, ); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index 3907bc1d4e..eff65e329f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -19,6 +19,7 @@ import { PassThrough, Writable } from 'stream'; import { Logger } from 'winston'; import { Git } from '@backstage/backend-common'; import { Octokit } from '@octokit/rest'; +import { assertError } from '@backstage/errors'; export type RunCommandOptions = { command: string; @@ -152,6 +153,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ }, }); } catch (e) { + assertError(e); if ( e.message.includes( 'Upgrade to GitHub Pro or make this repository public to enable this feature', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index b32f9b5b3f..ad6a4a7857 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -22,6 +22,7 @@ import { getRepoSourceDirectory } from './util'; import { createTemplateAction } from '../../createTemplateAction'; import { Config } from '@backstage/config'; import { OctokitProvider } from '../github/OctokitProvider'; +import { assertError } from '@backstage/errors'; type Permission = 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; type Collaborator = { access: Permission; username: string }; @@ -198,6 +199,7 @@ export function createPublishGithubAction(options: { permission, }); } catch (e) { + assertError(e); ctx.logger.warn( `Skipping ${permission} access for ${team_slug}, ${e.message}`, ); @@ -213,6 +215,7 @@ export function createPublishGithubAction(options: { names: topics.map(t => t.toLowerCase()), }); } catch (e) { + assertError(e); ctx.logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`); } } @@ -250,6 +253,7 @@ export function createPublishGithubAction(options: { requireCodeOwnerReviews, }); } catch (e) { + assertError(e); ctx.logger.warn( `Skipping: default branch protection on '${newRepo.name}', ${e.message}`, ); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts index 2ba30bb3db..ed72b14a65 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts @@ -14,21 +14,20 @@ * limitations under the License. */ +import { getRootLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; import mockFs from 'mock-fs'; -import { Writable } from 'stream'; import os from 'os'; import { resolve as resolvePath } from 'path'; -import { - PullRequestCreator, - GithubPullRequestActionInput, - createPublishGithubPullRequestAction, - ClientFactoryInput, -} from './githubPullRequest'; +import { Writable } from 'stream'; import { ActionContext, TemplateAction } from '../../types'; -import { getRootLogger } from '@backstage/backend-common'; - -import { ScmIntegrations } from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; +import { + ClientFactoryInput, + createPublishGithubPullRequestAction, + GithubPullRequestActionInput, + PullRequestCreator, +} from './githubPullRequest'; const root = os.platform() === 'win32' ? 'C:\\root' : '/root'; const workspacePath = resolvePath(root, 'my-workspace'); @@ -174,6 +173,14 @@ describe('createPublishGithubPullRequestAction', () => { ], }); }); + + it('should not allow to use files outside of the workspace', async () => { + input.sourcePath = '../../test'; + + await expect(instance.handler(ctx)).rejects.toThrow( + 'Relative path is not allowed to refer to a directory outside its parent', + ); + }); }); describe('with repoUrl', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index e21ffc5cd0..253fd72b38 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -28,6 +28,7 @@ import { Octokit } from '@octokit/rest'; import { InputError, CustomErrorBase } from '@backstage/errors'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import globby from 'globby'; +import { resolveSafeChildPath } from '@backstage/backend-common'; class GithubResponseError extends CustomErrorBase {} @@ -183,7 +184,7 @@ export const createPublishGithubPullRequestAction = ({ const client = await clientFactory({ integrations, host, owner, repo }); const fileRoot = sourcePath - ? path.resolve(ctx.workspacePath, sourcePath) + ? resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath; const localFilePaths = await globby(['./**', './**/.*', '!.git'], { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts new file mode 100644 index 0000000000..f179289624 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import path from 'path'; +import { getRepoSourceDirectory } from './util'; + +describe('getRepoSourceDirectory', () => { + it('should return workspace root if no sub folder is given', () => { + expect( + getRepoSourceDirectory(path.join('/', 'var', 'workspace'), undefined), + ).toEqual(path.join('/', 'var', 'workspace')); + }); + + it('should return path in workspace if sub folder is given', () => { + expect( + getRepoSourceDirectory( + path.join('/', 'var', 'workspace'), + path.join('path', 'of', 'subfolder'), + ), + ).toEqual(path.join('/', 'var', 'workspace', 'path', 'of', 'subfolder')); + }); + + it('should not allow traversal outside the workspace root', () => { + // We have to construct the path manually here, as path.join would mitigate the path traversal + expect( + getRepoSourceDirectory( + path.join('/', 'var', 'workspace'), + `..${path.sep}secret`, + ), + ).toEqual(path.join('/', 'var', 'workspace', 'secret')); + expect( + getRepoSourceDirectory( + path.join('/', 'var', 'workspace'), + `.${path.sep}path${path.sep}..${path.sep}..${path.sep}secret`, + ), + ).toEqual(path.join('/', 'var', 'workspace', 'secret')); + expect( + getRepoSourceDirectory( + path.join('/', 'var', 'workspace'), + path.join('/', 'absolute', 'secret'), + ), + ).toEqual(path.join('/', 'var', 'workspace', 'absolute', 'secret')); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index dea180dc04..58ba5bfdd1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -15,6 +15,7 @@ */ import { InputError } from '@backstage/errors'; +import { isChildPath } from '@backstage/backend-common'; import { join as joinPath, normalize as normalizePath } from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -27,7 +28,11 @@ export const getRepoSourceDirectory = ( /^(\.\.(\/|\\|$))+/, '', ); - return joinPath(workspacePath, safeSuffix); + const path = joinPath(workspacePath, safeSuffix); + if (!isChildPath(workspacePath, path)) { + throw new Error('Invalid source path'); + } + return path; } return workspacePath; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts new file mode 100644 index 0000000000..d6d1b0cfd2 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts @@ -0,0 +1,413 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import mockFs from 'mock-fs'; +import * as winston from 'winston'; + +import { getVoidLogger } from '@backstage/backend-common'; +import { DefaultWorkflowRunner } from './DefaultWorkflowRunner'; +import { TemplateActionRegistry } from '../actions'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { Task, TaskSpec } from './types'; + +describe('DefaultWorkflowRunner', () => { + const logger = getVoidLogger(); + let actionRegistry = new TemplateActionRegistry(); + let runner: DefaultWorkflowRunner; + let fakeActionHandler: jest.Mock; + + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'github.com', token: 'token' }], + }, + }), + ); + + const createMockTaskWithSpec = (spec: TaskSpec): Task => ({ + spec, + complete: async () => {}, + done: false, + emitLog: async () => {}, + getWorkspaceName: () => Promise.resolve('test-workspace'), + }); + + beforeEach(() => { + winston.format.simple(); // put logform the require cache before mocking fs + mockFs({ + '/tmp': mockFs.directory(), + }); + + jest.resetAllMocks(); + actionRegistry = new TemplateActionRegistry(); + fakeActionHandler = jest.fn(); + + actionRegistry.register({ + id: 'jest-mock-action', + description: 'Mock action for testing', + handler: fakeActionHandler, + }); + + actionRegistry.register({ + id: 'jest-validated-action', + description: 'Mock action for testing', + handler: fakeActionHandler, + schema: { + input: { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'number', + }, + }, + }, + }, + }); + + actionRegistry.register({ + id: 'output-action', + description: 'Mock action for testing', + handler: async ctx => { + ctx.output('mock', 'backstage'); + ctx.output('shouldRun', true); + }, + }); + + runner = new DefaultWorkflowRunner({ + actionRegistry, + integrations, + workingDirectory: '/tmp', + logger, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should throw an error if the action does not exist', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: {}, + output: {}, + steps: [{ id: 'test', name: 'name', action: 'does-not-exist' }], + }); + + await expect(runner.execute(task)).rejects.toThrowError( + "Template action with ID 'does-not-exist' is not registered.", + ); + }); + + describe('validation', () => { + it('should throw an error if the action has a schema and the input does not match', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: {}, + output: {}, + steps: [{ id: 'test', name: 'name', action: 'jest-validated-action' }], + }); + + await expect(runner.execute(task)).rejects.toThrowError( + /Invalid input passed to action jest-validated-action, instance requires property \"foo\"/, + ); + }); + + it('should run the action when the validation passes', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: {}, + output: {}, + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-validated-action', + input: { foo: 1 }, + }, + ], + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledTimes(1); + }); + }); + + describe('conditionals', () => { + it('should execute steps conditionally', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { id: 'test', name: 'test', action: 'output-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'output-action', + if: '${{ steps.test.output.shouldRun }}', + }, + ], + output: { + result: '${{ steps.conditional.output.mock }}', + }, + parameters: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('backstage'); + }); + + it('should skips steps conditionally', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { id: 'test', name: 'test', action: 'output-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'output-action', + if: '${{ not steps.test.output.shouldRun}}', + }, + ], + output: { + result: '${{ steps.conditional.output.mock }}', + }, + parameters: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBeUndefined(); + }); + + it('should skips steps using the negating equals operator', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { id: 'test', name: 'test', action: 'output-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'output-action', + if: '${{ steps.test.output.mock !== "backstage"}}', + }, + ], + output: { + result: '${{ steps.conditional.output.mock }}', + }, + parameters: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBeUndefined(); + }); + }); + + describe('templating', () => { + it('should template the input to an action', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + foo: '${{parameters.input | lower }}', + }, + }, + ], + output: {}, + parameters: { + input: 'BACKSTAGE', + }, + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { foo: 'backstage' } }), + ); + }); + + it('should keep the original types for the input and not parse things that arent meant to be parsed', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + number: '${{parameters.number}}', + string: '${{parameters.string}}', + }, + }, + ], + output: {}, + parameters: { + number: 0, + string: '1', + }, + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { number: 0, string: '1' } }), + ); + }); + + it('should template complex values into the action', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + foo: '${{parameters.complex}}', + }, + }, + ], + output: {}, + parameters: { + complex: { bar: 'BACKSTAGE' }, + }, + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { foo: { bar: 'BACKSTAGE' } } }), + ); + }); + + it('supports really complex structures', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + foo: '${{parameters.complex.baz.something}}', + }, + }, + ], + output: {}, + parameters: { + complex: { + bar: 'BACKSTAGE', + baz: { something: 'nested', here: 'yas' }, + }, + }, + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { foo: 'nested' } }), + ); + }); + + it('supports numbers as first class too', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + foo: '${{parameters.complex.baz.number}}', + }, + }, + ], + output: {}, + parameters: { + complex: { + bar: 'BACKSTAGE', + baz: { number: 1 }, + }, + }, + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { foo: 1 } }), + ); + }); + + it('should template the output from simple actions', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'output-action', + input: {}, + }, + ], + output: { + foo: '${{steps.test.output.mock | upper}}', + }, + parameters: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.foo).toEqual('BACKSTAGE'); + }); + }); + + describe('filters', () => { + it('provides the parseRepoUrl filter', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'output-action', + input: {}, + }, + ], + output: { + foo: '${{ parameters.repoUrl | parseRepoUrl }}', + }, + parameters: { + repoUrl: 'github.com?repo=repo&owner=owner', + }, + }); + + const { output } = await runner.execute(task); + + expect(output.foo).toEqual({ + host: 'github.com', + owner: 'owner', + repo: 'repo', + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts new file mode 100644 index 0000000000..2ac309a1d4 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts @@ -0,0 +1,272 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { + Task, + TaskSpec, + TaskSpecV1beta3, + TaskStep, + WorkflowResponse, + WorkflowRunner, +} from './types'; +import * as winston from 'winston'; +import nunjucks from 'nunjucks'; +import fs from 'fs-extra'; +import path from 'path'; +import { JsonObject, JsonValue } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { PassThrough } from 'stream'; +import { isTruthy } from './helper'; +import { validate as validateJsonSchema } from 'jsonschema'; +import { parseRepoUrl } from '../actions/builtin/publish/util'; +import { TemplateActionRegistry } from '../actions'; + +type Options = { + workingDirectory: string; + actionRegistry: TemplateActionRegistry; + integrations: ScmIntegrations; + logger: winston.Logger; +}; + +type TemplateContext = { + parameters: JsonObject; + steps: { + [stepName: string]: { output: { [outputName: string]: JsonValue } }; + }; +}; + +const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { + return taskSpec.apiVersion === 'scaffolder.backstage.io/v1beta3'; +}; + +const createStepLogger = ({ task, step }: { task: Task; step: TaskStep }) => { + const metadata = { stepId: step.id }; + const taskLogger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: {}, + }); + + const streamLogger = new PassThrough(); + streamLogger.on('data', async data => { + const message = data.toString().trim(); + if (message?.length > 1) { + await task.emitLog(message, metadata); + } + }); + + taskLogger.add(new winston.transports.Stream({ stream: streamLogger })); + + return { taskLogger, streamLogger }; +}; + +export class DefaultWorkflowRunner implements WorkflowRunner { + private readonly nunjucks: nunjucks.Environment; + + private readonly nunjucksOptions: nunjucks.ConfigureOptions = { + autoescape: false, + tags: { + variableStart: '${{', + variableEnd: '}}', + }, + }; + + constructor(private readonly options: Options) { + this.nunjucks = nunjucks.configure(this.nunjucksOptions); + + // TODO(blam): let's work out how we can deprecate these. + // We shouldn't really need to be exposing these now we can deal with + // objects in the params block. + // Maybe we can expose a new RepoUrlPicker with secrets for V3 that provides an object already. + this.nunjucks.addFilter('parseRepoUrl', repoUrl => { + return parseRepoUrl(repoUrl, this.options.integrations); + }); + + this.nunjucks.addFilter('projectSlug', repoUrl => { + const { owner, repo } = parseRepoUrl(repoUrl, this.options.integrations); + return `${owner}/${repo}`; + }); + } + + private isSingleTemplateString(input: string) { + const { parser, nodes } = require('nunjucks'); + const parsed = parser.parse(input, {}, this.nunjucksOptions); + return ( + parsed.children.length === 1 && + !(parsed.children[0] instanceof nodes.TemplateData) + ); + } + + private render(input: T, context: TemplateContext): T { + return JSON.parse(JSON.stringify(input), (_key, value) => { + try { + if (typeof value === 'string') { + try { + if (this.isSingleTemplateString(value)) { + // Lets convert ${{ parameters.bob }} to ${{ (parameters.bob) | dump }} so we can keep the input type + const wrappedDumped = value.replace( + /\${{(.+)}}/g, + '${{ ( $1 ) | dump }}', + ); + + // Run the templating + const templated = this.nunjucks.renderString( + wrappedDumped, + context, + ); + + // If there's an empty string returned, then it's undefined + if (templated === '') { + return undefined; + } + + // Reparse the dumped string + return JSON.parse(templated); + } + } catch (ex) { + this.options.logger.error( + `Failed to parse template string: ${value} with error ${ex.message}`, + ); + } + + // Fallback to default behaviour + const templated = this.nunjucks.renderString(value, context); + + if (templated === '') { + return undefined; + } + + return templated; + } + } catch { + return value; + } + return value; + }); + } + + async execute(task: Task): Promise { + if (!isValidTaskSpec(task.spec)) { + throw new InputError( + 'Wrong template version executed with the workflow engine', + ); + } + const workspacePath = path.join( + this.options.workingDirectory, + await task.getWorkspaceName(), + ); + try { + await fs.ensureDir(workspacePath); + await task.emitLog( + `Starting up task with ${task.spec.steps.length} steps`, + ); + + const context: TemplateContext = { + parameters: task.spec.parameters, + steps: {}, + }; + + for (const step of task.spec.steps) { + try { + if (step.if) { + const ifResult = await this.render(step.if, context); + if (!isTruthy(ifResult)) { + await task.emitLog( + `Skipping step ${step.id} because it's if condition was false`, + { stepId: step.id, status: 'skipped' }, + ); + continue; + } + } + + await task.emitLog(`Beginning step ${step.name}`, { + stepId: step.id, + status: 'processing', + }); + + const action = this.options.actionRegistry.get(step.action); + const { taskLogger, streamLogger } = createStepLogger({ task, step }); + + const input = (step.input && this.render(step.input, context)) ?? {}; + + if (action.schema?.input) { + const validateResult = validateJsonSchema( + input, + action.schema.input, + ); + if (!validateResult.valid) { + const errors = validateResult.errors.join(', '); + throw new InputError( + `Invalid input passed to action ${action.id}, ${errors}`, + ); + } + } + + const tmpDirs = new Array(); + const stepOutput: { [outputName: string]: JsonValue } = {}; + + await action.handler({ + baseUrl: task.spec.baseUrl, + input, + logger: taskLogger, + logStream: streamLogger, + workspacePath, + createTemporaryDirectory: async () => { + const tmpDir = await fs.mkdtemp( + `${workspacePath}_step-${step.id}-`, + ); + tmpDirs.push(tmpDir); + return tmpDir; + }, + output(name: string, value: JsonValue) { + stepOutput[name] = value; + }, + }); + + // Remove all temporary directories that were created when executing the action + for (const tmpDir of tmpDirs) { + await fs.remove(tmpDir); + } + + context.steps[step.id] = { output: stepOutput }; + + await task.emitLog(`Finished step ${step.name}`, { + stepId: step.id, + status: 'completed', + }); + } catch (err) { + await task.emitLog(String(err.stack), { + stepId: step.id, + status: 'failed', + }); + throw err; + } + } + + const output = this.render(task.spec.output, context); + + return { output }; + } finally { + if (workspacePath) { + await fs.remove(workspacePath); + } + } + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts new file mode 100644 index 0000000000..2714f1b80c --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts @@ -0,0 +1,410 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import mockFs from 'mock-fs'; +import * as winston from 'winston'; +import { createTemplateAction, TemplateActionRegistry } from '../actions'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { LegacyWorkflowRunner } from './LegacyWorkflowRunner'; +import { Task, TaskSpec } from './types'; +import { RepoSpec } from '../actions/builtin/publish/util'; + +describe('LegacyWorkflowRunner', () => { + let runner: LegacyWorkflowRunner; + const logger = getVoidLogger(); + let actionRegistry = new TemplateActionRegistry(); + + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'github.com', token: 'token' }], + }, + }), + ); + + const createMockTaskWithSpec = (spec: TaskSpec): Task => ({ + spec, + complete: async () => {}, + done: false, + emitLog: async () => {}, + getWorkspaceName: () => Promise.resolve('test-workspace'), + }); + + beforeEach(() => { + winston.format.simple(); // put logform the require cache before mocking fs + mockFs({ + '/tmp': mockFs.directory(), + }); + + actionRegistry = new TemplateActionRegistry(); + actionRegistry.register({ + id: 'test-action', + handler: async ctx => { + ctx.output('testOutput', 'mockOutputData'); + ctx.output('badOutput', false); + }, + }); + + runner = new LegacyWorkflowRunner({ + actionRegistry, + integrations, + workingDirectory: '/tmp', + logger, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should fail when the action does not exist', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, + }); + + await expect(() => runner.execute(task)).rejects.toThrow( + /Template action with ID 'not-found-action' is not registered/, + ); + }); + + describe('templating', () => { + it('should template the output', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [{ id: 'test', name: 'test', action: 'test-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('mockOutputData'); + }); + + it('should template the input', async () => { + const inputAction = createTemplateAction<{ + name: string; + }>({ + id: 'test-input', + schema: { + input: { + type: 'object', + required: ['name'], + properties: { + name: { + title: 'name', + description: 'Enter name', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + if (ctx.input.name !== 'mockOutputData') { + throw new Error( + `expected name to be "mockOutputData" got ${ctx.input.name}`, + ); + } + }, + }); + actionRegistry.register(inputAction); + + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'test-input', + name: 'test-input', + action: 'test-input', + input: { + name: '{{ steps.test.output.testOutput }}', + }, + }, + ], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('mockOutputData'); + }); + }); + + describe('conditionals', () => { + it('should execute steps conditionally', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'test-action', + if: '{{ steps.test.output.testOutput }}', + }, + ], + output: { + result: '{{ steps.conditional.output.testOutput }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('mockOutputData'); + }); + + it('should execute steps conditionally with eq helper', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'test-action', + if: '{{ eq steps.test.output.testOutput "mockOutputData" }}', + }, + ], + output: { + result: '{{ steps.conditional.output.testOutput }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('mockOutputData'); + }); + + it('should skip test conditionally', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'test-action', + if: '{{ steps.test.output.badOutput }}', + }, + ], + output: { + result: '{{ steps.conditional.output.testOutput }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBeUndefined(); + }); + }); + + describe('parsing', () => { + it('should parse strings as objects if possible', async () => { + const inputAction = createTemplateAction<{ + address: { line1: string }; + list: string[]; + address2: string; + }>({ + id: 'test-input', + schema: { + input: { + type: 'object', + required: ['address'], + properties: { + address: { + title: 'address', + description: 'Enter name', + type: 'object', + properties: { + line1: { + type: 'string', + }, + }, + }, + address2: { + type: 'string', + }, + list: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, + }, + async handler(ctx) { + if (ctx.input.list.length !== 1) { + throw new Error( + `expected list to have length "1" got ${ctx.input.list.length}`, + ); + } + if (ctx.input.address.line1 !== 'line 1') { + throw new Error( + `expected address.line1 to be "line 1" got ${ctx.input.address.line1}`, + ); + } + + if (ctx.input.address2 !== '{"not valid"}') { + throw new Error( + `expected address2 to be "{"not valid"}" got ${ctx.input.address2}`, + ); + } + ctx.output('address', ctx.input.address.line1); + }, + }); + actionRegistry.register(inputAction); + + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { + id: 'test-input', + name: 'test-input', + action: 'test-input', + input: { + address: JSON.stringify({ line1: 'line 1' }), + list: JSON.stringify(['hey!']), + address2: '{"not valid"}', + }, + }, + ], + output: { + result: '{{ steps.test-input.output.address }}', + }, + values: {}, + }); + + const { output } = await runner.execute(task); + + expect(output.result).toBe('line 1'); + }); + + it('should provide a parseRepoUrl helper', async () => { + const inputAction = createTemplateAction<{ + destination: RepoSpec; + }>({ + id: 'test-input', + schema: { + input: { + type: 'object', + required: ['destination'], + properties: { + destination: { + title: 'destination', + type: 'object', + properties: { + repo: { + type: 'string', + }, + host: { + type: 'string', + }, + owner: { + type: 'string', + }, + organization: { + type: 'string', + }, + workspace: { + type: 'string', + }, + project: { + type: 'string', + }, + }, + }, + }, + }, + }, + async handler(ctx) { + ctx.output('host', ctx.input.destination.host); + ctx.output('repo', ctx.input.destination.repo); + + if (ctx.input.destination.owner) { + ctx.output('owner', ctx.input.destination.owner); + } + + if (ctx.input.destination.host !== 'github.com') { + throw new Error( + `expected host to be "github.com" got ${ctx.input.destination.host}`, + ); + } + + if (ctx.input.destination.repo !== 'repo') { + throw new Error( + `expected repo to be "repo" got ${ctx.input.destination.repo}`, + ); + } + + if ( + ctx.input.destination.owner && + ctx.input.destination.owner !== 'owner' + ) { + throw new Error( + `expected repo to be "owner" got ${ctx.input.destination.owner}`, + ); + } + }, + }); + actionRegistry.register(inputAction); + + const task = createMockTaskWithSpec({ + apiVersion: 'backstage.io/v1beta2', + steps: [ + { + id: 'test-input', + name: 'test-input', + action: 'test-input', + input: { + destination: '{{ parseRepoUrl parameters.repoUrl }}', + }, + }, + ], + output: { + host: '{{ steps.test-input.output.host }}', + repo: '{{ steps.test-input.output.repo }}', + owner: '{{ steps.test-input.output.owner }}', + }, + values: { + repoUrl: 'github.com?repo=repo&owner=owner', + }, + }); + + const { output } = await runner.execute(task); + + expect(output.host).toBe('github.com'); + expect(output.repo).toBe('repo'); + expect(output.owner).toBe('owner'); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts new file mode 100644 index 0000000000..97e3a4dec4 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts @@ -0,0 +1,309 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Task, + WorkflowRunner, + WorkflowResponse, + TaskSpecV1beta2, + TaskSpec, +} from './types'; +import * as Handlebars from 'handlebars'; +import { TemplateActionRegistry } from '..'; +import { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '../actions/builtin/publish/util'; +import { isTruthy } from './helper'; +import { PassThrough } from 'stream'; +import * as winston from 'winston'; +import { Logger } from 'winston'; +import path from 'path'; +import fs from 'fs-extra'; +import { validate as validateJsonSchema } from 'jsonschema'; +import { JsonObject, JsonValue } from '@backstage/config'; +import { InputError } from '@backstage/errors'; + +type Options = { + workingDirectory: string; + actionRegistry: TemplateActionRegistry; + integrations: ScmIntegrations; + logger: Logger; +}; + +const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta2 => + taskSpec.apiVersion === 'backstage.io/v1beta2'; +/** + * This is the legacy workflow runner, which supports handlebars. This entire implementation will be replaced + * with the default workflow runner interface in the future so this entire thing can go bye bye. + */ +export class LegacyWorkflowRunner implements WorkflowRunner { + private readonly handlebars: typeof Handlebars; + + constructor(private readonly options: Options) { + this.handlebars = Handlebars.create(); + + // TODO(blam): this should be a public facing API but it's a little + // scary right now, so we're going to lock it off like the component API is + // in the frontend until we can work out a nice way to do it. + this.handlebars.registerHelper('parseRepoUrl', repoUrl => { + return JSON.stringify(parseRepoUrl(repoUrl, this.options.integrations)); + }); + + this.handlebars.registerHelper('projectSlug', repoUrl => { + const { owner, repo } = parseRepoUrl(repoUrl, this.options.integrations); + return `${owner}/${repo}`; + }); + + this.handlebars.registerHelper('json', obj => JSON.stringify(obj)); + + this.handlebars.registerHelper('not', value => !isTruthy(value)); + + this.handlebars.registerHelper('eq', (a, b) => a === b); + } + + async execute(task: Task): Promise { + if (!isValidTaskSpec(task.spec)) { + throw new InputError(`Task spec is not a valid v1beta2 task spec`); + } + + const { actionRegistry } = this.options; + + const workspacePath = path.join( + this.options.workingDirectory, + await task.getWorkspaceName(), + ); + try { + await fs.ensureDir(workspacePath); + await task.emitLog( + `Starting up task with ${task.spec.steps.length} steps`, + ); + + const templateCtx: { + parameters: JsonObject; + steps: { + [stepName: string]: { output: { [outputName: string]: JsonValue } }; + }; + } = { parameters: task.spec.values, steps: {} }; + + for (const step of task.spec.steps) { + const metadata = { stepId: step.id }; + try { + const taskLogger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: {}, + }); + + const stream = new PassThrough(); + stream.on('data', async data => { + const message = data.toString().trim(); + if (message?.length > 1) { + await task.emitLog(message, metadata); + } + }); + + taskLogger.add(new winston.transports.Stream({ stream })); + + if (step.if !== undefined) { + // Support passing values like false to disable steps + let skip = !step.if; + + // Evaluate strings as handlebar templates + if (typeof step.if === 'string') { + const condition = JSON.parse( + JSON.stringify(step.if), + (_key, value) => { + if (typeof value === 'string') { + const templated = this.handlebars.compile(value, { + noEscape: true, + data: false, + preventIndent: true, + })(templateCtx); + + // If it's just an empty string, treat it as undefined + if (templated === '') { + return undefined; + } + + try { + return JSON.parse(templated); + } catch { + return templated; + } + } + + return value; + }, + ); + + skip = !isTruthy(condition); + } + + if (skip) { + await task.emitLog(`Skipped step ${step.name}`, { + ...metadata, + status: 'skipped', + }); + continue; + } + } + + await task.emitLog(`Beginning step ${step.name}`, { + ...metadata, + status: 'processing', + }); + + const action = actionRegistry.get(step.action); + if (!action) { + throw new Error(`Action '${step.action}' does not exist`); + } + + const input = + step.input && + JSON.parse(JSON.stringify(step.input), (_key, value) => { + if (typeof value === 'string') { + const templated = this.handlebars.compile(value, { + noEscape: true, + data: false, + preventIndent: true, + })(templateCtx); + + // If it smells like a JSON object then give it a parse as an object and if it fails return the string + if ( + (templated.startsWith('"') && templated.endsWith('"')) || + (templated.startsWith('{') && templated.endsWith('}')) || + (templated.startsWith('[') && templated.endsWith(']')) + ) { + try { + // Don't recursively JSON parse the values of this string. + // Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else + return JSON.parse(templated); + } catch { + return templated; + } + } + return templated; + } + + return value; + }); + + if (action.schema?.input) { + const validateResult = validateJsonSchema( + input, + action.schema.input, + ); + if (!validateResult.valid) { + const errors = validateResult.errors.join(', '); + throw new InputError( + `Invalid input passed to action ${action.id}, ${errors}`, + ); + } + } + + const stepOutputs: { [name: string]: JsonValue } = {}; + + // Keep track of all tmp dirs that are created by the action so we can remove them after + const tmpDirs = new Array(); + + this.options.logger.debug(`Running ${action.id} with input`, { + input: JSON.stringify(input, null, 2), + }); + + await action.handler({ + baseUrl: task.spec.baseUrl, + logger: taskLogger, + logStream: stream, + input, + token: task.secrets?.token, + workspacePath, + async createTemporaryDirectory() { + const tmpDir = await fs.mkdtemp( + `${workspacePath}_step-${step.id}-`, + ); + tmpDirs.push(tmpDir); + return tmpDir; + }, + output(name: string, value: JsonValue) { + stepOutputs[name] = value; + }, + }); + + // Remove all temporary directories that were created when executing the action + for (const tmpDir of tmpDirs) { + await fs.remove(tmpDir); + } + + templateCtx.steps[step.id] = { output: stepOutputs }; + + await task.emitLog(`Finished step ${step.name}`, { + ...metadata, + status: 'completed', + }); + } catch (error) { + await task.emitLog(String(error.stack), { + ...metadata, + status: 'failed', + }); + throw error; + } + } + + const output = JSON.parse( + JSON.stringify(task.spec.output), + (_key, value) => { + if (typeof value === 'string') { + const templated = this.handlebars.compile(value, { + noEscape: true, + data: false, + preventIndent: true, + })(templateCtx); + + // If it's just an empty string, treat it as undefined + if (templated === '') { + return undefined; + } + + // If it smells like a JSON object then give it a parse as an object and if it fails return the string + if ( + (templated.startsWith('"') && templated.endsWith('"')) || + (templated.startsWith('{') && templated.endsWith('}')) || + (templated.startsWith('[') && templated.endsWith(']')) + ) { + try { + // Don't recursively JSON parse the values of this string. + // Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else + return JSON.parse(templated); + } catch { + return templated; + } + } + return templated; + } + return value; + }, + ); + + return { output }; + } finally { + if (workspacePath) { + await fs.remove(workspacePath); + } + } + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 867e5bb127..4cd1066fc9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/config'; +import { assertError } from '@backstage/errors'; import { Logger } from 'winston'; import { CompletedTaskState, @@ -185,6 +186,7 @@ export class StorageTaskBroker implements TaskBroker { try { callback(undefined, result); } catch (error) { + assertError(error); callback(error, { events: [] }); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 2c274df7fc..87a0229b5d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -14,15 +14,13 @@ * limitations under the License. */ -import os from 'os'; import { getVoidLogger, DatabaseManager } from '@backstage/backend-common'; -import { ConfigReader, JsonObject } from '@backstage/config'; -import { createTemplateAction, TemplateActionRegistry } from '../actions'; -import { RepoSpec } from '../actions/builtin/publish/util'; +import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; import { TaskWorker } from './TaskWorker'; -import { ScmIntegrations } from '@backstage/integration'; +import { WorkflowRunner } from './types'; +import { LegacyWorkflowRunner } from './LegacyWorkflowRunner'; async function createStore(): Promise { const manager = DatabaseManager.fromConfig( @@ -40,138 +38,95 @@ async function createStore(): Promise { describe('TaskWorker', () => { let storage: DatabaseTaskStore; - let actionRegistry = new TemplateActionRegistry(); + const workflowRunner: WorkflowRunner = { + execute: jest.fn(), + } as unknown as WorkflowRunner; - const integrations = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - github: [{ host: 'github.com', token: 'token' }], - }, - }), - ); + const legacyWorkflowRunner: LegacyWorkflowRunner = { + execute: jest.fn(), + } as unknown as LegacyWorkflowRunner; beforeAll(async () => { storage = await createStore(); }); beforeEach(() => { - actionRegistry = new TemplateActionRegistry(); - actionRegistry.register({ - id: 'test-action', - handler: async ctx => { - ctx.output('testOutput', 'winning'); - ctx.output('badOutput', false); - }, - }); + jest.resetAllMocks(); }); const logger = getVoidLogger(); - it('should fail when action does not exist', async () => { + it('should call the legacy workflow runner when the apiVersion is not beta3', async () => { const broker = new StorageTaskBroker(storage, logger); const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, taskBroker: broker, - integrations, + runners: { + legacyWorkflowRunner, + workflowRunner, + }, }); - const { taskId } = await broker.dispatch({ + + await broker.dispatch({ + apiVersion: 'backstage.io/v1beta2', steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], output: { result: '{{ steps.test.output.testOutput }}', }, values: {}, }); + const task = await broker.claim(); await taskWorker.runOneTask(task); - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - expect((event?.body?.error as JsonObject)?.message).toBe( - "Template action with ID 'not-found-action' is not registered.", - ); + expect(legacyWorkflowRunner.execute).toHaveBeenCalled(); }); - it('should template output', async () => { + it('should call the default workflow runner when the apiVersion is beta3', async () => { const broker = new StorageTaskBroker(storage, logger); const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, taskBroker: broker, - integrations, + runners: { + legacyWorkflowRunner, + workflowRunner, + }, }); - const { taskId } = await broker.dispatch({ - steps: [{ id: 'test', name: 'test', action: 'test-action' }], + await broker.dispatch({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], output: { result: '{{ steps.test.output.testOutput }}', }, - values: {}, + parameters: {}, }); const task = await broker.claim(); await taskWorker.runOneTask(task); - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - expect((event?.body?.output as JsonObject).result).toBe('winning'); + expect(workflowRunner.execute).toHaveBeenCalled(); }); - it('should template input', async () => { - const inputAction = createTemplateAction<{ - name: string; - }>({ - id: 'test-input', - schema: { - input: { - type: 'object', - required: ['name'], - properties: { - name: { - title: 'name', - description: 'Enter name', - type: 'string', - }, - }, - }, - }, - async handler(ctx) { - if (ctx.input.name !== 'winning') { - throw new Error( - `expected name to be "winning" got ${ctx.input.name}`, - ); - } - }, + it('should save the output to the task', async () => { + (workflowRunner.execute as jest.Mock).mockResolvedValue({ + output: { testOutput: 'testmockoutput' }, }); - actionRegistry.register(inputAction); const broker = new StorageTaskBroker(storage, logger); const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, taskBroker: broker, - integrations, + runners: { + legacyWorkflowRunner, + workflowRunner, + }, }); const { taskId } = await broker.dispatch({ - steps: [ - { id: 'test', name: 'test', action: 'test-action' }, - { - id: 'test-input', - name: 'test-input', - action: 'test-input', - input: { - name: '{{ steps.test.output.testOutput }}', - }, - }, - ], + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], output: { result: '{{ steps.test.output.testOutput }}', }, - values: {}, + parameters: {}, }); const task = await broker.claim(); @@ -179,312 +134,6 @@ describe('TaskWorker', () => { const { events } = await storage.listEvents({ taskId }); const event = events.find(e => e.type === 'completion'); - expect((event?.body?.output as JsonObject).result).toBe('winning'); - }); - - it('should execute steps conditionally', async () => { - const broker = new StorageTaskBroker(storage, logger); - const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, - taskBroker: broker, - integrations, - }); - - const { taskId } = await broker.dispatch({ - steps: [ - { id: 'test', name: 'test', action: 'test-action' }, - { - id: 'conditional', - name: 'conditional', - action: 'test-action', - if: '{{ steps.test.output.testOutput }}', - }, - ], - output: { - result: '{{ steps.conditional.output.testOutput }}', - }, - values: {}, - }); - - const task = await broker.claim(); - await taskWorker.runOneTask(task); - - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - expect((event?.body?.output as JsonObject).result).toBe('winning'); - }); - - it('should execute steps conditionally with eq helper', async () => { - const broker = new StorageTaskBroker(storage, logger); - const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, - taskBroker: broker, - integrations, - }); - - const { taskId } = await broker.dispatch({ - steps: [ - { id: 'test', name: 'test', action: 'test-action' }, - { - id: 'conditional', - name: 'conditional', - action: 'test-action', - if: '{{ eq steps.test.output.testOutput "winning" }}', - }, - ], - output: { - result: '{{ steps.conditional.output.testOutput }}', - }, - values: {}, - }); - - const task = await broker.claim(); - await taskWorker.runOneTask(task); - - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - expect((event?.body?.output as JsonObject).result).toBe('winning'); - }); - - it('should skip steps conditionally', async () => { - const broker = new StorageTaskBroker(storage, logger); - const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, - taskBroker: broker, - integrations, - }); - - const { taskId } = await broker.dispatch({ - steps: [ - { id: 'test', name: 'test', action: 'test-action' }, - { - id: 'conditional', - name: 'conditional', - action: 'test-action', - if: '{{ steps.test.output.badOutput }}', - }, - ], - output: { - result: '{{ steps.conditional.output.testOutput }}', - }, - values: {}, - }); - - const task = await broker.claim(); - await taskWorker.runOneTask(task); - - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - expect((event?.body?.output as JsonObject).result).toBeUndefined(); - }); - - it('should parse strings as objects if possible', async () => { - const inputAction = createTemplateAction<{ - address: { line1: string }; - list: string[]; - address2: string; - }>({ - id: 'test-input', - schema: { - input: { - type: 'object', - required: ['address'], - properties: { - address: { - title: 'address', - description: 'Enter name', - type: 'object', - properties: { - line1: { - type: 'string', - }, - }, - }, - address2: { - type: 'string', - }, - list: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }, - }, - async handler(ctx) { - if (ctx.input.list.length !== 1) { - throw new Error( - `expected list to have length "1" got ${ctx.input.list.length}`, - ); - } - if (ctx.input.address.line1 !== 'line 1') { - throw new Error( - `expected address.line1 to be "line 1" got ${ctx.input.address.line1}`, - ); - } - - if (ctx.input.address2 !== '{"not valid"}') { - throw new Error( - `expected address2 to be "{"not valid"}" got ${ctx.input.address2}`, - ); - } - ctx.output('address', ctx.input.address.line1); - }, - }); - actionRegistry.register(inputAction); - - const broker = new StorageTaskBroker(storage, logger); - const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, - taskBroker: broker, - integrations, - }); - - const { taskId } = await broker.dispatch({ - steps: [ - { - id: 'test-input', - name: 'test-input', - action: 'test-input', - input: { - address: JSON.stringify({ line1: 'line 1' }), - list: JSON.stringify(['hey!']), - address2: '{"not valid"}', - }, - }, - ], - output: { - result: '{{ steps.test-input.output.address }}', - }, - values: {}, - }); - - const task = await broker.claim(); - await taskWorker.runOneTask(task); - - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - - expect((event?.body?.output as JsonObject).result).toBe('line 1'); - }); - - // TODO(blam): Can delete this test when we make the helpers a public API - it('should provide a repoUrlParse helper for the templates', async () => { - const inputAction = createTemplateAction<{ - destination: RepoSpec; - }>({ - id: 'test-input', - schema: { - input: { - type: 'object', - required: ['destination'], - properties: { - destination: { - title: 'destination', - type: 'object', - properties: { - repo: { - type: 'string', - }, - host: { - type: 'string', - }, - owner: { - type: 'string', - }, - organization: { - type: 'string', - }, - workspace: { - type: 'string', - }, - project: { - type: 'string', - }, - }, - }, - }, - }, - }, - async handler(ctx) { - ctx.output('host', ctx.input.destination.host); - ctx.output('repo', ctx.input.destination.repo); - - if (ctx.input.destination.owner) { - ctx.output('owner', ctx.input.destination.owner); - } - - if (ctx.input.destination.host !== 'github.com') { - throw new Error( - `expected host to be "github.com" got ${ctx.input.destination.host}`, - ); - } - - if (ctx.input.destination.repo !== 'repo') { - throw new Error( - `expected repo to be "repo" got ${ctx.input.destination.repo}`, - ); - } - - if ( - ctx.input.destination.owner && - ctx.input.destination.owner !== 'owner' - ) { - throw new Error( - `expected repo to be "owner" got ${ctx.input.destination.owner}`, - ); - } - }, - }); - actionRegistry.register(inputAction); - - const broker = new StorageTaskBroker(storage, logger); - const taskWorker = new TaskWorker({ - logger, - workingDirectory: os.tmpdir(), - actionRegistry, - taskBroker: broker, - integrations, - }); - - const { taskId } = await broker.dispatch({ - steps: [ - { - id: 'test-input', - name: 'test-input', - action: 'test-input', - input: { - destination: '{{ parseRepoUrl parameters.repoUrl }}', - }, - }, - ], - output: { - host: '{{ steps.test-input.output.host }}', - repo: '{{ steps.test-input.output.repo }}', - owner: '{{ steps.test-input.output.owner }}', - }, - values: { - repoUrl: 'github.com?repo=repo&owner=owner', - }, - }); - - const task = await broker.claim(); - await taskWorker.runOneTask(task); - - const { events } = await storage.listEvents({ taskId }); - const event = events.find(e => e.type === 'completion'); - - expect((event?.body?.output as JsonObject).host).toBe('github.com'); - expect((event?.body?.output as JsonObject).repo).toBe('repo'); - expect((event?.body?.output as JsonObject).owner).toBe('owner'); + expect(event?.body.output).toEqual({ testOutput: 'testmockoutput' }); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 819df56109..6a5a1f7843 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,54 +14,20 @@ * limitations under the License. */ -import { JsonObject, JsonValue } from '@backstage/config'; -import { InputError } from '@backstage/errors'; -import fs from 'fs-extra'; -import * as Handlebars from 'handlebars'; -import { validate as validateJsonSchema } from 'jsonschema'; -import path from 'path'; -import { PassThrough } from 'stream'; -import * as winston from 'winston'; -import { Logger } from 'winston'; -import { parseRepoUrl } from '../actions/builtin/publish/util'; -import { TemplateActionRegistry } from '../actions/TemplateActionRegistry'; -import { isTruthy } from './helper'; -import { Task, TaskBroker } from './types'; -import { ScmIntegrations } from '@backstage/integration'; +import { Task, TaskBroker, WorkflowRunner } from './types'; +import { LegacyWorkflowRunner } from './LegacyWorkflowRunner'; +import { assertError } from '@backstage/errors'; type Options = { - logger: Logger; taskBroker: TaskBroker; - workingDirectory: string; - actionRegistry: TemplateActionRegistry; - integrations: ScmIntegrations; + runners: { + legacyWorkflowRunner: LegacyWorkflowRunner; + workflowRunner: WorkflowRunner; + }; }; export class TaskWorker { - private readonly handlebars: typeof Handlebars; - - constructor(private readonly options: Options) { - this.handlebars = Handlebars.create(); - - // TODO(blam): this should be a public facing API but it's a little - // scary right now, so we're going to lock it off like the component API is - // in the frontend until we can work out a nice way to do it. - this.handlebars.registerHelper('parseRepoUrl', repoUrl => { - return JSON.stringify(parseRepoUrl(repoUrl, options.integrations)); - }); - - this.handlebars.registerHelper('projectSlug', repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl, options.integrations); - return `${owner}/${repo}`; - }); - - this.handlebars.registerHelper('json', obj => JSON.stringify(obj)); - - this.handlebars.registerHelper('not', value => !isTruthy(value)); - - this.handlebars.registerHelper('eq', (a, b) => a === b); - } - + constructor(private readonly options: Options) {} start() { (async () => { for (;;) { @@ -72,238 +38,18 @@ export class TaskWorker { } async runOneTask(task: Task) { - let workspacePath: string | undefined = undefined; try { - const { actionRegistry } = this.options; - - workspacePath = path.join( - this.options.workingDirectory, - await task.getWorkspaceName(), - ); - await fs.ensureDir(workspacePath); - await task.emitLog( - `Starting up task with ${task.spec.steps.length} steps`, - ); - - const templateCtx: { - parameters: JsonObject; - steps: { - [stepName: string]: { output: { [outputName: string]: JsonValue } }; - }; - } = { parameters: task.spec.values, steps: {} }; - - for (const step of task.spec.steps) { - const metadata = { stepId: step.id }; - try { - const taskLogger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp(), - winston.format.simple(), - ), - defaultMeta: {}, - }); - - const stream = new PassThrough(); - stream.on('data', async data => { - const message = data.toString().trim(); - if (message?.length > 1) { - await task.emitLog(message, metadata); - } - }); - - taskLogger.add(new winston.transports.Stream({ stream })); - - if (step.if !== undefined) { - // Support passing values like false to disable steps - let skip = !step.if; - - // Evaluate strings as handlebar templates - if (typeof step.if === 'string') { - const condition = JSON.parse( - JSON.stringify(step.if), - (_key, value) => { - if (typeof value === 'string') { - const templated = this.handlebars.compile(value, { - noEscape: true, - data: false, - preventIndent: true, - })(templateCtx); - - // If it's just an empty string, treat it as undefined - if (templated === '') { - return undefined; - } - - try { - return JSON.parse(templated); - } catch { - return templated; - } - } - - return value; - }, - ); - - skip = !isTruthy(condition); - } - - if (skip) { - await task.emitLog(`Skipped step ${step.name}`, { - ...metadata, - status: 'skipped', - }); - continue; - } - } - - await task.emitLog(`Beginning step ${step.name}`, { - ...metadata, - status: 'processing', - }); - - const action = actionRegistry.get(step.action); - if (!action) { - throw new Error(`Action '${step.action}' does not exist`); - } - - const input = - step.input && - JSON.parse(JSON.stringify(step.input), (_key, value) => { - if (typeof value === 'string') { - const templated = this.handlebars.compile(value, { - noEscape: true, - data: false, - preventIndent: true, - })(templateCtx); - - // If it smells like a JSON object then give it a parse as an object and if it fails return the string - if ( - (templated.startsWith('"') && templated.endsWith('"')) || - (templated.startsWith('{') && templated.endsWith('}')) || - (templated.startsWith('[') && templated.endsWith(']')) - ) { - try { - // Don't recursively JSON parse the values of this string. - // Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else - return JSON.parse(templated); - } catch { - return templated; - } - } - return templated; - } - - return value; - }); - - if (action.schema?.input) { - const validateResult = validateJsonSchema( - input, - action.schema.input, - ); - if (!validateResult.valid) { - const errors = validateResult.errors.join(', '); - throw new InputError( - `Invalid input passed to action ${action.id}, ${errors}`, - ); - } - } - - const stepOutputs: { [name: string]: JsonValue } = {}; - - // Keep track of all tmp dirs that are created by the action so we can remove them after - const tmpDirs = new Array(); - - this.options.logger.debug(`Running ${action.id} with input`, { - input: JSON.stringify(input, null, 2), - }); - - await action.handler({ - baseUrl: task.spec.baseUrl, - logger: taskLogger, - logStream: stream, - input, - token: task.secrets?.token, - workspacePath, - async createTemporaryDirectory() { - const tmpDir = await fs.mkdtemp( - `${workspacePath}_step-${step.id}-`, - ); - tmpDirs.push(tmpDir); - return tmpDir; - }, - output(name: string, value: JsonValue) { - stepOutputs[name] = value; - }, - }); - - // Remove all temporary directories that were created when executing the action - for (const tmpDir of tmpDirs) { - await fs.remove(tmpDir); - } - - templateCtx.steps[step.id] = { output: stepOutputs }; - - await task.emitLog(`Finished step ${step.name}`, { - ...metadata, - status: 'completed', - }); - } catch (error) { - await task.emitLog(String(error.stack), { - ...metadata, - status: 'failed', - }); - throw error; - } - } - - const output = JSON.parse( - JSON.stringify(task.spec.output), - (_key, value) => { - if (typeof value === 'string') { - const templated = this.handlebars.compile(value, { - noEscape: true, - data: false, - preventIndent: true, - })(templateCtx); - - // If it's just an empty string, treat it as undefined - if (templated === '') { - return undefined; - } - - // If it smells like a JSON object then give it a parse as an object and if it fails return the string - if ( - (templated.startsWith('"') && templated.endsWith('"')) || - (templated.startsWith('{') && templated.endsWith('}')) || - (templated.startsWith('[') && templated.endsWith(']')) - ) { - try { - // Don't recursively JSON parse the values of this string. - // Shouldn't need to, don't want to encourage the use of returning handlebars from somewhere else - return JSON.parse(templated); - } catch { - return templated; - } - } - return templated; - } - return value; - }, - ); + const { output } = + task.spec.apiVersion === 'scaffolder.backstage.io/v1beta3' + ? await this.options.runners.workflowRunner.execute(task) + : await this.options.runners.legacyWorkflowRunner.execute(task); await task.complete('completed', { output }); } catch (error) { + assertError(error); await task.complete('failed', { error: { name: error.name, message: error.message }, }); - } finally { - if (workspacePath) { - await fs.remove(workspacePath); - } } } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 3b1805f117..cd93404165 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -43,7 +43,8 @@ export type DbTaskEventRow = { createdAt: string; }; -export type TaskSpec = { +export interface TaskSpecV1beta2 { + apiVersion: 'backstage.io/v1beta2'; baseUrl?: string; values: JsonObject; steps: Array<{ @@ -54,7 +55,24 @@ export type TaskSpec = { if?: string | boolean; }>; output: { [name: string]: string }; -}; +} + +export interface TaskStep { + id: string; + name: string; + action: string; + input?: JsonObject; + if?: string | boolean; +} +export interface TaskSpecV1beta3 { + apiVersion: 'scaffolder.backstage.io/v1beta3'; + baseUrl?: string; + parameters: JsonObject; + steps: TaskStep[]; + output: { [name: string]: JsonValue }; +} + +export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; export type TaskSecrets = { token: string | undefined; @@ -122,3 +140,8 @@ export interface TaskStore { after, }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }>; } + +export type WorkflowResponse = { output: { [key: string]: JsonValue } }; +export interface WorkflowRunner { + execute(task: Task): Promise; +} diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 7125e9df93..6741ab3379 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -21,6 +21,7 @@ import { SOURCE_LOCATION_ANNOTATION, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { assertError } from '@backstage/errors'; import fs from 'fs-extra'; import os from 'os'; import { Logger } from 'winston'; @@ -39,6 +40,7 @@ export async function getWorkingDirectory( await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK); logger.info(`using working directory: ${workingDirectory}`); } catch (err) { + assertError(err); logger.error( `working directory ${workingDirectory} ${ err.code === 'ENOENT' ? 'does not exist' : 'is not writable' diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index f2a9c5b708..f8ba99336f 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -35,9 +35,14 @@ import { import { InputError, NotFoundError } from '@backstage/errors'; import { CatalogApi } from '@backstage/catalog-client'; import { TemplateEntityV1beta2, Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '../scaffolder/actions'; import { createBuiltinActions } from '../scaffolder/actions/builtin/createBuiltinActions'; +import { LegacyWorkflowRunner } from '../scaffolder/tasks/LegacyWorkflowRunner'; +import { DefaultWorkflowRunner } from '../scaffolder/tasks/DefaultWorkflowRunner'; +import { TaskSpec } from '../scaffolder/tasks/types'; export interface RouterOptions { logger: Logger; @@ -50,10 +55,13 @@ export interface RouterOptions { containerRunner: ContainerRunner; } -function isBeta2Template( - entity: TemplateEntityV1beta2, -): entity is TemplateEntityV1beta2 { - return entity.apiVersion === 'backstage.io/v1beta2'; +function isSupportedTemplate( + entity: TemplateEntityV1beta2 | TemplateEntityV1beta3, +) { + return ( + entity.apiVersion === 'backstage.io/v1beta2' || + entity.apiVersion === 'scaffolder.backstage.io/v1beta3' + ); } export async function createRouter( @@ -83,14 +91,28 @@ export async function createRouter( ); const taskBroker = new StorageTaskBroker(databaseTaskStore, logger); const actionRegistry = new TemplateActionRegistry(); + const legacyWorkflowRunner = new LegacyWorkflowRunner({ + logger, + actionRegistry, + integrations, + workingDirectory, + }); + + const workflowRunner = new DefaultWorkflowRunner({ + actionRegistry, + integrations, + logger, + workingDirectory, + }); + const workers = []; for (let i = 0; i < (taskWorkers || 1); i++) { const worker = new TaskWorker({ - logger, taskBroker, - actionRegistry, - workingDirectory, - integrations, + runners: { + legacyWorkflowRunner, + workflowRunner, + }, }); workers.push(worker); } @@ -128,7 +150,7 @@ export async function createRouter( const template = await entityClient.findTemplate(name, { token: getBearerToken(req.headers.authorization), }); - if (isBeta2Template(template)) { + if (isSupportedTemplate(template)) { const parameters = [template.spec.parameters ?? []].flat(); res.json({ title: template.metadata.title ?? template.metadata.name, @@ -164,9 +186,9 @@ export async function createRouter( token, }); - let taskSpec; + let taskSpec: TaskSpec; - if (isBeta2Template(template)) { + if (isSupportedTemplate(template)) { for (const parameters of [template.spec.parameters ?? []].flat()) { const result = validate(values, parameters); @@ -178,16 +200,30 @@ export async function createRouter( const baseUrl = getEntityBaseUrl(template); - taskSpec = { - baseUrl, - values, - steps: template.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), - output: template.spec.output ?? {}, - }; + taskSpec = + template.apiVersion === 'backstage.io/v1beta2' + ? { + apiVersion: template.apiVersion, + baseUrl, + values, + steps: template.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })), + output: template.spec.output ?? {}, + } + : { + apiVersion: template.apiVersion, + baseUrl, + parameters: values, + steps: template.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })), + output: template.spec.output ?? {}, + }; } else { throw new InputError( `Unsupported apiVersion field in schema entity, ${ diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 1d5510f704..119ed36685 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder +## 0.11.8 + +### Patch Changes + +- 0366c9b667: Introduce a `useStarredEntity` hook to check if a single entity is starred. + It provides a more efficient implementation compared to the `useStarredEntities` hook, because the rendering is only triggered if the selected entity is starred, not if _any_ entity is starred. +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/integration-react@0.1.12 + ## 0.11.7 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 7acfe55692..6f472e8ab1 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "0.11.7", + "version": "0.11.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,13 +34,13 @@ "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.2", - "@backstage/integration": "^0.6.7", - "@backstage/integration-react": "^0.1.11", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/integration": "^0.6.8", + "@backstage/integration-react": "^0.1.12", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -64,10 +64,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder/src/components/FavouriteTemplate/FavouriteTemplate.tsx b/plugins/scaffolder/src/components/FavouriteTemplate/FavouriteTemplate.tsx index d158315130..2c55ffdf88 100644 --- a/plugins/scaffolder/src/components/FavouriteTemplate/FavouriteTemplate.tsx +++ b/plugins/scaffolder/src/components/FavouriteTemplate/FavouriteTemplate.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ -import React, { ComponentProps, useMemo } from 'react'; -import { useStarredEntities } from '@backstage/plugin-catalog-react'; -import { IconButton, makeStyles, Tooltip, withStyles } from '@material-ui/core'; -import StarBorder from '@material-ui/icons/StarBorder'; -import Star from '@material-ui/icons/Star'; import { Entity } from '@backstage/catalog-model'; +import { useStarredEntity } from '@backstage/plugin-catalog-react'; +import { IconButton, makeStyles, Tooltip, withStyles } from '@material-ui/core'; +import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; +import React, { ComponentProps } from 'react'; type Props = ComponentProps & { entity: Entity }; @@ -56,20 +56,18 @@ export const favouriteTemplateIcon = (isStarred: boolean) => */ export const FavouriteTemplate = (props: Props) => { const classes = useStyles(); - const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); - const isStarred = useMemo( - () => isStarredEntity(props.entity), - [isStarredEntity, props.entity], + const { toggleStarredEntity, isStarredEntity } = useStarredEntity( + props.entity, ); return ( toggleStarredEntity(props.entity)} + onClick={() => toggleStarredEntity()} > - - {favouriteTemplateIcon(isStarred)} + + {favouriteTemplateIcon(isStarredEntity)} ); diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 89e33f9784..f92a021e5c 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search +## 0.4.15 + +### Patch Changes + +- 56bd537256: SearchBar component to accept optional placeholder prop +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.4.14 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index d763d06774..7f6858f42b 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "0.4.14", + "version": "0.4.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,12 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.5.2", + "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/search-common": "^0.2.0", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 1d1a5028f3..3ed90a617f 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-sentry +## 0.3.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.24 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 366a609b06..6703332aae 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.3.24", + "version": "0.3.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts index 0e7f5b784d..e634650f96 100644 --- a/plugins/sentry/src/api/production-api.ts +++ b/plugins/sentry/src/api/production-api.ts @@ -32,7 +32,7 @@ export class ProductionSentryApi implements SentryApi { const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sentry/api`; const response = await fetch( - `${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsFor=${statsFor}`, + `${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsPeriod=${statsFor}`, ); if (response.status >= 400 && response.status < 600) { diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index a5a53a45dc..8e454f53e3 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-shortcuts +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.11 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 0ae27eec9e..b4bfcd9a95 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.1.11", + "version": "0.1.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -37,10 +37,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 917a2ec0e2..8886ca3645 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-sonarqube +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.5 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 4d835045fa..6dc132a366 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.2.5", + "version": "0.2.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index f78686838c..4bd0b9e9bc 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-splunk-on-call +## 0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.13 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 7841d99ee1..4d853be4dd 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.13", + "version": "0.3.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index b5ab486b79..8e7b5875a9 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-radar +## 0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.4.10 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 7ad89495e6..79d5fc1487 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.4.10", + "version": "0.4.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -45,10 +45,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/src/sample.ts b/plugins/tech-radar/src/sample.ts index 9c89a1e73a..2b674f35c8 100644 --- a/plugins/tech-radar/src/sample.ts +++ b/plugins/tech-radar/src/sample.ts @@ -51,7 +51,7 @@ entries.push({ title: 'JavaScript', quadrant: 'languages', description: - 'Excepteur **sint** occaecat *cupidatat* non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', + 'Excepteur **sint** occaecat *cupidatat* non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\n```ts\nconst x = "3";\n```\n', }); entries.push({ timeline: [ diff --git a/plugins/techdocs-backend/src/DocsBuilder/builder.ts b/plugins/techdocs-backend/src/DocsBuilder/builder.ts index 5327062233..821b3478a0 100644 --- a/plugins/techdocs-backend/src/DocsBuilder/builder.ts +++ b/plugins/techdocs-backend/src/DocsBuilder/builder.ts @@ -19,7 +19,7 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { NotModifiedError } from '@backstage/errors'; +import { assertError, isError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBase, @@ -131,7 +131,7 @@ export class DocsBuilder { preparedDir = preparerResponse.preparedDir; newEtag = preparerResponse.etag; } catch (err) { - if (err instanceof NotModifiedError) { + if (isError(err) && err.name === 'NotModifiedError') { // No need to prepare anymore since cache is valid. // Set last check happened to now new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated(); @@ -142,7 +142,7 @@ export class DocsBuilder { ); return false; } - throw new Error(err.message); + throw err; } this.logger.info( @@ -195,6 +195,7 @@ export class DocsBuilder { // Not a blocker hence no need to await this. fs.remove(preparedDir); } catch (error) { + assertError(error); this.logger.debug(`Error removing prepared directory ${error.message}`); } } @@ -221,6 +222,7 @@ export class DocsBuilder { `Removing generated directory ${outputDir} since the site has been published`, ); } catch (error) { + assertError(error); this.logger.debug(`Error removing generated directory ${error.message}`); } diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index 0df22bee53..006cfdd073 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -58,6 +58,7 @@ const expectedEntities: Entity[] = [ apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { + title: 'Test Entity with Docs!', name: 'test-entity-with-docs', description: 'Documented description', annotations: { @@ -133,6 +134,7 @@ describe('DefaultTechDocsCollator with legacyPathCasing configuration', () => { location: `/docs/default/Component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, text: mockSearchDocIndex.docs[idx].text, namespace: 'default', + entityTitle: entity!.metadata.title, componentType: entity!.spec!.type, lifecycle: entity!.spec!.lifecycle, owner: '', @@ -177,6 +179,7 @@ describe('DefaultTechDocsCollator', () => { location: `/docs/default/component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, text: mockSearchDocIndex.docs[idx].text, namespace: 'default', + entityTitle: entity!.metadata.title, componentType: entity!.spec!.type, lifecycle: entity!.spec!.lifecycle, owner: '', diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index cb7d40206b..c0a96b7862 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -93,6 +93,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { 'namespace', 'metadata.annotations', 'metadata.name', + 'metadata.title', 'metadata.namespace', 'spec.type', 'spec.lifecycle', @@ -130,6 +131,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { }), path: doc.location, ...entityInfo, + entityTitle: entity.metadata.title, componentType: entity.spec?.type?.toString() || 'other', lifecycle: (entity.spec?.lifecycle as string) || '', owner: diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts index 463b895564..6f977dcb47 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { NotFoundError } from '@backstage/errors'; +import { assertError, NotFoundError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBuilder, @@ -113,6 +113,7 @@ export class DocsSynchronizer { return; } } catch (e) { + assertError(e); const msg = `Failed to build the docs page: ${e.message}`; taskLogger.error(msg); this.logger.error(msg, e); diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 4eb78f8c26..1fbdc22195 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs +## 0.12.2 + +### Patch Changes + +- 76fef740fe: Refactored `` component internals to support future extensibility. +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/plugin-catalog@0.7.1 + - @backstage/integration@0.6.8 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + - @backstage/plugin-search@0.4.15 + - @backstage/integration-react@0.1.12 + ## 0.12.1 ### Patch Changes diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index b091d37743..c6b16be0b3 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -145,6 +145,11 @@ export type DocsTableRow = { // @public (undocumented) export const EmbeddedDocsRouter: (_props: Props_2) => JSX.Element; +// Warning: (ae-missing-release-tag) "EntityListDocsGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityListDocsGrid: () => JSX.Element; + // Warning: (ae-missing-release-tag) "EntityListDocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7a61144192..762c52efd9 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "0.12.1", + "version": "0.12.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,15 +34,15 @@ "dependencies": { "@backstage/catalog-model": "^0.9.4", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.6.7", - "@backstage/integration-react": "^0.1.11", - "@backstage/plugin-catalog": "^0.7.0", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/plugin-search": "^0.4.14", - "@backstage/theme": "^0.2.10", + "@backstage/integration": "^0.6.8", + "@backstage/integration-react": "^0.1.12", + "@backstage/plugin-catalog": "^0.7.1", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/plugin-search": "^0.4.15", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -61,10 +61,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx index 22dc86f440..807914c5ad 100644 --- a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx +++ b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx @@ -33,6 +33,17 @@ const validResult = { lifecycle: 'production', }; +const validResultWithTitle = { + location: 'https://backstage.io/docs', + title: 'Documentation', + text: 'Backstage is an open-source developer portal that puts the developer experience first.', + kind: 'library', + namespace: '', + name: 'Backstage', + entityTitle: 'Backstage App', + lifecycle: 'production', +}; + describe('DocsResultListItem test', () => { it('should render search doc passed in', async () => { const { findByText } = render(); @@ -59,4 +70,14 @@ describe('DocsResultListItem test', () => { ), ).toBeInTheDocument(); }); + + it('should use entity title if defined', async () => { + const { findByText } = render( + , + ); + + expect( + await findByText('Documentation | Backstage App docs'), + ).toBeInTheDocument(); + }); }); diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx index 54e6110b88..03cf011c32 100644 --- a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx +++ b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx @@ -47,7 +47,11 @@ export const DocsResultListItem = ({ { const actual = jest.requireActual('@backstage/plugin-catalog-react'); @@ -63,10 +67,13 @@ describe('TechDocs Home', () => { }, }); + const storageApi = MockStorageApi.create(); + const apiRegistry = ApiRegistry.from([ [catalogApiRef, mockCatalogApi], [configApiRef, configApi], - [storageApiRef, MockStorageApi.create()], + [storageApiRef, storageApi], + [starredEntitiesApiRef, new DefaultStarredEntitiesApi({ storageApi })], ]); it('should render a TechDocs home page', async () => { diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx b/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx index 8b0e31326c..e52636a54a 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx +++ b/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ +import { configApiRef } from '@backstage/core-plugin-api'; import { wrapInTestApp } from '@backstage/test-utils'; import { render } from '@testing-library/react'; import React from 'react'; -import { configApiRef } from '@backstage/core-plugin-api'; -import { DocsCardGrid } from './DocsCardGrid'; import { rootDocsRouteRef } from '../../routes'; +import { DocsCardGrid } from './DocsCardGrid'; // Hacky way to mock a specific boolean config value. const getOptionalBooleanMock = jest.fn().mockReturnValue(false); @@ -126,4 +126,32 @@ describe('Entity Docs Card Grid', () => { '/techdocs/SomeNamespace/TestKind/testName', ); }); + + it('should render entity title if available', async () => { + const { findByText } = render( + wrapInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ), + ); + expect(await findByText('TestTitle')).toBeInTheDocument(); + }); }); diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/DocsCardGrid.tsx index badc338b1b..e84454ae54 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/DocsCardGrid.tsx @@ -49,7 +49,9 @@ export const DocsCardGrid = ({ : entities.map((entity, index: number) => ( - + {entity.metadata.description} diff --git a/plugins/techdocs/src/home/components/EntityListDocsGrid.tsx b/plugins/techdocs/src/home/components/EntityListDocsGrid.tsx new file mode 100644 index 0000000000..8c04a29f66 --- /dev/null +++ b/plugins/techdocs/src/home/components/EntityListDocsGrid.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + CodeSnippet, + Progress, + WarningPanel, +} from '@backstage/core-components'; +import { useEntityListProvider } from '@backstage/plugin-catalog-react'; +import React from 'react'; +import { DocsCardGrid } from './DocsCardGrid'; + +export const EntityListDocsGrid = () => { + const { loading, error, entities } = useEntityListProvider(); + + if (error) { + return ( + + + + ); + } + + if (loading || !entities) { + return ; + } + + entities.sort((a, b) => + (a.metadata.title ?? a.metadata.name).localeCompare( + b.metadata.title ?? b.metadata.name, + ), + ); + + return ; +}; diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index 53dd0fd909..d9d627cf8d 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -export { EntityListDocsTable } from './EntityListDocsTable'; export { DefaultTechDocsHome } from './DefaultTechDocsHome'; +export { EntityListDocsGrid } from './EntityListDocsGrid'; +export { EntityListDocsTable } from './EntityListDocsTable'; +export type { PanelType } from './TechDocsCustomHome'; export { TechDocsPageWrapper } from './TechDocsPageWrapper'; export { TechDocsPicker } from './TechDocsPicker'; -export type { PanelType } from './TechDocsCustomHome'; export type { DocsTableRow } from './types'; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 50e25c5582..a6a95e9264 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -24,14 +24,15 @@ export * from './api'; export { techdocsApiRef, techdocsStorageApiRef } from './api'; export type { TechDocsApi, TechDocsStorageApi } from './api'; export { TechDocsClient, TechDocsStorageClient } from './client'; -export type { DocsTableRow, PanelType } from './home/components'; +export * from './components/DocsResultListItem'; export { - EntityListDocsTable, DefaultTechDocsHome, + EntityListDocsGrid, + EntityListDocsTable, TechDocsPageWrapper, TechDocsPicker, } from './home/components'; -export * from './components/DocsResultListItem'; +export type { DocsTableRow, PanelType } from './home/components'; export { DocsCardGrid, DocsTable, @@ -44,4 +45,4 @@ export { TechDocsReaderPage, } from './plugin'; export * from './reader'; -export { EmbeddedDocsRouter, Router, isTechDocsAvailable } from './Router'; +export { EmbeddedDocsRouter, isTechDocsAvailable, Router } from './Router'; diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 826271a7c1..7307961772 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -14,22 +14,26 @@ * limitations under the License. */ +import React, { + PropsWithChildren, + ComponentType, + createContext, + useContext, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Grid, makeStyles, useTheme } from '@material-ui/core'; + import { EntityName } from '@backstage/catalog-model'; -import { Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { BackstageTheme } from '@backstage/theme'; -import { - Button, - CircularProgress, - Grid, - makeStyles, - useTheme, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; + import { techdocsStorageApiRef } from '../../api'; + import { addBaseUrl, addGitFeedbackLink, @@ -40,26 +44,21 @@ import { rewriteDocLinks, sanitizeDOM, simplifyMkdocsFooter, + scrollIntoAnchor, transform as transformer, } from '../transformers'; -import { TechDocsBuildLogs } from './TechDocsBuildLogs'; -import { TechDocsNotFound } from './TechDocsNotFound'; + import { TechDocsSearch } from './TechDocsSearch'; +import { TechDocsStateIndicator } from './TechDocsStateIndicator'; import { useReaderState } from './useReaderState'; type Props = { entityRef: EntityName; - onReady?: () => void; withSearch?: boolean; + onReady?: () => void; }; const useStyles = makeStyles(theme => ({ - message: { - // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet - // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break - wordBreak: 'break-word', - overflowWrap: 'anywhere', - }, searchBar: { marginLeft: '20rem', maxWidth: 'calc(100% - 20rem * 2 - 3rem)', @@ -71,44 +70,86 @@ const useStyles = makeStyles(theme => ({ }, })); -export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { - const { kind, namespace, name } = entityRef; - const theme = useTheme(); - const classes = useStyles(); +type TechDocsReaderValue = ReturnType; - const { - state, - path, - contentReload, - content: rawPage, - contentErrorMessage, - syncErrorMessage, - buildLog, - } = useReaderState(kind, namespace, name, useParams()['*']); +const TechDocsReaderContext = createContext( + {} as TechDocsReaderValue, +); - const techdocsStorageApi = useApi(techdocsStorageApiRef); - const [sidebars, setSidebars] = useState(); +const TechDocsReaderProvider = ({ children }: PropsWithChildren<{}>) => { + const { namespace = '', kind = '', name = '', '*': path } = useParams(); + const value = useReaderState(kind, namespace, name, path); + return ( + + {children} + + ); +}; + +/** + * Note: this HOC is currently being exported so that we can rapidly + * iterate on alternative implementations that extend core + * functionality. There is no guarantee that this HOC will continue to be + * exported by the package in the future! + * + * todo: Make public or stop exporting (ctrl+f "altReaderExperiments") + * @internal + */ +export const withTechDocsReaderProvider = + (Component: ComponentType) => + (props: T) => + ( + + + + ); + +/** + * Note: this hook is currently being exported so that we can rapidly + * iterate on alternative implementations that extend core + * functionality. There is no guarantee that this hook will continue to be + * exported by the package in the future! + * + * todo: Make public or stop exporting (ctrl+f "altReaderExperiments") + * @internal + */ +export const useTechDocsReader = () => useContext(TechDocsReaderContext); + +/** + * Hook that encapsulates the behavior of getting raw HTML and applying + * transforms to it in order to make it function at a basic level in the + * Backstage UI. + * + * Note: this hook is currently being exported so that we can rapidly iterate + * on alternative implementations that extend core functionality. + * There is no guarantee that this hook will continue to be exported by the + * package in the future! + * + * todo: Make public or stop exporting (see others: "altReaderExperiments") + * @internal + */ +export const useTechDocsReaderDom = (): Element | null => { const navigate = useNavigate(); - const shadowDomRef = useRef(null); + const theme = useTheme(); + const techdocsStorageApi = useApi(techdocsStorageApiRef); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); + const { namespace = '', kind = '', name = '' } = useParams(); + const { state, path, content: rawPage } = useTechDocsReader(); + + const [sidebars, setSidebars] = useState(); + const [dom, setDom] = useState(null); const updateSidebarPosition = useCallback(() => { - if (!!shadowDomRef.current && !!sidebars) { - const shadowDiv: HTMLElement = shadowDomRef.current!; - const shadowRoot = - shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' }); - const mdTabs = shadowRoot.querySelector('.md-container > .md-tabs'); - sidebars!.forEach(sidebar => { - const newTop = Math.max( - shadowDomRef.current!.getBoundingClientRect().top, - 0, - ); - sidebar.style.top = mdTabs - ? `${newTop + mdTabs.getBoundingClientRect().height}px` - : `${newTop}px`; - }); - } - }, [shadowDomRef, sidebars]); + if (!dom || !sidebars) return; + // set sidebar height so they don't initially render in wrong position + const mdTabs = dom.querySelector('.md-container > .md-tabs'); + sidebars.forEach(sidebar => { + const newTop = Math.max(dom.getBoundingClientRect().top, 0); + sidebar.style.top = mdTabs + ? `${newTop + mdTabs.getBoundingClientRect().height}px` + : `${newTop}px`; + }); + }, [dom, sidebars]); useEffect(() => { updateSidebarPosition(); @@ -141,62 +182,62 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { addGitFeedbackLink(scmIntegrationsApi), injectCss({ css: ` - body { - font-family: ${theme.typography.fontFamily}; - --md-text-color: ${theme.palette.text.primary}; - --md-text-link-color: ${theme.palette.primary.main}; + body { + font-family: ${theme.typography.fontFamily}; + --md-text-color: ${theme.palette.text.primary}; + --md-text-link-color: ${theme.palette.primary.main}; - --md-code-fg-color: ${theme.palette.text.primary}; - --md-code-bg-color: ${theme.palette.background.paper}; - } - .md-main__inner { margin-top: 0; } - .md-sidebar { position: fixed; bottom: 100px; width: 20rem; } - .md-sidebar--secondary { right: 2rem; } - .md-content { margin-bottom: 50px } - .md-footer { position: fixed; bottom: 0px; width: 100vw; } - .md-footer-nav__link { width: 20rem;} - .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); } - .md-typeset { font-size: 1rem; } - .md-nav { font-size: 1rem; } - .md-grid { max-width: 90vw; margin: 0 } - .md-typeset table:not([class]) { - font-size: 1rem; - border: 1px solid ${theme.palette.text.primary}; - border-bottom: none; - border-collapse: collapse; - } - .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { - border-bottom: 1px solid ${theme.palette.text.primary}; - } - .md-typeset table:not([class]) th { font-weight: bold; } - .md-typeset .admonition, .md-typeset details { - font-size: 1rem; - } - @media screen and (max-width: 76.1875em) { - .md-nav { - background-color: ${theme.palette.background.default}; - transition: none !important + --md-code-fg-color: ${theme.palette.text.primary}; + --md-code-bg-color: ${theme.palette.background.paper}; } - .md-sidebar--secondary { display: none; } - .md-sidebar--primary { left: 72px; width: 10rem } - .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); } - .md-content__inner { font-size: 0.9rem } - .md-footer { - position: static; - margin-left: 10rem; - width: calc(100% - 10rem); + .md-main__inner { margin-top: 0; } + .md-sidebar { position: fixed; bottom: 100px; width: 20rem; } + .md-sidebar--secondary { right: 2rem; } + .md-content { margin-bottom: 50px } + .md-footer { position: fixed; bottom: 0px; width: 100vw; } + .md-footer-nav__link { width: 20rem;} + .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); } + .md-typeset { font-size: 1rem; } + .md-nav { font-size: 1rem; } + .md-grid { max-width: 90vw; margin: 0 } + .md-typeset table:not([class]) { + font-size: 1rem; + border: 1px solid ${theme.palette.text.primary}; + border-bottom: none; + border-collapse: collapse; } - .md-nav--primary .md-nav__title { - white-space: normal; - height: auto; - line-height: 1rem; - cursor: auto; + .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { + border-bottom: 1px solid ${theme.palette.text.primary}; } - .md-nav--primary > .md-nav__title [for="none"] { - padding-top: 0; + .md-typeset table:not([class]) th { font-weight: bold; } + .md-typeset .admonition, .md-typeset details { + font-size: 1rem; } - } - `, + @media screen and (max-width: 76.1875em) { + .md-nav { + background-color: ${theme.palette.background.default}; + transition: none !important + } + .md-sidebar--secondary { display: none; } + .md-sidebar--primary { left: 72px; width: 10rem } + .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); } + .md-content__inner { font-size: 0.9rem } + .md-footer { + position: static; + margin-left: 10rem; + width: calc(100% - 10rem); + } + .md-nav--primary .md-nav__title { + white-space: normal; + height: auto; + line-height: 1rem; + cursor: auto; + } + .md-nav--primary > .md-nav__title [for="none"] { + padding-top: 0; + } + } + `, }), injectCss({ // Disable CSS animations on link colors as they lead to issues in dark @@ -204,20 +245,20 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { // is always an animation from light to dark mode when navigation // between pages. css: ` - .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { - transition: none; - } - `, + .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { + transition: none; + } + `, }), injectCss({ // Properly style code blocks. css: ` - .md-typeset pre > code::-webkit-scrollbar-thumb { - background-color: hsla(0, 0%, 0%, 0.32); - } - .md-typeset pre > code::-webkit-scrollbar-thumb:hover { - background-color: hsla(0, 0%, 0%, 0.87); - } + .md-typeset pre > code::-webkit-scrollbar-thumb { + background-color: hsla(0, 0%, 0%, 0.32); + } + .md-typeset pre > code::-webkit-scrollbar-thumb:hover { + background-color: hsla(0, 0%, 0%, 0.87); + } `, }), injectCss({ @@ -230,30 +271,30 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { // example in the backend), we have to copy from main*.css and modify // them. css: ` - :host { - --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); - } - :host { - --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); - } - :host { - --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); - } - :host { - --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); - } + :host { + --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); + } + :host { + --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); + } + :host { + --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); + } + :host { + --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + } `, }), ]), @@ -273,29 +314,18 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { // a function that performs transformations that are executed after adding it to the DOM const postRender = useCallback( - async (shadowRoot: ShadowRoot) => - transformer(shadowRoot.children[0], [ - dom => { - setTimeout(() => { - // Scoll to the desired anchor on initial navigation - if (window.location.hash) { - const hash = window.location.hash.slice(1); - shadowRoot?.getElementById(hash)?.scrollIntoView(); - } - }, 200); - return dom; - }, + async (transformedElement: Element) => + transformer(transformedElement, [ + scrollIntoAnchor(), addLinkClickListener({ baseUrl: window.location.origin, onClick: (_: MouseEvent, url: string) => { const parsedUrl = new URL(url); - if (parsedUrl.hash) { navigate(`${parsedUrl.pathname}${parsedUrl.hash}`); - // Scroll to hash if it's on the current page - shadowRoot - ?.getElementById(parsedUrl.hash.slice(1)) + transformedElement + ?.querySelector(`#${parsedUrl.hash.slice(1)}`) ?.scrollIntoView(); } else { navigate(parsedUrl.pathname); @@ -304,28 +334,18 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { }), onCssReady({ docStorageUrl: await techdocsStorageApi.getApiOrigin(), - onLoading: (dom: Element) => { - (dom as HTMLElement).style.setProperty('opacity', '0'); + onLoading: (renderedElement: Element) => { + (renderedElement as HTMLElement).style.setProperty('opacity', '0'); }, - onLoaded: (dom: Element) => { - (dom as HTMLElement).style.removeProperty('opacity'); + onLoaded: (renderedElement: Element) => { + (renderedElement as HTMLElement).style.removeProperty('opacity'); // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism) - (dom as HTMLElement) + renderedElement .querySelector('.md-nav__title') ?.removeAttribute('for'); - const sideDivs: HTMLElement[] = Array.from( - shadowRoot!.querySelectorAll('.md-sidebar'), + setSidebars( + Array.from(renderedElement.querySelectorAll('.md-sidebar')), ); - setSidebars(sideDivs); - // set sidebar height so they don't initially render in wrong position - const docTopPosition = (dom as HTMLElement).getBoundingClientRect() - .top; - const mdTabs = dom.querySelector('.md-container > .md-tabs'); - sideDivs!.forEach(sidebar => { - sidebar.style.top = mdTabs - ? `${docTopPosition + mdTabs.getBoundingClientRect().height}px` - : `${docTopPosition}px`; - }); }, }), ]), @@ -333,23 +353,14 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { ); useEffect(() => { - if (!rawPage || !shadowDomRef.current) { - // clear the shadow dom if no content is available - if (shadowDomRef.current?.shadowRoot) { - shadowDomRef.current.shadowRoot.innerHTML = ''; - } - return () => {}; - } - if (onReady) { - onReady(); - } + if (!rawPage) return () => {}; // if false, there is already a newer execution of this effect let shouldReplaceContent = true; // Pre-render - preRender(rawPage, path).then(async transformedElement => { - if (!transformedElement?.innerHTML) { + preRender(rawPage, path).then(async preTransformedDomElement => { + if (!preTransformedDomElement?.innerHTML) { return; // An unexpected error occurred } @@ -358,94 +369,56 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { return; } - const shadowDiv: HTMLElement = shadowDomRef.current!; - const shadowRoot = - shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' }); - Array.from(shadowRoot.children).forEach(child => - shadowRoot.removeChild(child), - ); - shadowRoot.appendChild(transformedElement); - // Scroll to top after render window.scroll({ top: 0 }); // Post-render - await postRender(shadowRoot); + const postTransformedDomElement = await postRender( + preTransformedDomElement, + ); + setDom(postTransformedDomElement as HTMLElement); }); // cancel this execution return () => { shouldReplaceContent = false; }; - }, [onReady, path, postRender, preRender, rawPage]); + }, [rawPage, path, preRender, postRender]); + + return dom; +}; + +const TheReader = ({ + entityRef, + onReady = () => {}, + withSearch = true, +}: Props) => { + const classes = useStyles(); + const dom = useTechDocsReaderDom(); + const shadowDomRef = useRef(null); + + const onReadyRef = useRef<() => void>(onReady); + useEffect(() => { + onReadyRef.current = onReady; + }, [onReady]); + + useEffect(() => { + if (!dom || !shadowDomRef.current) return; + const shadowDiv = shadowDomRef.current; + const shadowRoot = + shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' }); + Array.from(shadowRoot.children).forEach(child => + shadowRoot.removeChild(child), + ); + shadowRoot.appendChild(dom); + onReadyRef.current(); + + // this hook must ONLY be triggered by a changed dom + }, [dom]); return ( <> - {state === 'CHECKING' && } - {state === 'INITIAL_BUILD' && ( - } - action={} - > - Documentation is accessed for the first time and is being prepared. - The subsequent loads are much faster. - - )} - {state === 'CONTENT_STALE_REFRESHING' && ( - } - action={} - > - A newer version of this documentation is being prepared and will be - available shortly. - - )} - {state === 'CONTENT_STALE_READY' && ( - contentReload()}> - Refresh - - } - > - A newer version of this documentation is now available, please refresh - to view. - - )} - {state === 'CONTENT_STALE_ERROR' && ( - } - classes={{ message: classes.message }} - > - Building a newer version of this documentation failed.{' '} - {syncErrorMessage} - - )} - {state === 'CONTENT_NOT_FOUND' && ( - <> - {syncErrorMessage && ( - } - classes={{ message: classes.message }} - > - Building a newer version of this documentation failed.{' '} - {syncErrorMessage} - - )} - - - )} - + {withSearch && shadowDomRef?.current?.shadowRoot?.innerHTML && ( @@ -455,3 +428,17 @@ export const Reader = ({ entityRef, onReady, withSearch = true }: Props) => { ); }; + +export const Reader = ({ + entityRef, + onReady = () => {}, + withSearch = true, +}: Props) => ( + + + +); diff --git a/plugins/techdocs/src/reader/components/TechDocsSearch.tsx b/plugins/techdocs/src/reader/components/TechDocsSearch.tsx index 5d330100ce..64229e6620 100644 --- a/plugins/techdocs/src/reader/components/TechDocsSearch.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsSearch.tsx @@ -61,7 +61,7 @@ export const buildInitialFilters = ( return legacyPaths ? entityId : Object.entries(entityId).reduce((acc, [key, value]) => { - return { ...acc, [key]: value.toLocaleLowerCase('en-US') }; + return { ...acc, [key]: value?.toLocaleLowerCase('en-US') }; }, {}); }; @@ -175,7 +175,7 @@ const TechDocsSearchBar = ({ ); }; -const TechDocsSearch = (props: TechDocsSearchProps) => { +export const TechDocsSearch = (props: TechDocsSearchProps) => { const configApi = useApi(configApiRef); const legacyPaths = configApi.getOptionalBoolean( 'techdocs.legacyUseCaseSensitiveTripletPaths', @@ -192,4 +192,3 @@ const TechDocsSearch = (props: TechDocsSearchProps) => { ); }; -export { TechDocsSearch }; diff --git a/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx new file mode 100644 index 0000000000..e70c17d14a --- /dev/null +++ b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Progress } from '@backstage/core-components'; +import { CircularProgress, Button, makeStyles } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; + +import { TechDocsBuildLogs } from './TechDocsBuildLogs'; +import { TechDocsNotFound } from './TechDocsNotFound'; +import { useTechDocsReader } from './Reader'; + +const useStyles = makeStyles(() => ({ + message: { + // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet + // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break + wordBreak: 'break-word', + overflowWrap: 'anywhere', + }, +})); + +/** + * Note: this component is currently being exported so that we can rapidly + * iterate on alternative implementations that extend core + * functionality. There is no guarantee that this component will continue to be + * exported by the package in the future! + * + * todo: Make public or stop exporting (ctrl+f "altReaderExperiments") + * @internal + */ +export const TechDocsStateIndicator = () => { + let StateAlert: JSX.Element | null = null; + const classes = useStyles(); + + const { + state, + contentReload, + contentErrorMessage, + syncErrorMessage, + buildLog, + } = useTechDocsReader(); + + const ReaderProgress = state === 'CHECKING' ? : null; + + if (state === 'INITIAL_BUILD') { + StateAlert = ( + } + action={} + > + Documentation is accessed for the first time and is being prepared. The + subsequent loads are much faster. + + ); + } + + if (state === 'CONTENT_STALE_REFRESHING') { + StateAlert = ( + } + action={} + > + A newer version of this documentation is being prepared and will be + available shortly. + + ); + } + + if (state === 'CONTENT_STALE_READY') { + StateAlert = ( + contentReload()}> + Refresh + + } + > + A newer version of this documentation is now available, please refresh + to view. + + ); + } + + if (state === 'CONTENT_STALE_ERROR') { + StateAlert = ( + } + classes={{ message: classes.message }} + > + Building a newer version of this documentation failed.{' '} + {syncErrorMessage} + + ); + } + + if (state === 'CONTENT_NOT_FOUND') { + StateAlert = ( + <> + {syncErrorMessage && ( + } + classes={{ message: classes.message }} + > + Building a newer version of this documentation failed.{' '} + {syncErrorMessage} + + )} + + + ); + } + + return ( + <> + {ReaderProgress} + {StateAlert} + + ); +}; diff --git a/plugins/techdocs/src/reader/components/index.ts b/plugins/techdocs/src/reader/components/index.ts index 4f6d151e5d..99a0a7a3bc 100644 --- a/plugins/techdocs/src/reader/components/index.ts +++ b/plugins/techdocs/src/reader/components/index.ts @@ -17,3 +17,18 @@ export * from './Reader'; export * from './TechDocsPage'; export * from './TechDocsPageHeader'; +export * from './TechDocsStateIndicator'; + +/** + * Note: this component is currently being exported so that we can rapidly + * iterate on alternative implementations that extend core + * functionality. There is no guarantee that this component will continue to be + * exported by the package in the future! + * + * Why is this comment here instead of above the component itself? It's a + * workaround for some kind of bug in @microsoft/api-extractor. + * + * todo: Make public or stop exporting (ctrl+f "altReaderExperiments") + * @internal + */ +export { TechDocsSearch } from './TechDocsSearch'; diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index cd1ad6511c..530bea2092 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -23,4 +23,5 @@ export * from './simplifyMkdocsFooter'; export * from './onCssReady'; export * from './sanitizeDOM'; export * from './injectCss'; +export * from './scrollIntoAnchor'; export * from './transformer'; diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts new file mode 100644 index 0000000000..b06762399b --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { scrollIntoAnchor } from '../transformers'; + +jest.useFakeTimers(); + +describe('scrollIntoAnchor', () => { + const transformer = scrollIntoAnchor(); + const dom = { querySelector: jest.fn() }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('does nothing if there is no anchor element', async () => { + transformer(dom as unknown as Element); + jest.advanceTimersByTime(200); + expect(dom.querySelector).not.toHaveBeenCalled(); + }); + + it('scroll to the hash anchor element', async () => { + const scrollIntoView = jest.fn(); + dom.querySelector.mockReturnValue({ scrollIntoView }); + const hash = '#hash'; + window.location.hash = hash; + transformer(dom as unknown as Element); + jest.advanceTimersByTime(200); + expect(dom.querySelector).toHaveBeenCalledWith(`#${hash.slice(1)}`); + expect(scrollIntoView).toHaveBeenCalledWith(); + window.location.hash = ''; + }); +}); diff --git a/packages/techdocs-common/src/types.d.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts similarity index 56% rename from packages/techdocs-common/src/types.d.ts rename to plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts index e085b95851..0fa977865e 100644 --- a/packages/techdocs-common/src/types.d.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,17 @@ * limitations under the License. */ -interface IStorageFilesMock { - emptyFiles(): void; - fileExists(targetPath: string): boolean; - readFile(targetPath: string): Buffer; - writeFile(targetPath: string, sourcePath: string): void; - writeFile(targetPath: string, sourceBuffer: Buffer): void; - writeFile(targetPath: string, source: string | Buffer): void; -} +import type { Transformer } from './transformer'; + +export const scrollIntoAnchor = (): Transformer => { + return dom => { + setTimeout(() => { + // Scroll to the desired anchor on initial navigation + if (window.location.hash) { + const hash = window.location.hash.slice(1); + dom?.querySelector(`#${hash}`)?.scrollIntoView(); + } + }, 200); + return dom; + }; +}; diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 880df42ca1..99c33c425e 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-todo +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.6.0 + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.1.13 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 03a955fda3..1c1af5e360 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.1.13", + "version": "0.1.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,11 +28,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.4", - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.5.2", - "@backstage/theme": "^0.2.10", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index b16e327f02..0cc6dbd72e 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-user-settings +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.3.8 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index c8f105080a..44f33df9c8 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.3.8", + "version": "0.3.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,11 +43,11 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", "@backstage/core-plugin-api": "^0.1.10", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx index 982911821b..8f6e60e289 100644 --- a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx @@ -20,7 +20,7 @@ import { UserSettingsPinToggle } from './UserSettingsPinToggle'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; export const UserSettingsAppearanceCard = () => ( - + diff --git a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx index f79b1084c5..93bb32c5db 100644 --- a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx @@ -24,7 +24,7 @@ export const UserSettingsProfileCard = () => { const { profile, displayName } = useUserProfile(); return ( - + diff --git a/plugins/welcome/CHANGELOG.md b/plugins/welcome/CHANGELOG.md deleted file mode 100644 index d6952c886a..0000000000 --- a/plugins/welcome/CHANGELOG.md +++ /dev/null @@ -1,204 +0,0 @@ -# @backstage/plugin-welcome - -## 0.3.7 - -### Patch Changes - -- 81a41ec249: Added a `name` key to all extensions in order to improve Analytics API metadata. -- Updated dependencies - - @backstage/core-components@0.6.1 - - @backstage/core-plugin-api@0.1.10 - -## 0.3.6 - -### Patch Changes - -- Updated dependencies - - @backstage/core-plugin-api@0.1.9 - - @backstage/core-components@0.6.0 - -## 0.3.5 - -### Patch Changes - -- Updated dependencies - - @backstage/core-components@0.5.0 - -## 0.3.4 - -### Patch Changes - -- 9f1362dcc1: Upgrade `@material-ui/lab` to `4.0.0-alpha.57`. -- Updated dependencies - - @backstage/core-components@0.4.2 - - @backstage/core-plugin-api@0.1.8 - -## 0.3.3 - -### Patch Changes - -- Updated dependencies - - @backstage/core-components@0.4.0 - -## 0.3.2 - -### Patch Changes - -- Updated dependencies - - @backstage/core-components@0.3.0 - - @backstage/core-plugin-api@0.1.5 - -## 0.3.1 - -### Patch Changes - -- 9d40fcb1e: - Bumping `material-ui/core` version to at least `4.12.2` as they made some breaking changes in later versions which broke `Pagination` of the `Table`. - - Switching out `material-table` to `@material-table/core` for support for the later versions of `material-ui/core` - - This causes a minor API change to `@backstage/core-components` as the interface for `Table` re-exports the `prop` from the underlying `Table` components. - - `onChangeRowsPerPage` has been renamed to `onRowsPerPageChange` - - `onChangePage` has been renamed to `onPageChange` - - Migration guide is here: https://material-table-core.com/docs/breaking-changes -- Updated dependencies - - @backstage/core-components@0.2.0 - - @backstage/core-plugin-api@0.1.4 - - @backstage/theme@0.2.9 - -## 0.3.0 - -### Minor Changes - -- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. - -### Patch Changes - -- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. -- Updated dependencies - - @backstage/core-plugin-api@0.1.3 - -## 0.2.8 - -### Patch Changes - -- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 -- 675a569a9: chore: bump `react-use` dependency in all packages -- Updated dependencies [062bbf90f] -- Updated dependencies [889d89b6e] -- Updated dependencies [3f988cb63] -- Updated dependencies [675a569a9] - - @backstage/core@0.7.9 - -## 0.2.7 - -### Patch Changes - -- f85851837: Australian Greeting -- Updated dependencies [94da20976] -- Updated dependencies [d8cc7e67a] -- Updated dependencies [99fbef232] -- Updated dependencies [ab07d77f6] -- Updated dependencies [931b21a12] -- Updated dependencies [937ed39ce] -- Updated dependencies [9a9e7a42f] -- Updated dependencies [50ce875a0] - - @backstage/core@0.7.6 - - @backstage/theme@0.2.6 - -## 0.2.6 - -### Patch Changes - -- Updated dependencies [40c0fdbaa] -- Updated dependencies [2a271d89e] -- Updated dependencies [bece09057] -- Updated dependencies [169f48deb] -- Updated dependencies [8a1566719] -- Updated dependencies [4c049a1a1] - - @backstage/core@0.7.0 - -## 0.2.5 - -### Patch Changes - -- 8dfdec613: Migrated to new composability API, exporting the plugin as `welcomePlugin` and the page as `WelcomePage`. -- Updated dependencies [12ece98cd] -- Updated dependencies [d82246867] -- Updated dependencies [c810082ae] -- Updated dependencies [5fa3bdb55] -- Updated dependencies [21e624ba9] -- Updated dependencies [da9f53c60] -- Updated dependencies [32c95605f] -- Updated dependencies [54c7d02f7] - - @backstage/core@0.6.0 - - @backstage/theme@0.2.3 - -## 0.2.4 - -### Patch Changes - -- Updated dependencies [efd6ef753] -- Updated dependencies [a187b8ad0] - - @backstage/core@0.5.0 - -## 0.2.3 - -### Patch Changes - -- Updated dependencies [2527628e1] -- Updated dependencies [1c69d4716] -- Updated dependencies [1665ae8bb] -- Updated dependencies [04f26f88d] -- Updated dependencies [ff243ce96] - - @backstage/core@0.4.0 - - @backstage/theme@0.2.2 - -## 0.2.2 - -### Patch Changes - -- 303c5ea17: Refactor route registration to remove deprecating code - -## 0.2.1 - -### Patch Changes - -- Updated dependencies [7b37d65fd] -- Updated dependencies [4aca74e08] -- Updated dependencies [e8f69ba93] -- Updated dependencies [0c0798f08] -- Updated dependencies [0c0798f08] -- Updated dependencies [199237d2f] -- Updated dependencies [6627b626f] -- Updated dependencies [4577e377b] - - @backstage/core@0.3.0 - - @backstage/theme@0.2.1 - -## 0.2.0 - -### Minor Changes - -- 28edd7d29: Create backend plugin through CLI - -### Patch Changes - -- Updated dependencies [819a70229] -- Updated dependencies [ae5983387] -- Updated dependencies [0d4459c08] -- Updated dependencies [482b6313d] -- Updated dependencies [1c60f716e] -- Updated dependencies [144c66d50] -- Updated dependencies [b79017fd3] -- Updated dependencies [6d97d2d6f] -- Updated dependencies [93a3fa3ae] -- Updated dependencies [782f3b354] -- Updated dependencies [2713f28f4] -- Updated dependencies [406015b0d] -- Updated dependencies [82759d3e4] -- Updated dependencies [ac8d5d5c7] -- Updated dependencies [ebca83d48] -- Updated dependencies [aca79334f] -- Updated dependencies [c0d5242a0] -- Updated dependencies [3beb5c9fc] -- Updated dependencies [754e31db5] -- Updated dependencies [1611c6dbc] - - @backstage/core@0.2.0 - - @backstage/theme@0.2.0 diff --git a/plugins/welcome/README.md b/plugins/welcome/README.md deleted file mode 100644 index c40a760c7c..0000000000 --- a/plugins/welcome/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Title - -> This plugin formerly provided the "homepage" of early alpha versions of Backstage. -> It is no longer in active use and in most cases can be removed from your instance. - -Welcome to the welcome plugin! - -## Sub-section 1 - -## Sub-section 2 diff --git a/plugins/welcome/api-report.md b/plugins/welcome/api-report.md deleted file mode 100644 index 02a46615f3..0000000000 --- a/plugins/welcome/api-report.md +++ /dev/null @@ -1,21 +0,0 @@ -## API Report File for "@backstage/plugin-welcome" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -/// - -import { BackstagePlugin } from '@backstage/core-plugin-api'; - -// Warning: (ae-missing-release-tag) "WelcomePage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const WelcomePage: () => JSX.Element; - -// Warning: (ae-missing-release-tag) "welcomePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -const welcomePlugin: BackstagePlugin<{}, {}>; -export { welcomePlugin as plugin }; -export { welcomePlugin }; -``` diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx deleted file mode 100644 index 22be84a4bf..0000000000 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { renderInTestApp } from '@backstage/test-utils'; -import { lightTheme } from '@backstage/theme'; -import { ThemeProvider } from '@material-ui/core'; -import React from 'react'; -import WelcomePage from './WelcomePage'; - -import { - ApiProvider, - ApiRegistry, - ConfigReader, -} from '@backstage/core-app-api'; -import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; - -describe('WelcomePage', () => { - it('should render', async () => { - const { baseElement } = await renderInTestApp( - - - - - , - ); - expect(baseElement).toBeInTheDocument(); - }); -}); diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx deleted file mode 100644 index ce45b1b031..0000000000 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { - Typography, - Grid, - List, - ListItem, - ListItemText, - Link, -} from '@material-ui/core'; - -import { - Content, - InfoCard, - Header, - HomepageTimer, - Page, - ContentHeader, - SupportButton, - WarningPanel, -} from '@backstage/core-components'; - -import { useApi, configApiRef } from '@backstage/core-plugin-api'; - -const WelcomePage = () => { - const appTitle = - useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; - const profile = { givenName: '' }; - - return ( - -
- -
- - - - - - - - - We created Backstage about 4 years ago. While Spotify's - internal version of Backstage has had the benefit of time to - mature and evolve, the first iteration of our open source - version is still nascent. We are envisioning three phases of - the project and we have already begun work on various aspects - of these phases. The best way to keep track of the progress is - through the  - - Milestones - - . - - } - /> - - - - - You now have a running instance of Backstage!  - - 🎉 - -  Let's make sure you get the most out of this platform by - walking you through the basics. - - - The Setup - - - Backstage is put together from three base concepts: the core, - the app and the plugins. - - - - - - - - - - - - - - Build Your Plugins - - - We suggest you either check out the documentation for{' '} - - creating a plugin - {' '} - or have a look in the code for the{' '} - - existing plugins - {' '} - in the directory{' '} - - plugins/ - - . - - - - - - - - backstage.io - - - - Create a plugin - - - - Plugin gallery - - - - - - -
- ); -}; - -export default WelcomePage; diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index ce8dae1168..a7d2d480b7 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-xcmetrics +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.0 + - @backstage/theme@0.2.11 + ## 0.2.8 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index e6ef2c2631..2d2f9983d6 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.6.1", + "@backstage/core-components": "^0.7.0", "@backstage/core-plugin-api": "^0.1.10", "@backstage/errors": "^0.1.1", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.11", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +36,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.15", - "@backstage/core-app-api": "^0.1.16", - "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.18", + "@backstage/cli": "^0.7.16", + "@backstage/core-app-api": "^0.1.17", + "@backstage/dev-utils": "^0.2.12", + "@backstage/test-utils": "^0.1.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index 3d74882b6c..c8f35508fa 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -134,6 +134,23 @@ async function findPackageDirs() { return packageDirs; } +function logApiReportInstructions() { + console.log(''); + console.log( + '*************************************************************************************', + ); + console.log( + '* You have uncommitted changes to the public API of a package. *', + ); + console.log( + '* To solve this, run `yarn build:api-reports` and commit all api-report.md changes. *', + ); + console.log( + '*************************************************************************************', + ); + console.log(''); +} + interface ApiExtractionOptions { packageDirs: string[]; outputDir: string; @@ -267,20 +284,7 @@ async function runApiExtraction({ if (!extractorResult.succeeded) { if (shouldLogInstructions) { - console.log(''); - console.log( - '*************************************************************************************', - ); - console.log( - '* You have uncommitted changes to the public API of a package. *', - ); - console.log( - '* To solve this, run `yarn build:api-reports` and commit all api-report.md changes. *', - ); - console.log( - '*************************************************************************************', - ); - console.log(''); + logApiReportInstructions(); if (conflictingFile) { console.log(''); @@ -294,7 +298,8 @@ async function runApiExtraction({ const content = await fs.readFile(conflictingFile, 'utf8'); console.log(content); - console.log(''); + + logApiReportInstructions(); } } diff --git a/yarn.lock b/yarn.lock index c17580a415..7c6bdf2f45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -375,7 +375,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.4.4", "@babel/core@^7.7.5": +"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.7.5": version "7.14.8" resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== @@ -414,7 +414,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13": +"@babel/helper-annotate-as-pure@^7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== @@ -1536,7 +1536,7 @@ "@babel/helper-plugin-utils" "^7.14.5" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.14.0", "@babel/plugin-transform-modules-commonjs@^7.14.5", "@babel/plugin-transform-modules-commonjs@^7.4.4": +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.14.0", "@babel/plugin-transform-modules-commonjs@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97" integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A== @@ -1663,7 +1663,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.13.0" -"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.12.13": +"@babel/plugin-transform-react-display-name@^7.0.0": version "7.14.2" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.2.tgz#2e854544d42ab3bb9c21f84e153d62e800fbd593" integrity sha512-zCubvP+jjahpnFJvPaHPiGVfuVUjXHhFvJKQdNnsmSsiU9kR/rCZ41jHc++tERD2zV+p7Hr6is+t5b6iWTCqSw== @@ -1677,13 +1677,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-react-jsx-development@^7.12.17": - version "7.12.17" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz#f510c0fa7cd7234153539f9a362ced41a5ca1447" - integrity sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.12.17" - "@babel/plugin-transform-react-jsx-development@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.14.5.tgz#1a6c73e2f7ed2c42eebc3d2ad60b0c7494fcb9af" @@ -1691,7 +1684,7 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.14.5" -"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.12.17", "@babel/plugin-transform-react-jsx@^7.13.12": +"@babel/plugin-transform-react-jsx@^7.0.0": version "7.14.3" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.3.tgz#0e26597805cf0862da735f264550933c38babb66" integrity sha512-uuxuoUNVhdgYzERiHHFkE4dWoJx+UFVyuAl0aqN8P2/AKFHwqgUC5w2+4/PjpKXJsFgBlYAFXlUmDQ3k3DUkXw== @@ -1713,14 +1706,6 @@ "@babel/plugin-syntax-jsx" "^7.14.5" "@babel/types" "^7.14.9" -"@babel/plugin-transform-react-pure-annotations@^7.12.1": - version "7.12.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" - integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-transform-react-pure-annotations@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.14.5.tgz#18de612b84021e3a9802cbc212c9d9f46d0d11fc" @@ -2046,7 +2031,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.12.10": +"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.12.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.14.5.tgz#0fbb769513f899c2c56f3a882fa79673c2d4ab3c" integrity sha512-XFxBkjyObLvBaAvkx1Ie95Iaq4S/GUEIrejyrntQ/VCMKUYvKLoyKxOBzJ2kjA3b6rC9/KL6KXfDC2GqvLiNqQ== @@ -2058,18 +2043,6 @@ "@babel/plugin-transform-react-jsx-development" "^7.14.5" "@babel/plugin-transform-react-pure-annotations" "^7.14.5" -"@babel/preset-react@^7.12.5": - version "7.13.13" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz#fa6895a96c50763fe693f9148568458d5a839761" - integrity sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-option" "^7.12.17" - "@babel/plugin-transform-react-display-name" "^7.12.13" - "@babel/plugin-transform-react-jsx" "^7.13.12" - "@babel/plugin-transform-react-jsx-development" "^7.12.17" - "@babel/plugin-transform-react-pure-annotations" "^7.12.1" - "@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.13.0": version "7.14.5" resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.14.5.tgz#aa98de119cf9852b79511f19e7f44a2d379bcce0" @@ -2098,14 +2071,7 @@ core-js-pure "^3.16.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.14.8" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" - integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.15.4" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== @@ -2362,6 +2328,49 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" +"@backstage/core-components@^0.6.1": + version "0.6.1" + resolved "https://registry.npmjs.org/@backstage/core-components/-/core-components-0.6.1.tgz#65cfc704828f76c48ad5115404779206bb6fe1bb" + integrity sha512-gQRRcYEv98BFLpVW5nu2MhuzgjoMVEhPMyUSRc9NYa58GA1D8Q7zV7xYdbfCanMOlKDmi+rQ3Cp2u6C60+uhNg== + dependencies: + "@backstage/config" "^0.1.10" + "@backstage/core-plugin-api" "^0.1.10" + "@backstage/errors" "^0.1.2" + "@backstage/theme" "^0.2.10" + "@material-table/core" "^3.1.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + "@types/dagre" "^0.7.44" + "@types/react" "*" + "@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.21" + pluralize "^8.0.0" + 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 "^7.12.2" + 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 "^17.2.4" + remark-gfm "^1.0.0" + zen-observable "^0.8.15" + "@backstage/core@*": version "0.7.14" resolved "https://registry.npmjs.org/@backstage/core/-/core-0.7.14.tgz#863844fe40bb6a29bcc2d297e42055633b0e886f" @@ -2432,6 +2441,29 @@ react-router-dom "6.0.0-beta.0" react-use "^17.2.4" +"@backstage/plugin-catalog-react@^0.5.0": + version "0.5.2" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.5.2.tgz#7d1af3b0aa2397e4c62847fe78b8fe5afd90ee07" + integrity sha512-eoVGSrFyPWgMNZY9rMwEm0QS7B+FRLR2L+nRUlFjK4YnGQCYMXMZroJqSMRdBqBKlPiMgZO2AzZiFROcpSPwmA== + dependencies: + "@backstage/catalog-client" "^0.5.0" + "@backstage/catalog-model" "^0.9.4" + "@backstage/core-app-api" "^0.1.16" + "@backstage/core-components" "^0.6.1" + "@backstage/core-plugin-api" "^0.1.10" + "@backstage/integration" "^0.6.7" + "@backstage/version-bridge" "^0.1.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + "@types/react" "*" + jwt-decode "^3.1.0" + lodash "^4.17.21" + qs "^6.9.4" + react "^16.13.1" + react-router "6.0.0-beta.0" + react-use "^17.2.4" + "@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" @@ -2442,16 +2474,16 @@ resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.2.tgz#b23080fa35520e993a8a37a0f5bca26aa4650a48" integrity sha512-NBEJlHWrhQucLhZGHtSxM2loSaNUMajC7KOYJLyfcdW/6goVoff2HoYI3bz8YCDN0wKGbxtUL0gx2dvHpvnWlw== -"@changesets/apply-release-plan@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.0.tgz#11bf168acecbf4cfa2b0e6425160bac5ceeec1c3" - integrity sha512-SE+5nPNSKUyUociPnAvnjYSVF+diciEhX9ZHSqKWMlydswCDjiaq9gz67qwWCmwgEgEOz0TS7VrQBoOlzbitvA== +"@changesets/apply-release-plan@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.1.tgz#ed3e30550f787ef1b72f0a51e29a54d244123109" + integrity sha512-ltYLM/PPoL1Un9hnNCbUac25FWonJvIZ/9C3O4UyZ/k4rir9FGvH6KLtMOiPEAJWnXmaHeRDr06MzohuXOnmvw== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/config" "^1.6.0" + "@changesets/config" "^1.6.1" "@changesets/get-version-range-type" "^0.3.2" - "@changesets/git" "^1.1.1" - "@changesets/types" "^4.0.0" + "@changesets/git" "^1.1.2" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" detect-indent "^6.0.0" fs-extra "^7.0.1" @@ -2461,36 +2493,36 @@ resolve-from "^5.0.0" semver "^5.4.1" -"@changesets/assemble-release-plan@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.0.tgz#3e57405e5c375e2d933f62e74d1874915e60cd61" - integrity sha512-LElDXTCBUkPSmdXlCisoUWw2paX48snatBmw/hKnGiSvnyZqdTIylLojAGQWG0/vOO9v3s/DvJ4hdagIquxJjg== +"@changesets/assemble-release-plan@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.1.tgz#80e9b750705677eb2d6356c581ed9c2e97fd68e7" + integrity sha512-KQqafvScTFQ/4Q2LpLmDYhU47LWvIGcgVS8tzKU8fBvRdKuLGQXe42VYbwVM0cHIkFd/b6YFn+H2QMdKC2MjIQ== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.1" - "@changesets/types" "^4.0.0" + "@changesets/get-dependents-graph" "^1.2.2" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" semver "^5.4.1" "@changesets/cli@^2.14.0": - version "2.16.0" - resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.16.0.tgz#9f794005d0503efba5e348b929821a1732fd0f0d" - integrity sha512-VFkXSyyk/WRjjUoBI7g7cDy09qBjPbBQOloPMEshTzMo/NY9muWHl2yB/FSSkV/6PxGimPtJ7aEJPYfk8HCfXw== + version "2.17.0" + resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.17.0.tgz#cc7ff4f64d029ddd6d87020a012c8cf8c7adde58" + integrity sha512-UyraYwYst1lTjef+8i80XQ6SqsLaGwi4Sgn9YuDf2xdHA9m+5qQXshHvHVjaTdPTA09rqMBk9yeO7vmAqF4+vQ== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/apply-release-plan" "^5.0.0" - "@changesets/assemble-release-plan" "^5.0.0" - "@changesets/config" "^1.6.0" + "@changesets/apply-release-plan" "^5.0.1" + "@changesets/assemble-release-plan" "^5.0.1" + "@changesets/config" "^1.6.1" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.1" - "@changesets/get-release-plan" "^3.0.0" - "@changesets/git" "^1.1.1" + "@changesets/get-dependents-graph" "^1.2.2" + "@changesets/get-release-plan" "^3.0.1" + "@changesets/git" "^1.1.2" "@changesets/logger" "^0.0.5" - "@changesets/pre" "^1.0.6" - "@changesets/read" "^0.4.7" - "@changesets/types" "^4.0.0" - "@changesets/write" "^0.1.4" + "@changesets/pre" "^1.0.7" + "@changesets/read" "^0.5.0" + "@changesets/types" "^4.0.1" + "@changesets/write" "^0.1.5" "@manypkg/get-packages" "^1.0.1" "@types/semver" "^6.0.0" boxen "^1.3.0" @@ -2509,15 +2541,15 @@ term-size "^2.1.0" tty-table "^2.8.10" -"@changesets/config@^1.6.0": - version "1.6.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.0.tgz#2cd9426b9d4212534d2b31c51de43280b76d3df4" - integrity sha512-vMY/OpMFSDC2crDKb5Nq2kMX9hozcXL4dY5Rr+a1JQ044Rz+jqjJPpdTP2yQ+j7qmeGcUTvwjJoEMeekYwfqhg== +"@changesets/config@^1.6.1": + version "1.6.1" + resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.1.tgz#e9b1636fd56a74411c493c924e6ffa07d7d26091" + integrity sha512-aQTo6ODvhsvnSFszMP1YbJyAi1DtE1Pes9rL+G+KYJiAOA6k5RzbiKOarjo+ZkKXpX0G3CBAbOO8jXOX4xG7cQ== dependencies: "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.1" + "@changesets/get-dependents-graph" "^1.2.2" "@changesets/logger" "^0.0.5" - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" fs-extra "^7.0.1" micromatch "^4.0.2" @@ -2529,28 +2561,28 @@ dependencies: extendable-error "^0.1.5" -"@changesets/get-dependents-graph@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.1.tgz#462908693dc3a354622e43f85a764b74b5bb53af" - integrity sha512-vJOibo9SkqhVbgfq5AHIlQ7tzkYQIXh3tPAnlNLy2bPZsU+SByd74GaxHYWt1zOBlncU25WKrIM6J7XBB+GVUg== +"@changesets/get-dependents-graph@^1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.2.tgz#7a2238f3d1a023de83d37b727a0da15826e88d73" + integrity sha512-3zJRw6TcexmOrmIZNOXpIRsZtqtrdmlzbqp4+V0VgnBvTxz16rqCS9VBsBqFYeJDWFj3soOlHUMeTwLghr18DA== dependencies: - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" chalk "^2.1.0" fs-extra "^7.0.1" semver "^5.4.1" -"@changesets/get-release-plan@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.0.tgz#55efc01db2e24bd7a88e703956eb2f6c4a79054f" - integrity sha512-7VLiqpcWZyjwIXYgkubBC/9cdwqUJEhLMRT9/Y9+ctHqrpsXmJg15QQPTOh3HT9yGN5fJPL1WwuZkc1HXUhK0g== +"@changesets/get-release-plan@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.1.tgz#c98a34321eac9e4187098893ff8dadb6f90ad89c" + integrity sha512-HTZeEPvLlcWMWKxLrzQNLQWKDDN1lUKvaOV+hl/yBhgtyJECljJJzd3IRaKqCSWMrYKNaaEcmunTtZ4oaeoK9w== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/assemble-release-plan" "^5.0.0" - "@changesets/config" "^1.6.0" - "@changesets/pre" "^1.0.6" - "@changesets/read" "^0.4.7" - "@changesets/types" "^4.0.0" + "@changesets/assemble-release-plan" "^5.0.1" + "@changesets/config" "^1.6.1" + "@changesets/pre" "^1.0.7" + "@changesets/read" "^0.5.0" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" "@changesets/get-version-range-type@^0.3.2": @@ -2558,14 +2590,14 @@ resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== -"@changesets/git@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.1.tgz#f444d3ff3604acb6949560656c9ef330485a5fa3" - integrity sha512-Z12TcKwgU33YE3r76cyU+X81RchOXljDZ5s3G2u0Zd+ODyrwlDb91IO55+6R0Ha6ouPz8ioont0gA70c1RFngg== +"@changesets/git@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.2.tgz#248d4418bcb2d4f198852409cfcbd06a1fcb0424" + integrity sha512-dfza8elsIwcYVa4fFzLaPs4+AkoCFiW3sfzkkC7WR+rG9j+zZh7CelzVpnoiAbEI2QOzeCbZKMoLSvBPgHhB1g== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" is-subdir "^1.1.1" spawndamnit "^2.0.0" @@ -2577,51 +2609,51 @@ dependencies: chalk "^2.1.0" -"@changesets/parse@^0.3.8": - version "0.3.8" - resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.8.tgz#0bb244eccb35cb301168f85684bb03389c59341d" - integrity sha512-0S7Dc7XbMOKamBtd48vVuWL2aFZyaglw6lJsXNddn9forFf8oMKMmdyJ/HQPyeEChDDOhDF1/ya7m/zpt4Dk4w== +"@changesets/parse@^0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.9.tgz#c518792b05f15ab418d58dc1cea81601556e845e" + integrity sha512-XoTEkMpvRRVxSlhvOaK4YSFM+RZhYFTksxRh7ieNkb6pMxkpq8MOYSi/07BuqkODn4dJEMOoSy3RzL99P6FyqA== dependencies: - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" js-yaml "^3.13.1" -"@changesets/pre@^1.0.6": - version "1.0.6" - resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.6.tgz#45700cf18274b35b2296000befe7fe4df8ff046f" - integrity sha512-ZwFFQLjhTmA4hj8+Cf9pm6nD9Tp+AiBz1wJLaGum4Ae1fPXMwDnJfHknFUTytqZBlC0gHkiGSj6QkUuetWvckg== +"@changesets/pre@^1.0.7": + version "1.0.7" + resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.7.tgz#caf6430c90b8ac6d58c9cd90a19558ab06b19b88" + integrity sha512-oUU6EL4z0AIyCv/EscQFxxJsQfc9/AcSpqAGbdZrLXwshUWTXsJHMWlE3/+iSIyQ+I+/xtxbBxnqDUpUU3TOOg== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" "@manypkg/get-packages" "^1.0.1" fs-extra "^7.0.1" -"@changesets/read@^0.4.7": - version "0.4.7" - resolved "https://registry.npmjs.org/@changesets/read/-/read-0.4.7.tgz#5a32ae7092330fba31eaec4c83321bb936605766" - integrity sha512-E70QrYQpSCMF0nC0dlPU7i6A9zht+8zkQczrKMbOUwDVrfidcvgojxfuJSQbzptYSb9OKYh8GOLd+bsq9+uO9Q== +"@changesets/read@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@changesets/read/-/read-0.5.0.tgz#52f7a10f6baebf97172e62035ee8345652c5a1c0" + integrity sha512-A2OJ+vgfvbUaLx2yKyHH+tapa+DUd2NtpFpVuxjUqv0zirjqju20z1bziqaqpIQSf/rXPuoc09vp5w4VakraHg== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/git" "^1.1.1" + "@changesets/git" "^1.1.2" "@changesets/logger" "^0.0.5" - "@changesets/parse" "^0.3.8" - "@changesets/types" "^4.0.0" + "@changesets/parse" "^0.3.9" + "@changesets/types" "^4.0.1" chalk "^2.1.0" fs-extra "^7.0.1" p-filter "^2.1.0" -"@changesets/types@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.0.tgz#635f804546b0a96ecc0ca3f26403a6782a3dc938" - integrity sha512-whLmPx2wgJRoOtxVZop+DJ71z1gTSkij7osiHgN+pe//FiE6bb4ffvBBb0rACs2cUPfAkWxgSPzqkECgKS1jvQ== +"@changesets/types@^4.0.1": + version "4.0.1" + resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.1.tgz#85cf3cc32baff0691112d9d15fc21fbe022c9f0a" + integrity sha512-zVfv752D8K2tjyFmxU/vnntQ+dPu+9NupOSguA/2Zuym4tVxRh0ylArgKZ1bOAi2eXfGlZMxJU/kj7uCSI15RQ== -"@changesets/write@^0.1.4": - version "0.1.4" - resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.4.tgz#5828ecc70c48d0e8696c5f13fe06b730cdfde6f2" - integrity sha512-uco+vS3mo2JqflLciIU707har+6AEFOeP8pgu3vVC1M2WcKukQgR1KylHFqZJxKQWahf8mQnuUSbgR4yJQuhmA== +"@changesets/write@^0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.5.tgz#97574d95c8e48c3bbb1173802672f9a64d1b7fef" + integrity sha512-AYVSCH7on/Cyzo/8lVfqlsXmyKl3JhbNu9yHApdLPhHAzv5wqoHiZlMDkmd+AA67SRqzK2lDs4BcIojK+uWeIA== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/types" "^4.0.0" + "@changesets/types" "^4.0.1" fs-extra "^7.0.1" human-id "^1.0.2" prettier "^1.19.1" @@ -2723,6 +2755,11 @@ dependencies: "@date-io/core" "^2.10.11" +"@discoveryjs/json-ext@^0.5.3": + version "0.5.5" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" + integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== + "@elastic/elasticsearch-mock@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@elastic/elasticsearch-mock/-/elasticsearch-mock-0.3.0.tgz#6b1d8448aad3ca20f760fa01c0206b733c9c1e54" @@ -5546,7 +5583,22 @@ react-syntax-highlighter "^13.5.3" regenerator-runtime "^0.13.7" -"@storybook/addons@6.3.7", "@storybook/addons@^6.1.11": +"@storybook/addons@6.3.11", "@storybook/addons@^6.1.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.11.tgz#4b5e88793bcce7ef823340e9010a96e35e3284cc" + integrity sha512-2Y03lOwzWDRB/glISa/4luBMM5uyYhkIBixbZF9miIb2SCWRlNmom5NCnKsR18Wu6g7zI7os3aAMfKr24aSofQ== + dependencies: + "@storybook/api" "6.3.11" + "@storybook/channels" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/core-events" "6.3.11" + "@storybook/router" "6.3.11" + "@storybook/theming" "6.3.11" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + +"@storybook/addons@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.7.tgz#7c6b8d11b65f67b1884f6140437fe996dc39537a" integrity sha512-9stVjTcc52bqqh7YQex/LpSjJ4e2Czm4/ZYDjIiNy0p4OZEx+yLhL5mZzMWh2NQd6vv+pHASBSxf2IeaR5511A== @@ -5561,6 +5613,32 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/api@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.11.tgz#ea3806a0570da65bfb5b39e4edb90289b5ba701e" + integrity sha512-QmgXM613zy/DPrJLVRJnALT5Rh4VWgvxrHbdLxRWGDrMHNYlm3N1KA+a6ZtIM5Zl94B3y5o0SPCSleSbnA3viA== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/channels" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/core-events" "6.3.11" + "@storybook/csf" "0.0.1" + "@storybook/router" "6.3.11" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.3.11" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/api@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.7.tgz#88b8a51422cd0739c91bde0b1d65fb6d8a8485d0" @@ -5587,10 +5665,10 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-webpack4@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.3.7.tgz#1cc1a1184043be3f6ef840d0b43ba91a803105e2" - integrity sha512-M5envblMzAUrNqP1+ouKiL8iSIW/90+kBRU2QeWlZoZl1ib+fiFoKk06cgbaC70Bx1lU8nOnI/VBvB5pLhXLaw== +"@storybook/builder-webpack4@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.3.11.tgz#b1b62a41b2fbd951733e86aaa4730dc2541b4221" + integrity sha512-FkLfbe8GneSALj9Dgh0i+mZR8EVoM5T96HDM0DxhNbWzO3lDrcKxFdlRjYRohZaJn3gYUU09IU9xrfFKgYoYew== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -5613,20 +5691,20 @@ "@babel/preset-env" "^7.12.11" "@babel/preset-react" "^7.12.10" "@babel/preset-typescript" "^7.12.7" - "@storybook/addons" "6.3.7" - "@storybook/api" "6.3.7" - "@storybook/channel-postmessage" "6.3.7" - "@storybook/channels" "6.3.7" - "@storybook/client-api" "6.3.7" - "@storybook/client-logger" "6.3.7" - "@storybook/components" "6.3.7" - "@storybook/core-common" "6.3.7" - "@storybook/core-events" "6.3.7" - "@storybook/node-logger" "6.3.7" - "@storybook/router" "6.3.7" + "@storybook/addons" "6.3.11" + "@storybook/api" "6.3.11" + "@storybook/channel-postmessage" "6.3.11" + "@storybook/channels" "6.3.11" + "@storybook/client-api" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/components" "6.3.11" + "@storybook/core-common" "6.3.11" + "@storybook/core-events" "6.3.11" + "@storybook/node-logger" "6.3.11" + "@storybook/router" "6.3.11" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.3.7" - "@storybook/ui" "6.3.7" + "@storybook/theming" "6.3.11" + "@storybook/ui" "6.3.11" "@types/node" "^14.0.10" "@types/webpack" "^4.41.26" autoprefixer "^9.8.6" @@ -5663,6 +5741,19 @@ webpack-hot-middleware "^2.25.0" webpack-virtual-modules "^0.2.2" +"@storybook/channel-postmessage@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.11.tgz#b03748affc8775ce241ded0eabd50d17bb065117" + integrity sha512-cXmDqs91CLrqPugLLVdmE6iPH0eWJ0LsO3uoYXGDIAkv+C1HJBn5VdO1/gBm/B+lpkZ++DYiZ0tRUOMKsuw8IQ== + dependencies: + "@storybook/channels" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/core-events" "6.3.11" + core-js "^3.8.2" + global "^4.4.0" + qs "^6.10.0" + telejson "^5.3.2" + "@storybook/channel-postmessage@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.7.tgz#bd4edf84a29aa2cd4a22d26115c60194d289a840" @@ -5676,6 +5767,15 @@ qs "^6.10.0" telejson "^5.3.2" +"@storybook/channels@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.11.tgz#a14ce233367a9072bd1cffef3825f125c27bb0ae" + integrity sha512-slY3B1zRJaABrkFj2r89O0D6axQjeh6oyuJFrRZ7SoTK26ODi839LD+LwSpk40c4FoQAgT3pcJmjiFyUbBdtRg== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channels@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.7.tgz#85ed5925522b802d959810f78d37aacde7fea66e" @@ -5685,6 +5785,30 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/client-api@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.11.tgz#3e6548bf6e83a2958db701cf59740a2519eea771" + integrity sha512-YUiVZzQYaGqYCTp1Jcf7zviJSWq3no93wT8uCj2HLW7n56fa5Iwx1O29dY2lhBEGmJ3JjTPNeSrSrzoHb1AZRg== + dependencies: + "@storybook/addons" "6.3.11" + "@storybook/channel-postmessage" "6.3.11" + "@storybook/channels" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/core-events" "6.3.11" + "@storybook/csf" "0.0.1" + "@types/qs" "^6.9.5" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + stable "^0.1.8" + store2 "^2.12.0" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.7.tgz#cb1dca05467d777bd09aadbbdd1dd22ca537ce14" @@ -5709,6 +5833,14 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/client-logger@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.11.tgz#d2e0e17f35ed4c5ee282d818b6db3a015ce6b833" + integrity sha512-U06IuAGZFOJYgtXH02dbNe5lHLcwsSo6JBdFLjcUP3C0WqtskoUGLA2XLUCYasl7OMB0mcLHy4GaZL4wtrW6+Q== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/client-logger@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.7.tgz#ff17b7494e7e9e23089b0d5c5364c371c726bdd1" @@ -5717,6 +5849,36 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/components@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/components/-/components-6.3.11.tgz#a7d015fc9808d0200d033d8f694db79277770030" + integrity sha512-/iajr+kMXQLgXa6hr/nupcHFzdppZ4S/vdDgoyXW/L1jDeKzy5Bxzf4JjjoR/tY6jgiFRja1C1mUGFqceyb9ow== + dependencies: + "@popperjs/core" "^2.6.0" + "@storybook/client-logger" "6.3.11" + "@storybook/csf" "0.0.1" + "@storybook/theming" "6.3.11" + "@types/color-convert" "^2.0.0" + "@types/overlayscrollbars" "^1.12.0" + "@types/react-syntax-highlighter" "11.0.5" + color-convert "^2.0.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + overlayscrollbars "^1.13.1" + polished "^4.0.5" + prop-types "^15.7.2" + react-colorful "^5.1.2" + react-popper-tooltip "^3.1.1" + react-syntax-highlighter "^13.5.3" + react-textarea-autosize "^8.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/components@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/components/-/components-6.3.7.tgz#42b1ca6d24e388e02eab82aa9ed3365db2266ecc" @@ -5747,18 +5909,18 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/core-client@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.3.7.tgz#cfb75952e0e1d32f2aca92bca2786334ab589c40" - integrity sha512-M/4A65yV+Y4lsCQXX4BtQO/i3BcMPrU5FkDG8qJd3dkcx2fUlFvGWqQPkcTZE+MPVvMEGl/AsEZSADzah9+dAg== +"@storybook/core-client@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.3.11.tgz#beede8dfb0b0d86945f0b15ef574c6ed8af37576" + integrity sha512-GSVxt6ZnZPboZ0nRJ0b4+r8yL43l0uImgg6tsCwctxj/qvx4O9thh3zVfgu1i6BqG9auJoNby2dTZDaGkPLaIg== dependencies: - "@storybook/addons" "6.3.7" - "@storybook/channel-postmessage" "6.3.7" - "@storybook/client-api" "6.3.7" - "@storybook/client-logger" "6.3.7" - "@storybook/core-events" "6.3.7" + "@storybook/addons" "6.3.11" + "@storybook/channel-postmessage" "6.3.11" + "@storybook/client-api" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/core-events" "6.3.11" "@storybook/csf" "0.0.1" - "@storybook/ui" "6.3.7" + "@storybook/ui" "6.3.11" airbnb-js-shims "^2.2.1" ansi-to-html "^0.6.11" core-js "^3.8.2" @@ -5770,10 +5932,10 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" -"@storybook/core-common@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.3.7.tgz#9eedf3ff16aff870950e3372ab71ef846fa3ac52" - integrity sha512-exLoqRPPsAefwyjbsQBLNFrlPCcv69Q/pclqmIm7FqAPR7f3CKP1rqsHY0PnemizTL/+cLX5S7mY90gI6wpNug== +"@storybook/core-common@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.3.11.tgz#e21cd087435c4f17d099ea3a27d34300d5f201db" + integrity sha512-PR/TndBaItPjItoOMEYCE4rPZGF7dfuqh+SbL0pmk6yLqO5MTSPEVpLEutbKh3uYlt1mgb8cXLuTQ3ybkG5O+Q== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -5796,7 +5958,7 @@ "@babel/preset-react" "^7.12.10" "@babel/preset-typescript" "^7.12.7" "@babel/register" "^7.12.1" - "@storybook/node-logger" "6.3.7" + "@storybook/node-logger" "6.3.11" "@storybook/semver" "^7.3.2" "@types/glob-base" "^0.3.0" "@types/micromatch" "^4.0.1" @@ -5824,6 +5986,13 @@ util-deprecate "^1.0.2" webpack "4" +"@storybook/core-events@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.11.tgz#76c1701c9cb3f50d6a1fe2344d396df2dd76f8a3" + integrity sha512-wuWZsw2VB2qSuac+40SaeaZ40/4sQhYSnLSN+l944G1kkjZwZcVc1nBTaYBt9H7uH+INkmGblDbGNnme71KCjg== + dependencies: + core-js "^3.8.2" + "@storybook/core-events@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.7.tgz#c5bc7cae7dc295de73b6b9f671ecbe582582e9bd" @@ -5831,17 +6000,18 @@ dependencies: core-js "^3.8.2" -"@storybook/core-server@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.3.7.tgz#6f29ad720aafe4a97247b5e306eac4174d0931f2" - integrity sha512-m5OPD/rmZA7KFewkXzXD46/i1ngUoFO4LWOiAY/wR6RQGjYXGMhSa5UYFF6MNwSbiGS5YieHkR5crB1HP47AhQ== +"@storybook/core-server@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.3.11.tgz#e6cfdab8de72254007de50473186734c26bbcbc7" + integrity sha512-pQiLtvoa4PkQ0I/i/nA+GSa7XsJeu83BQHirNba8kI+bsRLFWAY6BPM3nIs3n7fa3OlkAVxnYRgmZJXodGFIng== dependencies: - "@storybook/builder-webpack4" "6.3.7" - "@storybook/core-client" "6.3.7" - "@storybook/core-common" "6.3.7" - "@storybook/csf-tools" "6.3.7" - "@storybook/manager-webpack4" "6.3.7" - "@storybook/node-logger" "6.3.7" + "@discoveryjs/json-ext" "^0.5.3" + "@storybook/builder-webpack4" "6.3.11" + "@storybook/core-client" "6.3.11" + "@storybook/core-common" "6.3.11" + "@storybook/csf-tools" "6.3.11" + "@storybook/manager-webpack4" "6.3.11" + "@storybook/node-logger" "6.3.11" "@storybook/semver" "^7.3.2" "@types/node" "^14.0.10" "@types/node-fetch" "^2.5.7" @@ -5870,18 +6040,18 @@ util-deprecate "^1.0.2" webpack "4" -"@storybook/core@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/core/-/core-6.3.7.tgz#482228a270abc3e23fed10c7bc4df674da22ca19" - integrity sha512-YTVLPXqgyBg7TALNxQ+cd+GtCm/NFjxr/qQ1mss1T9GCMR0IjE0d0trgOVHHLAO8jCVlK8DeuqZCCgZFTXulRw== +"@storybook/core@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/core/-/core-6.3.11.tgz#7ded63649e8096339eca445e8de1f550a621179d" + integrity sha512-WhDA98hTI01L/R9l7ZCDXqhZ73Esf7VmngxcF8sVZVnM1ZwO3jq0BPYnTPUR65sVZ4XLUx1zuFQ2pAipsM8y1g== dependencies: - "@storybook/core-client" "6.3.7" - "@storybook/core-server" "6.3.7" + "@storybook/core-client" "6.3.11" + "@storybook/core-server" "6.3.11" -"@storybook/csf-tools@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.3.7.tgz#505514d211f8698c47ddb15662442098b4b00156" - integrity sha512-A7yGsrYwh+vwVpmG8dHpEimX021RbZd9VzoREcILH56u8atssdh/rseljyWlRes3Sr4QgtLvDB7ggoJ+XDZH7w== +"@storybook/csf-tools@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.3.11.tgz#53e40158032902cdedf6fbbdee9953e92f4a9973" + integrity sha512-XI9E1nhoD3ALRmHPL6NTVxap6ryh64pJDQyYbji7zRblL+QyCywv6AwUUs00iwjWrLeuthusJ434aX/imATQSA== dependencies: "@babel/generator" "^7.12.11" "@babel/parser" "^7.12.11" @@ -5905,20 +6075,20 @@ dependencies: lodash "^4.17.15" -"@storybook/manager-webpack4@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.3.7.tgz#9ca604dea38d3c47eb38bf485ca6107861280aa8" - integrity sha512-cwUdO3oklEtx6y+ZOl2zHvflICK85emiXBQGgRcCsnwWQRBZOMh+tCgOSZj4jmISVpT52RtT9woG4jKe15KBig== +"@storybook/manager-webpack4@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.3.11.tgz#477a796da09a771b2d3e1adfe217a79de37057af" + integrity sha512-EY/mhxAbOznZn4ql6/UW738ezd2dIWCgC3DAzhd/1crIbp7LqzNcXOW3QWYtfLLEOhsmI3TO1U7ExyN1t3n48g== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-transform-template-literals" "^7.12.1" "@babel/preset-react" "^7.12.10" - "@storybook/addons" "6.3.7" - "@storybook/core-client" "6.3.7" - "@storybook/core-common" "6.3.7" - "@storybook/node-logger" "6.3.7" - "@storybook/theming" "6.3.7" - "@storybook/ui" "6.3.7" + "@storybook/addons" "6.3.11" + "@storybook/core-client" "6.3.11" + "@storybook/core-common" "6.3.11" + "@storybook/node-logger" "6.3.11" + "@storybook/theming" "6.3.11" + "@storybook/ui" "6.3.11" "@types/node" "^14.0.10" "@types/webpack" "^4.41.26" babel-loader "^8.2.2" @@ -5948,10 +6118,10 @@ webpack-dev-middleware "^3.7.3" webpack-virtual-modules "^0.2.2" -"@storybook/node-logger@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.7.tgz#492469ea4749de8d984af144976961589a1ac382" - integrity sha512-YXHCblruRe6HcNefDOpuXJoaybHnnSryIVP9Z+gDv6OgLAMkyxccTIaQL9dbc/eI4ywgzAz4kD8t1RfVwXNVXw== +"@storybook/node-logger@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.3.11.tgz#b17c48b8b8083ed9d06d01266af0736b13101dd4" + integrity sha512-c6nsOk/AIux89/AVIydLA2ToqHWq4XoUKFkYJ/eOPJTLv3bdl7fV/5Vlx7EqRHqlXYCGbvTaDrMs+0tVHmp2vg== dependencies: "@types/npmlog" "^4.1.2" chalk "^4.1.0" @@ -5973,17 +6143,17 @@ tslib "^2.0.0" "@storybook/react@^6.3.6": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/react/-/react-6.3.7.tgz#b15259aeb4cdeef99cc7f09d21db42e3ecd7a01a" - integrity sha512-4S0iCQIzgi6PdAtV2sYw4uL57yIwbMInNFSux9CxPnVdlxOxCJ+U8IgTxD4tjkTvR4boYSEvEle46ns/bwg5iQ== + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/react/-/react-6.3.11.tgz#f6270fcebc072e6adbb1e08e4a4abb0f9b518744" + integrity sha512-nW04cvjGRW92gD2ibiDyRtSi0GW3RjZzZkFSiwzA7SbAw9HxvIP1nCYOroIX1x/xGMMiypgFPavgaFcaYbyE/Q== dependencies: "@babel/preset-flow" "^7.12.1" "@babel/preset-react" "^7.12.10" "@pmmmwh/react-refresh-webpack-plugin" "^0.4.3" - "@storybook/addons" "6.3.7" - "@storybook/core" "6.3.7" - "@storybook/core-common" "6.3.7" - "@storybook/node-logger" "6.3.7" + "@storybook/addons" "6.3.11" + "@storybook/core" "6.3.11" + "@storybook/core-common" "6.3.11" + "@storybook/node-logger" "6.3.11" "@storybook/react-docgen-typescript-plugin" "1.0.2-canary.253f8c1.0" "@storybook/semver" "^7.3.2" "@types/webpack-env" "^1.16.0" @@ -6001,6 +6171,22 @@ ts-dedent "^2.0.0" webpack "4" +"@storybook/router@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.3.11.tgz#7b43d4704f2bced12109e16906e100ff9fa3eae9" + integrity sha512-R9Den9xCGhU5CLx4wi79uHbJqKqpZO7QDpENZGL7bT/IDid7szqFldztrzvmC5LE1TBpU6VQjEGaITWMrG2tsw== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/client-logger" "6.3.11" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + ts-dedent "^2.0.0" + "@storybook/router@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/router/-/router-6.3.7.tgz#1714a99a58a7b9f08b6fcfe2b678dad6ca896736" @@ -6041,6 +6227,24 @@ prettier "~2.2.1" regenerator-runtime "^0.13.7" +"@storybook/theming@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.11.tgz#0fb4e42b43b5937647a3e4f17422c3cf45d046fb" + integrity sha512-MbEaGYP9nqu/Sv/FBK/7jRRHGe34TZ7tmZj6ZWE0KZih4Ey39LjPY6XcJ1rMDZclrXmT98p3+KClzdwKD0/Tdw== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.3.11" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/theming@6.3.7": version "6.3.7" resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.7.tgz#6daf9a21b26ed607f3c28a82acd90c0248e76d8b" @@ -6059,21 +6263,21 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/ui@6.3.7": - version "6.3.7" - resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.3.7.tgz#d0caea50640670da3189bbbb67c43da30c90455a" - integrity sha512-PBeRO8qtwAbtHvxUgNtz/ChUR6qnN+R37dMaIs3Y96jbks1fS2K9Mt7W5s1HnUbWbg2KsZMv9D4VYPBasY+Isw== +"@storybook/ui@6.3.11": + version "6.3.11" + resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.3.11.tgz#fc04d6ab50b78dc5f3d8fdc1eade0c78e4c4a4e9" + integrity sha512-Jy6J7GRCgcnCp6jOpGcGcjHfzMVqFJ8gnLzrvTPR6EQBl9sJlJMVvYyj/52fDTnrRj+mjs8+cnkQwUHg+U/yrA== dependencies: "@emotion/core" "^10.1.1" - "@storybook/addons" "6.3.7" - "@storybook/api" "6.3.7" - "@storybook/channels" "6.3.7" - "@storybook/client-logger" "6.3.7" - "@storybook/components" "6.3.7" - "@storybook/core-events" "6.3.7" - "@storybook/router" "6.3.7" + "@storybook/addons" "6.3.11" + "@storybook/api" "6.3.11" + "@storybook/channels" "6.3.11" + "@storybook/client-logger" "6.3.11" + "@storybook/components" "6.3.11" + "@storybook/core-events" "6.3.11" + "@storybook/router" "6.3.11" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.3.7" + "@storybook/theming" "6.3.11" "@types/markdown-to-jsx" "^6.11.3" copy-to-clipboard "^3.3.1" core-js "^3.8.2" @@ -6421,7 +6625,15 @@ dependencies: "@babel/types" "^7.3.0" -"@types/body-parser@*", "@types/body-parser@1.19.0", "@types/body-parser@^1.19.0": +"@types/body-parser@*", "@types/body-parser@^1.19.0": + version "1.19.1" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" + integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/body-parser@1.19.0": version "1.19.0" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== @@ -6634,6 +6846,13 @@ resolved "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.44.tgz#8f4b796b118ca29c132da7068fbc0d0351ee5851" integrity sha512-N6HD+79w77ZVAaVO7JJDW5yJ9LAxM62FpgNGO9xEde+KVYjDRyhIMzfiErXpr1g0JPon9kwlBzoBK6s4fOww9Q== +"@types/debug@^4.0.0": + version "4.1.7" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== + dependencies: + "@types/ms" "*" + "@types/diff@^5.0.0": version "5.0.0" resolved "https://registry.npmjs.org/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020" @@ -6708,9 +6927,9 @@ "@types/range-parser" "*" "@types/express-session@^1.17.2": - version "1.17.3" - resolved "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.3.tgz#4a37c5c4428b8f922ac8ac1cb4bd9190a4d2b097" - integrity sha512-57DnyxiqClXOIjoCgeKCUYfKxBPOlOY/k+l1TPK+7bSwyiPTrS5FIk1Ycql7twk4wO7P5lfOVy6akDGiaMSLfw== + version "1.17.4" + resolved "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz#97a30a35e853a61bdd26e727453b8ed314d6166b" + integrity sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg== dependencies: "@types/express" "*" @@ -6777,9 +6996,9 @@ "@types/node" "*" "@types/hast@^2.0.0": - version "2.3.1" - resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz#b16872f2a6144c7025f296fb9636a667ebb79cd9" - integrity sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q== + version "2.3.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== dependencies: "@types/unist" "*" @@ -7067,6 +7286,11 @@ dependencies: "@types/unist" "*" +"@types/mdurl@^1.0.0": + version "1.0.2" + resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + "@types/micromatch@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" @@ -7115,6 +7339,11 @@ dependencies: "@types/node" "*" +"@types/ms@*": + version "0.7.31" + resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.7": version "2.5.8" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" @@ -7366,7 +7595,7 @@ "@types/react" "*" "@types/react@*", "@types/react@>=16.9.0", "@types/react@^16.9": - version "16.14.15" + version "16.14.17" resolved "https://registry.npmjs.org/@types/react/-/react-16.14.15.tgz#95d8fa3148050e94bcdc5751447921adbe19f9e6" integrity sha512-jOxlBV9RGZhphdeqJTCv35VZOkjY+XIEY2owwSk84BNDdDv2xS6Csj6fhi+B/q30SR9Tz8lDNt/F2Z5RF3TrRg== dependencies: @@ -7634,9 +7863,9 @@ integrity sha512-vX1NPekXhrLquFWskH2thcvFAha187F/lM6xYOoEMZWwJ/6alSk0/ttmGP/YRqcqtCv0TMbZjYAdZyHAEcuU4g== "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": - version "2.0.3" - resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" - integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== + version "2.0.6" + resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== "@types/unzipper@^0.10.3": version "0.10.3" @@ -9457,6 +9686,11 @@ bail@^1.0.0: resolved "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== +bail@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/bail/-/bail-2.0.1.tgz#d676736373a374058a935aec81b94c12ba815771" + integrity sha512-d5FoTAr2S5DSUPKl85WNm2yUwsINN8eidIdIwsOge2t33DaOfOdSmmsI11jMN3GmALCXaw+Y6HMVHDzePshFAA== + balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -10237,9 +10471,9 @@ canvas@^2.6.1: simple-get "^3.0.3" canvg@^3.0.6: - version "3.0.8" - resolved "https://registry.npmjs.org/canvg/-/canvg-3.0.8.tgz#9125a4b7033d2f237402241b192587385f4589e6" - integrity sha512-9De5heHfVRgCkln3CGEeSJMviN5U2RyxL4uutYoe8HxI60BjH2XnT2ZUHIp+ZaAZNTUd5Asqfut8WEEdANqfAg== + version "3.0.9" + resolved "https://registry.npmjs.org/canvg/-/canvg-3.0.9.tgz#9ba095f158b94b97ca2c9c1c40785b11dc08df6d" + integrity sha512-rDXcnRPuz4QHoCilMeoTxql+fvGqNAxp+qV/KHD8rOiJSAfVjFclbdUNHD2Uqfthr+VMg17bD2bVuk6F07oLGw== dependencies: "@babel/runtime" "^7.12.5" "@types/raf" "^3.4.0" @@ -10248,7 +10482,7 @@ canvg@^3.0.6: regenerator-runtime "^0.13.7" rgbcolor "^1.0.1" stackblur-canvas "^2.0.0" - svg-pathdata "^5.0.5" + svg-pathdata "^6.0.3" capital-case@^1.0.4: version "1.0.4" @@ -10281,6 +10515,11 @@ ccount@^1.0.0: resolved "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +ccount@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.0.tgz#3d6fb55803832766a24c6f339abc507297eb5d25" + integrity sha512-VOR0NWFYX65n9gELQdcpqsie5L5ihBXuZGAgaPEp/U7IOSjnPMEH6geE+2f6lcekaNEfWzAHS45mPvSo5bqsUA== + chainsaw@~0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" @@ -10375,16 +10614,31 @@ character-entities-legacy@^1.0.0: resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== +character-entities-legacy@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-2.0.0.tgz#57f4d00974c696e8f74e9f493e7fcb75b44d7ee7" + integrity sha512-YwaEtEvWLpFa6Wh3uVLrvirA/ahr9fki/NUd/Bd4OR6EdJ8D22hovYQEOUCBfQfcqnC4IAMGMsHXY1eXgL4ZZA== + character-entities@^1.0.0: version "1.2.4" resolved "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== +character-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.0.tgz#508355fcc8c73893e0909efc1a44d28da2b6fdf3" + integrity sha512-oHqMj3eAuJ77/P5PaIRcqk+C3hdfNwyCD2DAUcD5gyXkegAuF2USC40CEqPscDk4I8FRGMTojGJQkXDsN5QlJA== + character-reference-invalid@^1.0.0: version "1.1.4" resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +character-reference-invalid@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.0.tgz#a0bdeb89c051fe7ed5d3158b2f06af06984f2813" + integrity sha512-pE3Z15lLRxDzWJy7bBHBopRwfI20sbrMVLQTC7xsPglCHf4Wv1e167OgYAFP78co2XlhojDyAqA+IAJse27//g== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -10871,6 +11125,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +comma-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" + integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== + command-exists@^1.2.9: version "1.2.9" resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" @@ -11281,12 +11540,7 @@ core-js-pure@^3.8.2: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.0.tgz#218e07add3f1844e53fab195c47871fc5ba18de8" integrity sha512-wzlhZNepF/QA9yvx3ePDgNGudU5KDB8lu/TRPKelYA/QtSnkS/cLl2W+TIdEX1FAFcBr0YpY7tPDlcmXJ7AyiQ== -core-js@^2.4.0, core-js@^2.6.10: - version "2.6.11" - resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.10: version "2.6.12" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== @@ -11297,9 +11551,9 @@ core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: integrity sha512-GUbtPllXMYRzIgHNZ4dTYTcUemls2cni83Q4Q/TrFONHfhcg9oEGOtaGHfb0cpzec60P96UKPvMkjX1jET8rUw== core-js@^3.6.0, core-js@^3.8.3: - version "3.18.1" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz#289d4be2ce0085d40fc1244c0b1a54c00454622f" - integrity sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA== + version "3.18.3" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" + integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -13150,6 +13404,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escodegen@^1.14.1, escodegen@^1.9.1: version "1.14.3" resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -15489,9 +15748,9 @@ highlight.js@~10.7.0: integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== history@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08" - integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg== + version "5.0.1" + resolved "https://registry.npmjs.org/history/-/history-5.0.1.tgz#de35025ed08bce0db62364b47ebbf9d97b5eb06a" + integrity sha512-5qC/tFUKfVci5kzgRxZxN5Mf1CV8NmJx9ByaPX0YTLx5Vz3Svh7NYp6eA4CpDq4iA9D0C1t8BNIfvQIrUI3mVw== dependencies: "@babel/runtime" "^7.7.6" @@ -16274,6 +16533,11 @@ is-alphabetical@1.0.4, is-alphabetical@^1.0.0: resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== +is-alphabetical@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.0.tgz#ef6e2caea57c63450fffc7abb6cbdafc5eb96e96" + integrity sha512-5OV8Toyq3oh4eq6sbWTYzlGdnMT/DPI5I0zxUBxjiigQsZycpkKF3kskkao3JyYGuYDHvhgJF+DrjMQp9SX86w== + is-alphanumerical@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" @@ -16282,6 +16546,14 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-alphanumerical@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.0.tgz#0fbfeb6a72d21d91143b3d182bf6cf5909ee66f6" + integrity sha512-t+2GlJ+hO9yagJ+jU3+HSh80VKvz/3cG2cxbGGm4S0hjKuhWQXgPVUVOZz3tqZzMjhmphZ+1TIJTlRZRoe6GCQ== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-arguments@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" @@ -16395,6 +16667,11 @@ is-decimal@^1.0.0: resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== +is-decimal@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.0.tgz#db1140337809fd043a056ae40a9bd1cdc563034c" + integrity sha512-QfrfjQV0LjoWQ1K1XSoEZkTAzSa14RKVMa5zg3SdAfzEmQzRM4+tbSFWb78creCeA9rNBzaZal92opi1TwPWZw== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -16516,6 +16793,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +is-hexadecimal@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.0.tgz#8e1ec9f48fe3eabd90161109856a23e0907a65d5" + integrity sha512-vGOtYkiaxwIiR0+Ng/zNId+ZZehGfINwTzdrDqc6iubbnQWhnPuYymOzOKUDqa2cSl59yHnEh2h6MvRLQsyNug== + is-in-browser@^1.0.2, is-in-browser@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" @@ -16650,6 +16932,11 @@ is-plain-obj@^3.0.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== +is-plain-obj@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22" + integrity sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw== + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -18820,6 +19107,11 @@ longest-streak@^2.0.0: resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== +longest-streak@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.0.tgz#f127e2bded83caa6a35ac5f7a2f2b2f94b36f3dc" + integrity sha512-XhUjWR5CFaQ03JOP+iSDS9koy8T5jfoImCZ4XprElw3BXsSk4MpVYOLw/6LTDKZhO13PlAXnB5gS4MHQTpkSOw== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -19078,6 +19370,11 @@ markdown-table@^2.0.0: dependencies: repeat-string "^1.0.0" +markdown-table@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.1.tgz#88c48957aaf2a8014ccb2ba026776a1d736fe3dc" + integrity sha512-CBbaYXKSGnE1uLRpKA1SWgIRb2PQrpkllNWpZtZe6VojOJ4ysqiq7/2glYcmKsOYN09QgH/HEBX5hIshAeiK6A== + markdown-to-jsx@^6.11.4: version "6.11.4" resolved "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz#b4528b1ab668aef7fe61c1535c27e837819392c5" @@ -19144,6 +19441,24 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" +mdast-util-definitions@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" + integrity sha512-5hcR7FL2EuZ4q6lLMUK5w4lHT2H3vqL9quPvYZ/Ku5iifrirfMHiGdhxdXMUbUkDmz5I+TYMd7nbaxUhbQkfpQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + unist-util-visit "^3.0.0" + +mdast-util-find-and-replace@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.1.0.tgz#69728acd250749f8aac6e150e07d1fd15619e829" + integrity sha512-1w1jbqAd13oU78QPBf5223+xB+37ecNtQ1JElq2feWols5oEYAl+SgNDnOZipe7NfLemoEt362yUS15/wip4mw== + dependencies: + escape-string-regexp "^5.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^4.0.0" + mdast-util-from-markdown@^0.8.0: version "0.8.1" resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.1.tgz#781371d493cac11212947226190270c15dc97116" @@ -19154,11 +19469,38 @@ mdast-util-from-markdown@^0.8.0: micromark "~2.10.0" parse-entities "^2.0.0" +mdast-util-from-markdown@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.0.2.tgz#7c793bb96b053d12f032e37382ae989efb70ee66" + integrity sha512-gXaxv/5fGdrr9TqSMlQK7FmshK8yR9DvW3+NapMBDm44inORxIZVJa1D3yjrUT9ISu8tB/jjblEkUzyzclquNg== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-to-string "^3.1.0" + micromark "^3.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-decode-string "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + parse-entities "^3.0.0" + unist-util-stringify-position "^3.0.0" + mdast-util-gfm-autolink-literal@^0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.1.tgz#94675074d725ed7254b3172fa7e7c3252960de39" integrity sha512-gJ2xSpqKCetSr22GEWpZH3f5ffb4pPn/72m4piY0v7T/S+O7n7rw+sfoPLhb2b4O7WdnERoYdALRcmD68FMtlw== +mdast-util-gfm-autolink-literal@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz#4032dcbaddaef7d4f2f3768ed830475bb22d3970" + integrity sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg== + dependencies: + "@types/mdast" "^3.0.0" + ccount "^2.0.0" + mdast-util-find-and-replace "^2.0.0" + micromark-util-character "^1.0.0" + mdast-util-gfm-strikethrough@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.2.tgz#6e9ddd33ce41b06a60463e817f6ef4cf7bfa0655" @@ -19166,6 +19508,14 @@ mdast-util-gfm-strikethrough@^0.2.0: dependencies: mdast-util-to-markdown "^0.5.0" +mdast-util-gfm-strikethrough@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.0.tgz#6cc72ef5d9539f4cee76af3f15dd0daa9e3af40f" + integrity sha512-gM9ipBUdRxYa6Yq1Hd8Otg6jEn/dRxFZ1F9ZX4QHosHOexLGqNZO2dh0A+YFbUEd10RcKjnjb4jOfJJzoXXUew== + dependencies: + "@types/mdast" "^3.0.3" + mdast-util-to-markdown "^1.0.0" + mdast-util-gfm-table@^0.1.0: version "0.1.4" resolved "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.4.tgz#5b3d71d16294c6fae1c2c424d3a081ffc7407b83" @@ -19174,6 +19524,14 @@ mdast-util-gfm-table@^0.1.0: markdown-table "^2.0.0" mdast-util-to-markdown "^0.5.0" +mdast-util-gfm-table@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.1.tgz#07c269a219d66ec2deb6de38aed0ba1d1f9442df" + integrity sha512-NByKuaSg5+M6r9DZBPXFUmhMHGFf9u+WE76EeStN01ghi8hpnydiWBXr+qj0XCRWI7SAMNtEjGvip6zci9axQA== + dependencies: + markdown-table "^3.0.0" + mdast-util-to-markdown "^1.0.0" + mdast-util-gfm-task-list-item@^0.1.0: version "0.1.4" resolved "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.4.tgz#5899b1320d1b826f7658ac3171babf4ae1e626a2" @@ -19181,6 +19539,14 @@ mdast-util-gfm-task-list-item@^0.1.0: dependencies: mdast-util-to-markdown "^0.5.0" +mdast-util-gfm-task-list-item@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.0.tgz#a0aa2a00c893f9f006d13ba096cbc64608559c7f" + integrity sha512-dwkzOTjQe8JCCHVE3Cb0pLHTYLudf7t9WCAnb20jI8/dW+VHjgWhjtIUVA3oigNkssgjEwX+i+3XesUdCnXGyA== + dependencies: + "@types/mdast" "^3.0.3" + mdast-util-to-markdown "^1.0.0" + mdast-util-gfm@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.0.tgz#bac0efe703670d1b40474e6be13dbdd887273a04" @@ -19191,6 +19557,16 @@ mdast-util-gfm@^0.1.0: mdast-util-gfm-table "^0.1.0" mdast-util-gfm-task-list-item "^0.1.0" +mdast-util-gfm@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-1.0.0.tgz#5cbedeabae72be2c3dff1ec958394e2f9553ec43" + integrity sha512-JY4qImsTqivQ0Gl3qvdaizCpomFaNrHnjEhNjNNKeNEA5jZHAJDYu1+yO4V9jn4/ti8GrKdAScaT4F71knoxsA== + dependencies: + mdast-util-gfm-autolink-literal "^1.0.0" + mdast-util-gfm-strikethrough "^1.0.0" + mdast-util-gfm-table "^1.0.0" + mdast-util-gfm-task-list-item "^1.0.0" + mdast-util-to-hast@10.0.1: version "10.0.1" resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" @@ -19205,6 +19581,21 @@ mdast-util-to-hast@10.0.1: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" +mdast-util-to-hast@^11.0.0: + version "11.3.0" + resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-11.3.0.tgz#ea9220617a710e80aa5cc3ac7cc9d4bb0440ae7a" + integrity sha512-4o3Cli3hXPmm1LhB+6rqhfsIUBjnKFlIUZvudaermXB+4/KONdd/W4saWWkC+LBLbPMqhFSSTSRgafHsT5fVJw== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + "@types/mdurl" "^1.0.0" + mdast-util-definitions "^5.0.0" + mdurl "^1.0.0" + unist-builder "^3.0.0" + unist-util-generated "^2.0.0" + unist-util-position "^4.0.0" + unist-util-visit "^4.0.0" + mdast-util-to-markdown@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.5.3.tgz#e05c54a3ccd239bab63c48a1e5b5747f0dcd5aca" @@ -19217,11 +19608,29 @@ mdast-util-to-markdown@^0.5.0: repeat-string "^1.0.0" zwitch "^1.0.0" +mdast-util-to-markdown@^1.0.0: + version "1.2.3" + resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.2.3.tgz#2b3af92bf0e29080927eb59a8a10cd0a7398e093" + integrity sha512-040jJYtjOUdbvYAXCfPrpLJRdvMOmR33KRqlhT4r+fEbVM+jao1RMbA8RmGeRmw8RAj3vQ+HvhIaJPijvnOwCg== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + longest-streak "^3.0.0" + mdast-util-to-string "^3.0.0" + micromark-util-decode-string "^1.0.0" + unist-util-visit "^4.0.0" + zwitch "^2.0.0" + mdast-util-to-string@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== +mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz#56c506d065fbf769515235e577b5a261552d56e9" + integrity sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA== + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -19429,6 +19838,37 @@ microevent.ts@~0.1.1: resolved "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== +micromark-core-commonmark@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.1.tgz#a64987cafe872e8b80bc8f2352a5d988586ac4f1" + integrity sha512-vEOw8hcQ3nwHkKKNIyP9wBi8M50zjNajtmI+cCUWcVfJS+v5/3WCh4PLKf7PPRZFUutjzl4ZjlHwBWUKfb/SkA== + dependencies: + micromark-factory-destination "^1.0.0" + micromark-factory-label "^1.0.0" + micromark-factory-space "^1.0.0" + micromark-factory-title "^1.0.0" + micromark-factory-whitespace "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-html-tag-name "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + parse-entities "^3.0.0" + +micromark-extension-gfm-autolink-literal@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.0.tgz#1a49a62bfcb00f9dff87ab39f3b21a108612dc24" + integrity sha512-t+K0aPK32mXypVTEKV+WRfoT/Rb7MERDgHZVRr56NXpyQQhgMk72QnK4NljYUlrgbuesH+MxiPQwThzqRDIwvA== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + micromark-extension-gfm-autolink-literal@~0.5.0: version "0.5.1" resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.1.tgz#5326fc86f3ae0fbba57bb0bfc2f158c9456528ce" @@ -19436,6 +19876,17 @@ micromark-extension-gfm-autolink-literal@~0.5.0: dependencies: micromark "~2.10.0" +micromark-extension-gfm-strikethrough@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.1.tgz#9f53ab4f5dc8c0525a889850bae615f074a98a27" + integrity sha512-fzGYXWz9HPWH1uHqYwdyR8XpEtuoYVHUjTdPQTnl3ETVZOQe1NXMwE3RA7AMqeON52hG+kO9g1/P1+pLONBSMQ== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-classify-character "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + micromark-extension-gfm-strikethrough@~0.6.0: version "0.6.2" resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.2.tgz#754788bdd13046e7f69edaa0d3f3d555d23128d6" @@ -19443,6 +19894,16 @@ micromark-extension-gfm-strikethrough@~0.6.0: dependencies: micromark "~2.10.0" +micromark-extension-gfm-table@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.0.tgz#f0d35dbf008b6182311049f9137323d34a54c7a0" + integrity sha512-OATRuHDgEAT/aaJJRSdU12V+s01kNSnJ0jumdfLq5mPy0F5DkR3zbTSFLH4tjVYM0/kEG6umxIhHY62mFe4z5Q== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + micromark-extension-gfm-table@~0.4.0: version "0.4.1" resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.1.tgz#79cc37da82d6ae0cc3901c1c6264b97a72372fbd" @@ -19450,11 +19911,28 @@ micromark-extension-gfm-table@~0.4.0: dependencies: micromark "~2.10.0" +micromark-extension-gfm-tagfilter@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.0.tgz#a38c7c462c2007b534fcb485e9310165879654a7" + integrity sha512-GGUZhzQrOdHR8RHU2ru6K+4LMlj+pBdNuXRtw5prOflDOk2hHqDB0xEgej1AHJ2VETeycX7tzQh2EmaTUOmSKg== + dependencies: + micromark-util-types "^1.0.0" + micromark-extension-gfm-tagfilter@~0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz#d9f26a65adee984c9ccdd7e182220493562841ad" integrity sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q== +micromark-extension-gfm-task-list-item@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.0.tgz#ab38b2b4ead4e746189d6323c32cacab2c63599d" + integrity sha512-3tkHCq1NNwijtwpjYba9+rl1yvQ4xYg8iQpUAfTJRyq8MtIEsBUF/vW6B9Gh8Qwy1hE2FmpyHhP4jnFAt61zLg== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + micromark-extension-gfm-task-list-item@~0.3.0: version "0.3.1" resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.1.tgz#24b7f54936a609179595f87a0e5279d1c6cf346c" @@ -19472,6 +19950,187 @@ micromark-extension-gfm@^0.3.0: micromark-extension-gfm-tagfilter "~0.3.0" micromark-extension-gfm-task-list-item "~0.3.0" +micromark-extension-gfm@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-1.0.0.tgz#7a1cee0df200e3f2ab14cb63d7a5ea6820a82f1d" + integrity sha512-OjqbQPL1Vec/4l5hnC8WnMNmWwgrT9JvzR2udqIGrGKecZsdwY9GAWZ5482CuD12SXuHNj8aS8epni6ip0Pwog== + dependencies: + micromark-extension-gfm-autolink-literal "^1.0.0" + micromark-extension-gfm-strikethrough "^1.0.0" + micromark-extension-gfm-table "^1.0.0" + micromark-extension-gfm-tagfilter "^1.0.0" + micromark-extension-gfm-task-list-item "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-destination@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e" + integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-label@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.0.tgz#b316ec479b474232973ff13b49b576f84a6f2cbb" + integrity sha512-XWEucVZb+qBCe2jmlOnWr6sWSY6NHx+wtpgYFsm4G+dufOf6tTQRRo0bdO7XSlGPu5fyjpJenth6Ksnc5Mwfww== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-space@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz#cebff49968f2b9616c0fcb239e96685cb9497633" + integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-title@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.0.tgz#708f7a8044f34a898c0efdb4f55e4da66b537273" + integrity sha512-flvC7Gx0dWVWorXuBl09Cr3wB5FTuYec8pMGVySIp2ZlqTcIjN/lFohZcP0EG//krTptm34kozHk7aK/CleCfA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-whitespace@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz#e991e043ad376c1ba52f4e49858ce0794678621c" + integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz#d97c54d5742a0d9611a68ca0cd4124331f264d86" + integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-chunked@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06" + integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-classify-character@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz#cbd7b447cb79ee6997dd274a46fc4eb806460a20" + integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-combine-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz#91418e1e74fb893e3628b8d496085639124ff3d5" + integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-decode-numeric-character-reference@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz#dcc85f13b5bd93ff8d2868c3dba28039d490b946" + integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-decode-string@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.0.tgz#f97946825231d9c97df767875064401774578a6e" + integrity sha512-4g5UJ8P/J8wuRKUXCcB7udQuOBXpLyvBQSLSuznfBLCG+thKG6UTwFnXfHkrr/1wddprkUbPatCzxDjrJ+5zDg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + parse-entities "^3.0.0" + +micromark-util-encode@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.0.tgz#c409ecf751a28aa9564b599db35640fccec4c068" + integrity sha512-cJpFVM768h6zkd8qJ1LNRrITfY4gwFt+tziPcIf71Ui8yFzY9wG3snZQqiWVq93PG4Sw6YOtcNiKJfVIs9qfGg== + +micromark-util-html-tag-name@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.0.0.tgz#75737e92fef50af0c6212bd309bc5cb8dbd489ed" + integrity sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g== + +micromark-util-normalize-identifier@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz#4a3539cb8db954bbec5203952bfe8cedadae7828" + integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg== + dependencies: + micromark-util-symbol "^1.0.0" + +micromark-util-resolve-all@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz#a7c363f49a0162e931960c44f3127ab58f031d88" + integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw== + dependencies: + micromark-util-types "^1.0.0" + +micromark-util-sanitize-uri@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz#27dc875397cd15102274c6c6da5585d34d4f12b2" + integrity sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-symbol "^1.0.0" + +micromark-util-subtokenize@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.0.tgz#6f006fa719af92776c75a264daaede0fb3943c6a" + integrity sha512-EsnG2qscmcN5XhkqQBZni/4oQbLFjz9yk3ZM/P8a3YUjwV6+6On2wehr1ALx0MxK3+XXXLTzuBKHDFeDFYRdgQ== + dependencies: + micromark-util-chunked "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-symbol@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz#91cdbcc9b2a827c0129a177d36241bcd3ccaa34d" + integrity sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ== + +micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.1.tgz#8bb8a092d93d326bd29fe29602799f2d0d922fd4" + integrity sha512-UT0ylWEEy80RFYzK9pEaugTqaxoD/j0Y9WhHpSyitxd99zjoQz7JJ+iKuhPAgOW2MiPSUAx+c09dcqokeyaROA== + +micromark@^3.0.0: + version "3.0.5" + resolved "https://registry.npmjs.org/micromark/-/micromark-3.0.5.tgz#d24792c8a06f201d5608c106dbfadef34c299684" + integrity sha512-QfjERBnPw0G9mxhOCkkbRP0n8SX8lIBLrEKeEVceviUukqVMv3hWE4AgNTOK/W6GWqtPvvIHg2Apl3j1Dxm6aQ== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + micromark-core-commonmark "^1.0.1" + micromark-factory-space "^1.0.0" + micromark-util-character "^1.0.0" + micromark-util-chunked "^1.0.0" + micromark-util-combine-extensions "^1.0.0" + micromark-util-decode-numeric-character-reference "^1.0.0" + micromark-util-encode "^1.0.0" + micromark-util-normalize-identifier "^1.0.0" + micromark-util-resolve-all "^1.0.0" + micromark-util-sanitize-uri "^1.0.0" + micromark-util-subtokenize "^1.0.0" + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.1" + parse-entities "^3.0.0" + micromark@~2.10.0: version "2.10.1" resolved "https://registry.npmjs.org/micromark/-/micromark-2.10.1.tgz#cd73f54e0656f10e633073db26b663a221a442a7" @@ -21140,6 +21799,18 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-3.0.0.tgz#9ed6d6569b6cfc95ade058d683ddef239dad60dc" + integrity sha512-AJlcIFDNPEP33KyJLguv0xJc83BNvjxwpuUIcetyXUsLpVXAUCePJ5kIoYtEN2R1ac0cYaRu/vk9dVFkewHQhQ== + dependencies: + character-entities "^2.0.0" + character-entities-legacy "^2.0.0" + character-reference-invalid "^2.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse-filepath@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -22233,17 +22904,7 @@ precond@0.2: resolved "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" integrity sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw= -preferred-pm@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.2.tgz#bbdbef1014e34a7490349bf70d6d244b8d57a5e1" - integrity sha512-yGIxyBkK/OWOppgCXfOeOXOeNrddyK1DzqS6XpOokRZb2ogXTpHRhKDTO7d0pjF/2p2sV9pEkKL4e0tNZI1y2A== - dependencies: - find-up "^5.0.0" - find-yarn-workspace-root2 "1.2.16" - path-exists "^4.0.0" - which-pm "2.0.0" - -preferred-pm@^3.0.3: +preferred-pm@^3.0.0, preferred-pm@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz#1b6338000371e3edbce52ef2e4f65eb2e73586d6" integrity sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ== @@ -22477,6 +23138,11 @@ property-information@^5.3.0: dependencies: xtend "^4.0.0" +property-information@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/property-information/-/property-information-6.0.1.tgz#7c668d9f2b9cb63bc3e105d8b8dfee7221a17800" + integrity sha512-F4WUUAF7fMeF4/JUFHNBWDaKDXi2jbvqBW/y6o5wsf3j19wTZ7S60TmtB5HoBhtgw7NKQRMWuz5vk2PR0CygUg== + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -22943,6 +23609,11 @@ react-hook-form@^7.12.2: resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.16.1.tgz#669046df378a71949e5cf8a2398cbe20d5cb27bc" integrity sha512-kcLDmSmlyLUFx2UU5bG/o4+3NeK753fhKodJa8gkplXohGkpAq0/p+TR24OWjZmkEc3ES7ppC5v5d6KUk+fJTA== +react-hook-form@^7.13.0: + version "7.17.4" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.17.4.tgz#232b6aaccddb91eb4a228ac20b154abd90866fdb" + integrity sha512-7XhbCr7d9fDC1TgcK/BUbt7D3q0VJMu7jPErfsa0JrxVjv/nni41xWdJcy0Zb7R+Np8OsCkQ2lMyloAtE3DLiQ== + react-hot-loader@^4.12.21: version "4.13.0" resolved "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202" @@ -22983,7 +23654,7 @@ react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-i resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2: +"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.1, react-is@^17.0.2: version "17.0.2" resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -23024,6 +23695,25 @@ react-markdown@^5.0.2: unist-util-visit "^2.0.0" xtend "^4.0.1" +react-markdown@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-7.0.1.tgz#c7365fcd7d1813b3ae68f2200e8f92d47d865627" + integrity sha512-pthNPaoiwg0q7hukoE04F2ENwSzijIlWHJ4UMs/96LUe/G/P3FnbP4qHzx3FoNqae+2SqDG8vzniTLnJDeWneg== + dependencies: + "@types/hast" "^2.0.0" + "@types/unist" "^2.0.0" + comma-separated-tokens "^2.0.0" + prop-types "^15.0.0" + property-information "^6.0.0" + react-is "^17.0.0" + remark-parse "^10.0.0" + remark-rehype "^9.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.3.0" + unified "^10.0.0" + unist-util-visit "^4.0.0" + vfile "^5.0.0" + react-popper-tooltip@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/react-popper-tooltip/-/react-popper-tooltip-3.1.1.tgz#329569eb7b287008f04fcbddb6370452ad3f9eac" @@ -23727,6 +24417,16 @@ remark-gfm@^1.0.0: mdast-util-gfm "^0.1.0" micromark-extension-gfm "^0.3.0" +remark-gfm@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-2.0.0.tgz#4fef4d7b35afa2bd3cd0410d313d32169634051c" + integrity sha512-waIv4Tjcd2CTUDxKRYzuPyIHw1FoX4H2GjXAzXV9PxQWb+dU4fJivd/FZ+nxyzPARrqTjMIkwIwPoWNbpBhjcQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-gfm "^1.0.0" + micromark-extension-gfm "^1.0.0" + unified "^10.0.0" + remark-mdx@1.6.22: version "1.6.22" resolved "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" @@ -23763,6 +24463,15 @@ remark-parse@8.0.3: vfile-location "^3.0.0" xtend "^4.0.1" +remark-parse@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.0.tgz#65e2b2b34d8581d36b97f12a2926bb2126961cb4" + integrity sha512-07ei47p2Xl7Bqbn9H2VYQYirnAFJPwdMuypdozWsSbnmrkgA2e2sZLZdnDNrrsxR4onmIzH/J6KXqKxCuqHtPQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-from-markdown "^1.0.0" + unified "^10.0.0" + remark-parse@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" @@ -23770,6 +24479,16 @@ remark-parse@^9.0.0: dependencies: mdast-util-from-markdown "^0.8.0" +remark-rehype@^9.0.0: + version "9.1.0" + resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-9.1.0.tgz#e4b5b6e19c125b3780343eb66c3e9b99b0f06a81" + integrity sha512-oLa6YmgAYg19zb0ZrBACh40hpBLteYROaPLhBXzLgjqyHQrN+gVP9N/FJvfzuNNuzCutktkroXEZBrxAxKhh7Q== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + mdast-util-to-hast "^11.0.0" + unified "^10.0.0" + remark-squeeze-paragraphs@4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" @@ -24405,20 +25124,13 @@ select-hose@^2.0.0: resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.11: +selfsigned@^1.10.11, selfsigned@^1.10.7: version "1.10.11" resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== dependencies: node-forge "^0.10.0" -selfsigned@^1.10.7: - version "1.10.8" - resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" - integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== - dependencies: - node-forge "^0.10.0" - semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -24991,6 +25703,11 @@ space-separated-tokens@^1.0.0: resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== +space-separated-tokens@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b" + integrity sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw== + spawn-command@^0.0.2-1: version "0.0.2-1" resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" @@ -25785,10 +26502,10 @@ svg-parser@^2.0.2: resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== -svg-pathdata@^5.0.5: - version "5.0.5" - resolved "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-5.0.5.tgz#65e8d765642ba15fe15434444087d082bc526b29" - integrity sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow== +svg-pathdata@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac" + integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== svgo@^1.0.0, svgo@^1.2.2: version "1.3.2" @@ -25809,7 +26526,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -swagger-client@^3.16.1: +swagger-client@3.16.1, swagger-client@^3.16.1: version "3.16.1" resolved "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.1.tgz#df86c9d407ab52c00cb356e714b0ec732bb3ad40" integrity sha512-BcNRQzXHRGuXfhN0f80ptlr+bSaPvXwo8+gWbpmTnbKdAjcWOKAWwUx7rgGHjTKZh0qROr/GX9xOZIY8LrBuTg== @@ -26507,6 +27224,11 @@ trough@^1.0.0: resolved "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +trough@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/trough/-/trough-2.0.2.tgz#94a3aa9d5ce379fc561f6244905b3f36b7458d96" + integrity sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w== + tryer@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -26910,6 +27632,19 @@ unified@9.2.0, unified@^9.0.0: trough "^1.0.0" vfile "^4.0.0" +unified@^10.0.0: + version "10.1.0" + resolved "https://registry.npmjs.org/unified/-/unified-10.1.0.tgz#4e65eb38fc2448b1c5ee573a472340f52b9346fe" + integrity sha512-4U3ru/BRXYYhKbwXV6lU6bufLikoAavTwev89H5UxY8enDFaAT2VXmIXYNm6hb5oHPng/EXr77PVyDFcptbk5g== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -26956,21 +27691,43 @@ unist-builder@2.0.3, unist-builder@^2.0.0: resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== +unist-builder@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" + integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-generated@^1.0.0: version "1.1.6" resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== +unist-util-generated@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113" + integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw== + unist-util-is@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.3.tgz#e8b44db55fc20c43752b3346c116344d45d7c91d" integrity sha512-bTofCFVx0iQM8Jqb1TBDVRIQW03YkD3p66JOd/aCWuqzlLyUtx1ZAGw/u+Zw+SttKvSVcvTiKYbfrtLoLefykw== +unist-util-is@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" + integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ== + unist-util-position@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== +unist-util-position@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.1.tgz#f8484b2da19a897a0180556d160c28633070dbb9" + integrity sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA== + unist-util-remove-position@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" @@ -26992,6 +27749,13 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" +unist-util-stringify-position@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz#d517d2883d74d0daa0b565adc3d10a02b4a8cde9" + integrity sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA== + dependencies: + "@types/unist" "^2.0.0" + unist-util-visit-parents@1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" @@ -27005,6 +27769,22 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" +unist-util-visit-parents@^4.0.0: + version "4.1.1" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2" + integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit-parents@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521" + integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit@2.0.3, unist-util-visit@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" @@ -27014,6 +27794,24 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0: unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" +unist-util-visit@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b" + integrity sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^4.0.0" + +unist-util-visit@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz#f41e407a9e94da31594e6b1c9811c51ab0b3d8f5" + integrity sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.0.0" + universal-github-app-jwt@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.0.tgz#0abaa876101cdf1d3e4c546be2768841c0c1b514" @@ -27440,6 +28238,14 @@ vfile-message@^2.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^2.0.0" +vfile-message@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-3.0.2.tgz#db7eaebe7fecb853010f2ef1664427f52baf8f74" + integrity sha512-UUjZYIOg9lDRwwiBAuezLIsu9KlXntdxwG+nXnjuQAHvBpcX3x0eN8h+I7TkY5nkCXj+cWVp4ZqebtGBvok8ww== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" @@ -27451,6 +28257,16 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +vfile@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/vfile/-/vfile-5.1.0.tgz#18e78016f0f71e98d737d40f0fca921dc264a600" + integrity sha512-4o7/DJjEaFPYSh0ckv5kcYkJTHQgCKdL8ozMM1jLAxO9ox95IzveDPXCZp08HamdWq8JXTkClDvfAKaeLQeKtg== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + vinyl-file@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz#b104d9e4409ffa325faadd520642d0a3b488b365" @@ -28474,3 +29290,8 @@ zwitch@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== + +zwitch@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" + integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==