diff --git a/.changeset/big-planets-train.md b/.changeset/big-planets-train.md new file mode 100644 index 0000000000..3639c3546c --- /dev/null +++ b/.changeset/big-planets-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-gcalendar': minor +--- + +Added Google calendar widget diff --git a/ADOPTERS.md b/ADOPTERS.md index 1961054237..35a0e4ccc3 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -2,105 +2,105 @@ _If you're using Backstage in your organization, please try to add your company name to this list. This really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact._ -| Organization | Contact | Description of Use | -| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@dschwank](https://github.com/dschwank), [@iammnils](https://github.com/iammnils) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | -| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | -| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | -| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | -| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | -| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | -| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | -| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | -| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | -| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | -| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | -| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | -| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | -| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | -| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | -| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | -| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | -| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | -| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | -| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | -| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | -| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | -| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | -| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | -| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | -| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | -| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | -| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | -| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | -| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | -| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | -| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | -| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | -| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. | -| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | -| [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. | -| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | -| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | -| [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | -| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | -| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | -| [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! | -| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. | -| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 | -| [GoTo](https://www.goto.com) | [Lorenzo Orsatti](https://github.com/lorsatti) | Improve onboarding experience of new developers. Discover faster and painlessly developer documentation, API definitions and team information. Provide useful dev metrics in a central place. Provide easy-to-use templates for new services. | -| [Telstra](https://www.telstra.com.au) | [@kiranpatel11](https://github.com/kiranpatel11), [JasonC](https://github.com/JasonC17) | Primary usage: software catalog and templates
Emerging usage : TechDocs, Explore Ecosystem, TechRadar, etc | -| [Mosaico](https://www.mosaico.com.br/) | [Wédney Yuri](https://github.com/wedneyyuri),[@tino.milton](https://github.com/miltonjacomini) | A centralized service catalog of our documentation for our service engineers. | -| [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | -| [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 | -| [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. | -| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | -| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | -| [EF Education First](https://www.ef.com) | [Daan Boerlage](https://github.com/runebaas), [Rafał Nowosielski](https://github.com/rnowosielski) | Our developer portal - primarily used for cataloging and scaffolding with the ambition to expand with more feature adoptions over time | -| [Power Home Remodeling](https://www.techatpower.com) | [Ben Langfeld](https://github.com/benlangfeld) | Developer portal to our internal services, build on open-source software (including Kubernetes) in our own datacenters. Our Portal allows our team members to navigate inherant complexity and standardise. | -| [Livspace](https://www.livspace.com) | [Praveen Kumar](https://github.com/praveen-livspace) | Developer portal, service catalog, tech docs, API docs and plugins | -| [Just Eat Takeaway](https://www.justeattakeaway.com) | [Kim Wilson](https://github.com/kwilson541) | Our developer portal which centralises applications, reduces cognitive load and provides teams insights. | -| [Hopin](https://hopin.com) | [Vladimir Glafirov](https://github.com/vglafirov), [Chloe Lee](https://github.com/msfuko) | Developer portal to streamline the development practices. Integrated with service catalog, software templates, application monitoring, tech docs and plugins. | -| [HBO Max](https://hbomax.com) | [@mdb](https://github.com/mdb), [@nesta219](https://github.com/nesta219), [@nmische](https://github.com/nmische), [@hbomark](https://github.com/hbomark) | Developer portal hosting service catalog and API documentation, as well as cloud infrastructure details, operational visibility tools, and a custom plugin for browsing notable platform change events, such as deployments and configuration updates. | -| [RCHLO](https://www.riachuelo.com.br) & [MIDWAY](https://www.midway.com.br) | [@marcosborges](https://github.com/marcosborges), [@defaultbr](https://github.com/defaultbr) | Self-Service Platform | -| [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | -| [VMware](https://www.vmware.com) | [@mpriamo](https://github.com/mpriamo), [@krisapplegate](https://github.com/krisapplegate) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | -| [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | -| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | -| [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | -| [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | -| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@jamesdabbs-procore](https://github.com/jamesdabbs-procore) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | -| [Bradesco](https://banco.bradesco/html/classic/sobre/index.shtm) | [@danilosoarescardoso](https://github.com/danilosoarescardoso) | Centralized developer portal with software catalog, software templates, techdocs and some plugins from community and by our own. | -| [SeatGeek](https://seatgeek.com) | [@zhammer](https://github.com/zhammer) | Software catalog and documentation platform | -| [Grupo Boticário](https://www.grupoboticario.com.br/) | [@chicoribas](https://github.com/chicoribas), [@fndiaz](https://github.com/fndiaz), [@digogid](https://github.com/digogid), [@manumbs](https://github.com/manumbs), [@haooliveira84](https://github.com/haooliveira84), [@Rhullyam](https://github.com/Rhullyam) | Centralized developer portal with catalog, documentation, and software templates to build a ready to go application, including pipelines (CI/CD) and cloud services provisioning | -| [Snyk](https://snyk.io/) | [@robcresswell](https://github.com/robcresswell) | Developer portal for software discovery, API documentation and templating | -| [Stilingue](https://www.stilingue.com.br/) | [@stilingue-inteligencia-artificial](https://github.com/Stilingue-IA), [@bbviana](https://github.com/bbviana) | Developer portal, services catalog and centralization of metrics from Grafana, Sentry and GCP. Furthermore, centralization of documentation and infra details like DNS, Network services, SSL and so on. | -| [TUI Group](https://www.tuigroup.com/) | [Simon Stamm](https://github.com/simonstamm), [Christian Rudolph](https://github.com/ChrisRu82) | Developer portal for all engineer to provide discoverability to all internal components, APIs, documentation and to scaffold templates with integrations to our internal tools extended by plugins from our community. | -| [Alliander](https://www.alliander.com/) | [@leon-vg](https://github.com/leon-vg), [@gieljl](https://github.com/gieljl), [@niekteg](https://github.com/niekteg) | Developer portal - software catalog, technical documentation, software templates, tech radar and exploration of used tools/services | +| Organization | Contact | Description of Use | +| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@dschwank](https://github.com/dschwank), [@iammnils](https://github.com/iammnils) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | +| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | +| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | +| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | +| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | +| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | +| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | +| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | +| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | +| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | +| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | +| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | +| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | +| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | +| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. | +| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc | +| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. | +| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe | +| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. | +| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. | +| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. | +| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. | +| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. | +| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. | +| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. | +| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. | +| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. | +| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | +| [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. | +| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | +| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | +| [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | +| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | +| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | +| [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! | +| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. | +| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 | +| [GoTo](https://www.goto.com) | [Lorenzo Orsatti](https://github.com/lorsatti) | Improve onboarding experience of new developers. Discover faster and painlessly developer documentation, API definitions and team information. Provide useful dev metrics in a central place. Provide easy-to-use templates for new services. | +| [Telstra](https://www.telstra.com.au) | [@kiranpatel11](https://github.com/kiranpatel11), [JasonC](https://github.com/JasonC17) | Primary usage: software catalog and templates
Emerging usage : TechDocs, Explore Ecosystem, TechRadar, etc | +| [Mosaico](https://www.mosaico.com.br/) | [Wédney Yuri](https://github.com/wedneyyuri),[@tino.milton](https://github.com/miltonjacomini) | A centralized service catalog of our documentation for our service engineers. | +| [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | +| [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 | +| [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. | +| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | +| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | +| [EF Education First](https://www.ef.com) | [Daan Boerlage](https://github.com/runebaas), [Rafał Nowosielski](https://github.com/rnowosielski) | Our developer portal - primarily used for cataloging and scaffolding with the ambition to expand with more feature adoptions over time | +| [Power Home Remodeling](https://www.techatpower.com) | [Ben Langfeld](https://github.com/benlangfeld) | Developer portal to our internal services, build on open-source software (including Kubernetes) in our own datacenters. Our Portal allows our team members to navigate inherant complexity and standardise. | +| [Livspace](https://www.livspace.com) | [Praveen Kumar](https://github.com/praveen-livspace) | Developer portal, service catalog, tech docs, API docs and plugins | +| [Just Eat Takeaway](https://www.justeattakeaway.com) | [Kim Wilson](https://github.com/kwilson541) | Our developer portal which centralises applications, reduces cognitive load and provides teams insights. | +| [Hopin](https://hopin.com) | [Vladimir Glafirov](https://github.com/vglafirov), [Chloe Lee](https://github.com/msfuko) | Developer portal to streamline the development practices. Integrated with service catalog, software templates, application monitoring, tech docs and plugins. | +| [HBO Max](https://hbomax.com) | [@mdb](https://github.com/mdb), [@nesta219](https://github.com/nesta219), [@nmische](https://github.com/nmische), [@hbomark](https://github.com/hbomark) | Developer portal hosting service catalog and API documentation, as well as cloud infrastructure details, operational visibility tools, and a custom plugin for browsing notable platform change events, such as deployments and configuration updates. | +| [RCHLO](https://www.riachuelo.com.br) & [MIDWAY](https://www.midway.com.br) | [@marcosborges](https://github.com/marcosborges), [@defaultbr](https://github.com/defaultbr) | Self-Service Platform | +| [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | +| [VMware](https://www.vmware.com) | [@mpriamo](https://github.com/mpriamo), [@krisapplegate](https://github.com/krisapplegate) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | +| [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | +| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | +| [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | +| [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | +| [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@jamesdabbs-procore](https://github.com/jamesdabbs-procore) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | +| [Bradesco](https://banco.bradesco/html/classic/sobre/index.shtm) | [@danilosoarescardoso](https://github.com/danilosoarescardoso) | Centralized developer portal with software catalog, software templates, techdocs and some plugins from community and by our own. | +| [SeatGeek](https://seatgeek.com) | [@zhammer](https://github.com/zhammer) | Software catalog and documentation platform | +| [Grupo Boticário](https://www.grupoboticario.com.br/) | [@chicoribas](https://github.com/chicoribas), [@fndiaz](https://github.com/fndiaz), [@digogid](https://github.com/digogid), [@manumbs](https://github.com/manumbs), [@haooliveira84](https://github.com/haooliveira84), [@Rhullyam](https://github.com/Rhullyam) | Centralized developer portal with catalog, documentation, and software templates to build a ready to go application, including pipelines (CI/CD) and cloud services provisioning | +| [Snyk](https://snyk.io/) | [@robcresswell](https://github.com/robcresswell) | Developer portal for software discovery, API documentation and templating | +| [Stilingue](https://www.stilingue.com.br/) | [@stilingue-inteligencia-artificial](https://github.com/Stilingue-IA), [@bbviana](https://github.com/bbviana) | Developer portal, services catalog and centralization of metrics from Grafana, Sentry and GCP. Furthermore, centralization of documentation and infra details like DNS, Network services, SSL and so on. | +| [TUI Group](https://www.tuigroup.com/) | [Simon Stamm](https://github.com/simonstamm), [Christian Rudolph](https://github.com/ChrisRu82) | Developer portal for all engineer to provide discoverability to all internal components, APIs, documentation and to scaffold templates with integrations to our internal tools extended by plugins from our community. | +| [Alliander](https://www.alliander.com/) | [@leon-vg](https://github.com/leon-vg), [@gieljl](https://github.com/gieljl), [@niekteg](https://github.com/niekteg) | Developer portal - software catalog, technical documentation, software templates, tech radar and exploration of used tools/services | diff --git a/packages/app/package.json b/packages/app/package.json index ee5affe302..bfd1b35008 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,6 +29,7 @@ "@backstage/plugin-code-coverage": "^0.1.29", "@backstage/plugin-cost-insights": "^0.11.24", "@backstage/plugin-explore": "^0.3.33", + "@backstage/plugin-gcalendar": "^0.1.0", "@backstage/plugin-gcp-projects": "^0.3.21", "@backstage/plugin-github-actions": "^0.5.2", "@backstage/plugin-gocd": "^0.1.8", @@ -59,10 +60,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^18.5.3", + "@roadiehq/backstage-plugin-buildkite": "^1.3.8", "@roadiehq/backstage-plugin-github-insights": "^1.5.0", "@roadiehq/backstage-plugin-github-pull-requests": "^1.4.0", "@roadiehq/backstage-plugin-travis-ci": "^1.3.6", - "@roadiehq/backstage-plugin-buildkite": "^1.3.8", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 374d637b2e..befd3274f3 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -76,7 +76,7 @@ import { hot } from 'react-hot-loader/root'; import { Navigate, Route } from 'react-router'; import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; -import { HomePage } from './components/home/HomePage'; +import { homePage } from './components/home/HomePage'; import { Root } from './components/Root'; import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions'; import { searchPage } from './components/search/SearchPage'; @@ -132,7 +132,7 @@ const routes = ( {/* TODO(rubenl): Move this to / once its more mature and components exist */} }> - + {homePage} } /> ( +export const homePage = (
} pageTitleOverride="Home"> @@ -100,6 +101,9 @@ export const HomePage = () => ( ]} /> + + + diff --git a/plugins/gcalendar/.eslintrc.js b/plugins/gcalendar/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/gcalendar/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/gcalendar/README.md b/plugins/gcalendar/README.md new file mode 100644 index 0000000000..ccfcf9c4eb --- /dev/null +++ b/plugins/gcalendar/README.md @@ -0,0 +1,28 @@ +# Google calendar plugin + +Plugin displays events from google calendar + +## Getting started + +The plugin exports `HomePageCalendar` widget for the Homepage. +If your homepage is not static JSX add `gcalendarApiRef` to the App's `apis.ts`: + +```ts +import { + GCalendarApiClient, + gcalendarApiRef, +} from '@backstage/plugin-gcalendar'; + +export const apis = [ + // ... + createApiFactory({ + api: gcalendarApiRef, + deps: { authApi: googleAuthApiRef, fetchApi: fetchApiRef }, + factory: deps => new GCalendarApiClient(deps), + }), +]; +``` + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/plugins/gcalendar/api-report.md b/plugins/gcalendar/api-report.md new file mode 100644 index 0000000000..276c144400 --- /dev/null +++ b/plugins/gcalendar/api-report.md @@ -0,0 +1,88 @@ +## API Report File for "@backstage/plugin-gcalendar" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { FetchApi } from '@backstage/core-plugin-api'; +import { OAuthApi } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "EventAttendee" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type EventAttendee = gapi.client.calendar.EventAttendee; + +// Warning: (ae-missing-release-tag) "GCalendar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type GCalendar = gapi.client.calendar.CalendarListEntry; + +// Warning: (ae-missing-release-tag) "GCalendarApiClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class GCalendarApiClient { + // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts + constructor(options: Options); + // (undocumented) + getCalendars(params?: any): Promise<{ + items: GCalendar[]; + }>; + // (undocumented) + getEvents( + calendarId: string, + params?: any, + ): Promise<{ + items: GCalendarEvent[]; + }>; +} + +// Warning: (ae-missing-release-tag) "gcalendarApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const gcalendarApiRef: ApiRef; + +// Warning: (ae-missing-release-tag) "GCalendarEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type GCalendarEvent = gapi.client.calendar.Event & + Pick & + Pick & { + calendarId?: string; + }; + +// Warning: (ae-missing-release-tag) "gcalendarPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const gcalendarPlugin: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; + +// Warning: (ae-missing-release-tag) "HomePageCalendar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const HomePageCalendar: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "ResponseStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum ResponseStatus { + // (undocumented) + accepted = 'accepted', + // (undocumented) + declined = 'declined', + // (undocumented) + maybe = 'tentative', + // (undocumented) + needsAction = 'needsAction', +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/gcalendar/dev/index.tsx b/plugins/gcalendar/dev/index.tsx new file mode 100644 index 0000000000..807c2b4d77 --- /dev/null +++ b/plugins/gcalendar/dev/index.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2022 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 { Content, Page } from '@backstage/core-components'; +import { googleAuthApiRef } from '@backstage/core-plugin-api'; +import { createDevApp } from '@backstage/dev-utils'; +import { calendarListMock, eventsMock } from './mocks'; +import { gcalendarPlugin, HomePageCalendar } from '../src/plugin'; +import { gcalendarApiRef } from '../src'; + +createDevApp() + .registerPlugin(gcalendarPlugin) + .registerApi({ + api: googleAuthApiRef, + deps: {}, + factory: () => + ({ + async getAccessToken() { + return Promise.resolve('token'); + }, + } as unknown as typeof googleAuthApiRef.T), + }) + .registerApi({ + api: gcalendarApiRef, + deps: {}, + factory: () => + ({ + async getCalendars() { + return Promise.resolve(calendarListMock); + }, + async getEvents() { + return Promise.resolve({ + items: eventsMock, + }); + }, + } as unknown as typeof gcalendarApiRef.T), + }) + .addPage({ + element: ( + + + + + + ), + title: 'Root Page', + }) + .render(); diff --git a/plugins/gcalendar/dev/mocks.ts b/plugins/gcalendar/dev/mocks.ts new file mode 100644 index 0000000000..c566527590 --- /dev/null +++ b/plugins/gcalendar/dev/mocks.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2022 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 { DateTime } from 'luxon'; +import { GCalendar, GCalendarEvent } from '../src/api'; + +const primaryCalendar: GCalendar = { + id: 'calendar-1', + summary: 'test-1@test.com', + primary: true, +}; +const nonPrimaryCalendar: GCalendar = { + id: 'calendar-2', + summary: 'test-2@test.com', + primary: false, + backgroundColor: '#9BF0E1', +}; + +export const calendarListMock = { + items: [primaryCalendar, nonPrimaryCalendar], +}; + +export const eventsMock: GCalendarEvent[] = [...Array(3).keys()].map(i => ({ + id: i.toString(), + summary: `Meeting ${i + 1}`, + start: { + dateTime: DateTime.now() + .minus({ hour: i + 1 }) + .toISO(), + timeZone: 'Europe/London', + }, + end: { + dateTime: DateTime.now().minus({ hour: i }).toISO(), + timeZone: 'Europe/London', + }, + description: '

Dummy title

Dummy description

', + conferenceData: { + entryPoints: [ + { + entryPointType: 'video', + uri: 'https://zoom.us/', + }, + ], + }, +})); diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json new file mode 100644 index 0000000000..faf5d5e323 --- /dev/null +++ b/plugins/gcalendar/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/plugin-gcalendar", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/core-components": "^0.9.1", + "@backstage/core-plugin-api": "^0.8.0", + "@backstage/errors": "^0.2.2", + "@backstage/theme": "^0.2.15", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "classnames": "^2.3.1", + "cross-fetch": "^3.1.5", + "dompurify": "^2.3.6", + "lodash": "^4.17.21", + "luxon": "^2.3.0", + "material-ui-popup-state": "^1.9.3", + "react-query": "^3.34.16", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.15.2", + "@backstage/core-app-api": "^0.6.0", + "@backstage/dev-utils": "^0.2.25", + "@backstage/test-utils": "^0.3.0", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/dompurify": "^2.3.3", + "@types/gapi": "^0.0.41", + "@types/gapi.auth2": "^0.0.56", + "@types/gapi.client.calendar": "^3.0.10", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "@types/sanitize-html": "^2.6.2", + "cross-fetch": "^3.1.5", + "msw": "^0.35.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/gcalendar/src/api/client.ts b/plugins/gcalendar/src/api/client.ts new file mode 100644 index 0000000000..5b6ea96339 --- /dev/null +++ b/plugins/gcalendar/src/api/client.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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 { OAuthApi, createApiRef, FetchApi } from '@backstage/core-plugin-api'; + +import { GCalendar, GCalendarEvent } from './types'; +import { ResponseError } from '@backstage/errors'; + +type Options = { + authApi: OAuthApi; + fetchApi: FetchApi; +}; + +export const gcalendarApiRef = createApiRef({ + id: 'plugin.gcalendar.service', +}); + +export class GCalendarApiClient { + private readonly authApi: OAuthApi; + private readonly fetchApi: FetchApi; + + constructor(options: Options) { + this.authApi = options.authApi; + this.fetchApi = options.fetchApi; + } + + private async get( + path: string, + params: { [key in string]: any }, + ): Promise { + const query = new URLSearchParams(params); + const url = new URL( + `${path}?${query.toString()}`, + 'https://www.googleapis.com', + ); + const token = await this.authApi.getAccessToken(); + const response = await this.fetchApi.fetch(url.toString(), { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json() as Promise; + } + + public async getCalendars(params?: any) { + return this.get<{ items: GCalendar[] }>( + '/calendar/v3/users/me/calendarList', + params, + ); + } + + public async getEvents(calendarId: string, params?: any) { + return this.get<{ items: GCalendarEvent[] }>( + `/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`, + params, + ); + } +} diff --git a/plugins/gcalendar/src/api/index.ts b/plugins/gcalendar/src/api/index.ts new file mode 100644 index 0000000000..4c195158d3 --- /dev/null +++ b/plugins/gcalendar/src/api/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 './client'; +export * from './types'; diff --git a/plugins/gcalendar/src/api/types.ts b/plugins/gcalendar/src/api/types.ts new file mode 100644 index 0000000000..1423b91aca --- /dev/null +++ b/plugins/gcalendar/src/api/types.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2022 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 type GCalendar = gapi.client.calendar.CalendarListEntry; + +export type EventAttendee = gapi.client.calendar.EventAttendee; + +export type GCalendarEvent = gapi.client.calendar.Event & + Pick & + Pick & { + calendarId?: string; + }; + +export enum ResponseStatus { + needsAction = 'needsAction', + accepted = 'accepted', + declined = 'declined', + maybe = 'tentative', +} diff --git a/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.test.tsx b/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.test.tsx new file mode 100644 index 0000000000..64485e74ab --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.test.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2022 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 { renderInTestApp } from '@backstage/test-utils'; + +import { AttendeeChip } from './AttendeeChip'; +import { EventAttendee, ResponseStatus } from '../../api'; + +describe('', () => { + it('renders attendee email', async () => { + const email = 'test@test.com'; + const user: EventAttendee = { + email, + responseStatus: ResponseStatus.needsAction, + }; + const { queryByText } = await renderInTestApp(); + expect(queryByText(email)).toBeInTheDocument(); + }); + + it('renders accepted icon', async () => { + const email = 'test@test.com'; + const user: EventAttendee = { + email, + responseStatus: ResponseStatus.accepted, + }; + const { getByTestId } = await renderInTestApp(); + expect(getByTestId('accepted-icon')).toBeInTheDocument(); + }); + + it('renders declined icon', async () => { + const email = 'test@test.com'; + const user: EventAttendee = { + email, + responseStatus: ResponseStatus.declined, + }; + const { getByTestId } = await renderInTestApp(); + expect(getByTestId('declined-icon')).toBeInTheDocument(); + }); +}); diff --git a/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.tsx b/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.tsx new file mode 100644 index 0000000000..dd4d743572 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/AttendeeChip.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2022 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 { BackstageTheme } from '@backstage/theme'; + +import { Badge, Chip, makeStyles } from '@material-ui/core'; +import CancelIcon from '@material-ui/icons/Cancel'; +import CheckIcon from '@material-ui/icons/CheckCircle'; + +import { EventAttendee, ResponseStatus } from '../../api'; + +const useStyles = makeStyles((theme: BackstageTheme) => { + const getIconColor = (responseStatus?: string) => { + if (!responseStatus) return theme.palette.primary.light; + + return { + [ResponseStatus.accepted]: theme.palette.status.ok, + [ResponseStatus.declined]: theme.palette.status.error, + }[responseStatus]; + }; + + return { + responseStatus: { + color: ({ responseStatus }: { responseStatus?: string }) => + getIconColor(responseStatus), + }, + badge: { + right: 10, + top: 5, + '& svg': { + height: 16, + width: 16, + background: '#fff', + }, + }, + }; +}); + +const ResponseIcon = ({ responseStatus }: any) => { + if (responseStatus === ResponseStatus.accepted) { + return ; + } + if (responseStatus === ResponseStatus.declined) { + return ; + } + + return null; +}; + +type AttendeeChipProps = { + user: EventAttendee; +}; + +export const AttendeeChip = ({ user }: AttendeeChipProps) => { + const classes = useStyles({ responseStatus: user.responseStatus }); + + return ( + } + > + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx new file mode 100644 index 0000000000..7b7c02b883 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx @@ -0,0 +1,146 @@ +/* + * Copyright 2022 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 { sortBy } from 'lodash'; +import { DateTime } from 'luxon'; +import React, { useEffect, useMemo, useState } from 'react'; + +import { InfoCard, Progress } from '@backstage/core-components'; +import { useAnalytics } from '@backstage/core-plugin-api'; + +import { Box, IconButton, Typography } from '@material-ui/core'; +import PrevIcon from '@material-ui/icons/NavigateBefore'; +import NextIcon from '@material-ui/icons/NavigateNext'; + +import { + useCalendarsQuery, + useEventsQuery, + useSignIn, + useStoredCalendars, +} from '../../hooks'; +import calendarCardIcon from '../../icons/calendarIcon.svg'; +import { CalendarEvent } from './CalendarEvent'; +import { CalendarSelect } from './CalendarSelect'; +import { SignInContent } from './SignInContent'; +import { getStartDate } from './util'; + +export const CalendarCard = () => { + const [date, setDate] = useState(DateTime.now()); + const analytics = useAnalytics(); + + const changeDay = (offset = 1) => { + setDate(prev => prev.plus({ day: offset })); + analytics.captureEvent('click', 'change date'); + }; + + const { isSignedIn, isInitialized, signIn } = useSignIn(); + + useEffect(() => { + signIn(true); + }, [signIn]); + + const { isLoading: isCalendarLoading, data } = useCalendarsQuery({ + enabled: isSignedIn, + }); + const calendars = useMemo(() => data?.items || [], [data]); + const primaryCalendarId = calendars.find(c => c.primary === true)?.id; + const defaultSelectedCalendars = primaryCalendarId ? [primaryCalendarId] : []; + const [storedCalendars, setStoredCalendars] = useStoredCalendars( + defaultSelectedCalendars, + ); + + const { events, isLoading: isEventLoading } = useEventsQuery({ + calendars, + selectedCalendars: storedCalendars, + enabled: isSignedIn && calendars.length > 0, + timeMin: date.startOf('day').toISO(), + timeMax: date.endOf('day').toISO(), + timeZone: date.zoneName, + }); + + return ( + + + Google Calendar + + {isSignedIn ? ( + <> + changeDay(-1)} size="small"> + + + changeDay(1)} size="small"> + + + + + {date.toLocaleString({ + weekday: 'short', + month: 'short', + day: 'numeric', + })} + + + + + + + ) : ( + Agenda + )} + + } + deepLink={{ + link: 'https://calendar.google.com/', + title: 'Go to Calendar', + }} + > + + {(isCalendarLoading || isEventLoading || !isInitialized) && ( + + + + )} + {!isSignedIn && isInitialized && ( + signIn(false)} /> + )} + {!isEventLoading && !isCalendarLoading && isSignedIn && ( + + {events.length === 0 && ( + + + No events + + + )} + {sortBy(events, [getStartDate]).map(event => ( + + ))} + + )} + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.test.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.test.tsx new file mode 100644 index 0000000000..7a48903bec --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2022 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 { fireEvent } from '@testing-library/react'; +import React from 'react'; + +import { renderInTestApp } from '@backstage/test-utils'; + +import { CalendarEvent } from './CalendarEvent'; + +describe('', () => { + const event = { + summary: 'Test event', + htmlLink: '/calendar-link', + start: { + dateTime: '2022-02-09T10:15:00', + }, + end: { + dateTime: '2022-02-09T10:45:00', + }, + conferenceData: { + entryPoints: [{ entryPointType: 'video', uri: '/zoom-link' }], + }, + description: 'Test description', + attendees: [ + { + email: 'test@test.com', + }, + ], + }; + + it('should render calendar event', async () => { + const { queryByText, queryByTestId } = await renderInTestApp( + , + ); + expect(queryByText(event.summary)).toBeInTheDocument(); + expect(queryByTestId('calendar-event-zoom-link')).toBeInTheDocument(); + expect(queryByTestId('calendar-event-zoom-link')).toHaveAttribute( + 'href', + event.conferenceData.entryPoints[0].uri, + ); + expect(queryByTestId('calendar-event-time')).toBeInTheDocument(); + }); + + it('should not render time for events longer than 1 day', async () => { + const allDayEvent = { + summary: 'Test event', + start: { + date: '2022-02-09', + }, + end: { + date: '2022-02-19', + }, + }; + const { queryByText, queryByTestId } = await renderInTestApp( + , + ); + expect(queryByText(allDayEvent.summary)).toBeInTheDocument(); + expect(queryByTestId('calendar-event-time')).not.toBeInTheDocument(); + }); + + it('should show popover on click', async () => { + const { queryByTestId, getByTestId } = await renderInTestApp( + , + ); + expect(queryByTestId('calendar-event-popover')).not.toBeInTheDocument(); + + fireEvent.click(getByTestId('calendar-event')); + expect(queryByTestId('calendar-event-popover')).toBeInTheDocument(); + }); +}); diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx new file mode 100644 index 0000000000..89705a5a7b --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx @@ -0,0 +1,165 @@ +/* + * Copyright 2022 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 classnames from 'classnames'; +import { + bindPopover, + bindTrigger, + usePopupState, +} from 'material-ui-popup-state/hooks'; +import React, { useState } from 'react'; + +import { useAnalytics } from '@backstage/core-plugin-api'; + +import { + Box, + Link, + Paper, + Popover, + Tooltip, + Typography, + makeStyles, +} from '@material-ui/core'; + +import zoomIcon from '../../icons/zoomIcon.svg'; +import { CalendarEventPopoverContent } from './CalendarEventPopoverContent'; +import { GCalendarEvent, ResponseStatus } from '../../api'; +import { getTimePeriod, getZoomLink, isAllDay, isPassed } from './util'; + +const useStyles = makeStyles( + theme => ({ + event: { + display: 'flex', + alignItems: 'center', + marginBottom: theme.spacing(1), + cursor: 'pointer', + paddingRight: 12, + }, + declined: { + textDecoration: 'line-through', + }, + passed: { + opacity: 0.6, + transition: 'opacity 0.15s ease-in-out', + '&:hover': { + opacity: 1, + }, + }, + link: { + width: 48, + height: 48, + display: 'inline-block', + padding: 8, + borderRadius: '50%', + '&:hover': { + backgroundColor: theme.palette.grey[100], + }, + }, + calendarColor: ({ event }: { event: GCalendarEvent }) => ({ + width: 8, + borderTopLeftRadius: 4, + borderBottomLeftRadius: 4, + backgroundColor: event.primary + ? theme.palette.primary.light + : event.backgroundColor, + }), + }), + { + name: 'GCalendarEvent', + }, +); + +export const CalendarEvent = ({ event }: { event: GCalendarEvent }) => { + const classes = useStyles({ event }); + const popoverState = usePopupState({ + variant: 'popover', + popupId: event.id, + disableAutoFocus: true, + }); + const [hovered, setHovered] = useState(false); + const analytics = useAnalytics(); + const zoomLink = getZoomLink(event); + + const { onClick, ...restBindProps } = bindTrigger(popoverState); + + return ( + <> + { + onClick(e); + analytics.captureEvent('click', 'event info'); + }} + {...restBindProps} + onMouseEnter={() => setHovered(true)} + onMouseLeave={() => setHovered(false)} + elevation={hovered ? 4 : 1} + className={classnames(classes.event, { + [classes.passed]: isPassed(event), + })} + data-testid="calendar-event" + > + + + + {event.summary} + + {!isAllDay(event) && ( + + {getTimePeriod(event)} + + )} + + + {zoomLink && ( + + { + e.stopPropagation(); + analytics.captureEvent('click', 'zoom link'); + }} + > + Zoom link + + + )} + + + + + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx new file mode 100644 index 0000000000..fe48b2fd5a --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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 { renderInTestApp } from '@backstage/test-utils'; + +import { CalendarEventPopoverContent } from './CalendarEventPopoverContent'; + +describe('', () => { + const event = { + summary: 'Test event', + htmlLink: '/calendar-link', + conferenceData: { + entryPoints: [{ entryPointType: 'video', uri: '/zoom-link' }], + }, + description: 'Test description', + attendees: [ + { + email: 'test@test.com', + }, + ], + }; + + it('should render event info', async () => { + const { queryByText, queryByTestId } = await renderInTestApp( + , + ); + expect(queryByText(event.summary)).toBeInTheDocument(); + expect(queryByText(event.description)).toBeInTheDocument(); + expect(queryByText(event.attendees[0].email)).toBeInTheDocument(); + expect(queryByText('Join Zoom Meeting')).toBeInTheDocument(); + expect(queryByText('Join Zoom Meeting')?.closest('a')).toHaveAttribute( + 'href', + event.conferenceData.entryPoints[0].uri, + ); + expect(queryByTestId('open-calendar-link')).toHaveAttribute( + 'href', + event.htmlLink, + ); + expect(queryByText(event.attendees[0].email)).toBeInTheDocument(); + }); +}); diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx new file mode 100644 index 0000000000..4af33171d4 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx @@ -0,0 +1,131 @@ +/* + * Copyright 2022 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 { sortBy } from 'lodash'; +import React from 'react'; +import DOMPurify from 'dompurify'; + +import { useAnalytics } from '@backstage/core-plugin-api'; + +import { + Box, + Divider, + IconButton, + Link, + Tooltip, + Typography, + makeStyles, +} from '@material-ui/core'; +import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; + +import { AttendeeChip } from './AttendeeChip'; +import { GCalendarEvent } from '../../api'; +import { getTimePeriod, getZoomLink } from './util'; + +const useStyles = makeStyles( + theme => { + return { + description: { + wordBreak: 'break-word', + '& a': { + color: theme.palette.primary.main, + fontWeight: 500, + }, + }, + divider: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + }; + }, + { + name: 'GCalendarEventPopoverContent', + }, +); + +type CalendarEventPopoverProps = { + event: GCalendarEvent; +}; + +export const CalendarEventPopoverContent = ({ + event, +}: CalendarEventPopoverProps) => { + const classes = useStyles({ event }); + const analytics = useAnalytics(); + const zoomLink = getZoomLink(event); + + return ( + + + + {event.summary} + {getTimePeriod(event)} + + {event.htmlLink && ( + + + analytics.captureEvent('click', 'open in calendar') + } + > + + + + + + )} + + {zoomLink && ( + analytics.captureEvent('click', 'zoom link')} + > + Join Zoom Meeting + + )} + + {event.description && ( + <> + + + + )} + + {event.attendees && ( + <> + + + Attendees + + {sortBy(event.attendees || [], 'email').map(user => ( + + ))} + + + )} + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarSelect.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarSelect.tsx new file mode 100644 index 0000000000..a1e037a2a3 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarSelect.tsx @@ -0,0 +1,96 @@ +/* + * Copyright 2022 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 { sortBy } from 'lodash'; +import React from 'react'; + +import { + Checkbox, + FormControl, + Input, + ListItemText, + MenuItem, + Select, + Typography, + makeStyles, +} from '@material-ui/core'; + +import { GCalendar } from '../../api'; + +const useStyles = makeStyles( + { + formControl: { + width: 120, + }, + selectedCalendars: { + textOverflow: 'ellipsis', + overflow: 'hidden', + }, + }, + { + name: 'GCalendarSelect', + }, +); + +type CalendarSelectProps = { + disabled: boolean; + selectedCalendars?: string[]; + setSelectedCalendars: (value: string[]) => void; + calendars: GCalendar[]; +}; + +export const CalendarSelect = ({ + disabled, + selectedCalendars = [], + setSelectedCalendars, + calendars, +}: CalendarSelectProps) => { + const classes = useStyles(); + + return ( + + } + renderValue={selected => ( + + {calendars + .filter(c => c.id && (selected as string[]).includes(c.id)) + .map(c => c.summary) + .join(', ')} + + )} + MenuProps={{ + PaperProps: { + style: { + width: 350, + }, + }, + }} + > + {sortBy(calendars, 'summary').map(c => ( + + + + + ))} + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx new file mode 100644 index 0000000000..c3f2fecdeb --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx @@ -0,0 +1,155 @@ +/* + * Copyright 2022 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 { googleAuthApiRef, storageApiRef } from '@backstage/core-plugin-api'; +import { + MockStorageApi, + TestApiProvider, + renderInTestApp, +} from '@backstage/test-utils'; + +import { HomePageCalendar } from '.'; +import { gcalendarApiRef, gcalendarPlugin } from '../..'; + +describe('', () => { + const primaryCalendar = { + id: 'test-1@test.com', + summary: 'test-1@test.com', + primary: true, + }; + const nonPrimaryCalendar = { + id: 'test-2@test.com', + summary: 'test-2@test.com', + primary: false, + }; + const calendarData = { + items: [primaryCalendar, nonPrimaryCalendar], + }; + const eventData = { + items: [ + { + id: '1', + summary: 'Test event', + }, + { + id: '2', + summary: 'Test event', + }, + ], + }; + + const getMockedApi = (calendarMock = {}, eventMock = {}) => ({ + getCalendars: jest.fn().mockResolvedValue(calendarMock), + getEvents: jest.fn().mockResolvedValue(eventMock), + }); + + const getAuthMockApi = (token = 'token') => ({ + getAccessToken: jest.fn().mockResolvedValue(token), + }); + + const mockStorage = MockStorageApi.create(); + + it('should render "Sign in" button if user not signed in', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.queryByText('Sign in')).toBeInTheDocument(); + }); + + it('should render empty card', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.queryByText('No events')).toBeInTheDocument(); + expect(rendered.queryByText('Go to Calendar')).toBeInTheDocument(); + }); + + it('should select primary calendar by default', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.queryByText(primaryCalendar.summary)).toBeInTheDocument(); + expect( + rendered.queryByText(nonPrimaryCalendar.summary), + ).not.toBeInTheDocument(); + }); + + it('should render calendar events', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.queryByText('No events')).not.toBeInTheDocument(); + expect(rendered.queryAllByText('Test event')).toHaveLength(2); + }); + + it('should select stored calendar', async () => { + mockStorage + .forBucket(gcalendarPlugin.getId()) + .set('google_calendars_selected', [nonPrimaryCalendar.id]); + + const rendered = await renderInTestApp( + + + , + ); + + expect( + rendered.queryByText(nonPrimaryCalendar.summary), + ).toBeInTheDocument(); + expect( + rendered.queryByText(primaryCalendar.summary), + ).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.tsx b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.tsx new file mode 100644 index 0000000000..afe7a0fada --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.tsx @@ -0,0 +1,29 @@ +/* + * Copyright 2022 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 { QueryClient, QueryClientProvider } from 'react-query'; + +import { CalendarCard } from './CalendarCard'; + +const queryClient = new QueryClient(); + +export const HomePageCalendar = () => { + return ( + + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx new file mode 100644 index 0000000000..922c18da49 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2022 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 { Box, Button, styled } from '@material-ui/core'; + +import { CalendarEvent } from './CalendarEvent'; +import { eventsMock } from './signInEventMock'; +import { GCalendarEvent } from '../..'; + +type Props = { + handleAuthClick: React.MouseEventHandler; + events?: GCalendarEvent[]; +}; + +const TransparentBox = styled(Box)({ + opacity: 0.3, + filter: 'blur(1.5px)', +}); + +export const SignInContent = ({ + handleAuthClick, + events = eventsMock, +}: Props) => { + return ( + + + {events.map(event => ( + + ))} + + + + + + + ); +}; diff --git a/plugins/gcalendar/src/components/CalendarCard/index.ts b/plugins/gcalendar/src/components/CalendarCard/index.ts new file mode 100644 index 0000000000..0a898b3cee --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2022 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 { HomePageCalendar } from './HomePageCalendar'; diff --git a/plugins/gcalendar/src/components/CalendarCard/signInEventMock.ts b/plugins/gcalendar/src/components/CalendarCard/signInEventMock.ts new file mode 100644 index 0000000000..d0c882c0dc --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/signInEventMock.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2022 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 { GCalendarEvent } from '../../api'; + +export const eventsMock: GCalendarEvent[] = [ + { + id: '1', + htmlLink: 'https://www.google.com/calendar/', + summary: 'Backstage Community Sessions', + start: { + dateTime: '2021-12-07T09:00:00+01:00', + timeZone: 'Europe/London', + }, + end: { + dateTime: '2021-12-07T10:00:00+01:00', + timeZone: 'Europe/London', + }, + }, + { + id: '2', + htmlLink: 'https://www.google.com/calendar/', + summary: 'Backstage Community Sessions', + start: { + dateTime: '2021-12-07T10:30:00+01:00', + timeZone: 'Europe/London', + }, + end: { + dateTime: '2021-12-07T10:45:00+01:00', + timeZone: 'Europe/London', + }, + conferenceData: { + entryPoints: [ + { + entryPointType: 'video', + uri: 'https://zoom.us', + }, + ], + }, + }, + { + id: '3', + htmlLink: 'https://www.google.com/calendar', + summary: 'Backstage Community Sessions', + start: { + dateTime: '2021-12-07T12:00:00+01:00', + timeZone: 'Europe/London', + }, + end: { + dateTime: '2021-12-07T13:00:00+01:00', + timeZone: 'Europe/London', + }, + conferenceData: { + entryPoints: [ + { + entryPointType: 'video', + uri: 'https://zoom.us', + label: 'zoom.us', + }, + ], + }, + }, + { + id: '4', + htmlLink: 'https://www.google.com/calendar', + summary: 'Backstage Community Sessions', + start: { + dateTime: '2021-12-07T15:00:00+01:00', + timeZone: 'Europe/London', + }, + end: { + dateTime: '2021-12-07T16:30:00+01:00', + timeZone: 'Europe/London', + }, + conferenceData: { + entryPoints: [ + { + entryPointType: 'video', + uri: 'https://zoom.us', + }, + ], + }, + }, + { + id: '5', + htmlLink: 'https://www.google.com/calendar', + summary: 'Backstage Community Sessions', + start: { + dateTime: '2021-12-07T17:00:00+01:00', + timeZone: 'Europe/London', + }, + end: { + dateTime: '2021-12-07T17:30:00+01:00', + timeZone: 'Europe/London', + }, + conferenceData: { + entryPoints: [ + { + entryPointType: 'video', + uri: 'https://zoom.us', + }, + ], + }, + }, +]; diff --git a/plugins/gcalendar/src/components/CalendarCard/util.ts b/plugins/gcalendar/src/components/CalendarCard/util.ts new file mode 100644 index 0000000000..e97a3ac8e6 --- /dev/null +++ b/plugins/gcalendar/src/components/CalendarCard/util.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2022 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 { DateTime } from 'luxon'; + +import { GCalendarEvent } from '../../api'; + +export function getZoomLink(event: GCalendarEvent) { + const videoEntrypoint = event.conferenceData?.entryPoints?.find( + e => e.entryPointType === 'video', + ); + + return videoEntrypoint?.uri ?? ''; +} + +export function getTimePeriod(event: GCalendarEvent) { + if (isAllDay(event)) { + return getAllDayTimePeriod(event); + } + + const format: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + }; + + const startTime = DateTime.fromISO(event.start?.dateTime || ''); + const endTime = DateTime.fromISO(event.end?.dateTime || ''); + + return `${startTime.toLocaleString(format)} - ${endTime.toLocaleString( + format, + )}`; +} + +function getAllDayTimePeriod(event: GCalendarEvent) { + const format: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric' }; + const startTime = DateTime.fromISO( + event.start?.dateTime || event.start?.date || '', + ); + const endTime = DateTime.fromISO( + event.end?.dateTime || event.end?.date || '', + ).minus({ day: 1 }); + + if (startTime.toISO() === endTime.toISO()) { + return startTime.toLocaleString(format); + } + + return `${startTime.toLocaleString(format)} - ${endTime.toLocaleString( + format, + )}`; +} + +export function isPassed(event: GCalendarEvent) { + if (!event.end?.dateTime && !event.end?.date) return false; + const eventDate = DateTime.fromISO(event.end?.dateTime || event.end?.date!); + return DateTime.now() >= eventDate; +} + +export function isAllDay(event: GCalendarEvent) { + if (event.start?.date || event.end?.date) { + return true; + } + const startTime = DateTime.fromISO(event.start?.dateTime || ''); + const endTime = DateTime.fromISO(event.end?.dateTime || ''); + + return endTime.diff(startTime, 'day').days >= 1; +} + +export function getStartDate(event: GCalendarEvent) { + return DateTime.fromISO(event.start?.dateTime || event.start?.date || ''); +} diff --git a/plugins/gcalendar/src/hooks/index.ts b/plugins/gcalendar/src/hooks/index.ts new file mode 100644 index 0000000000..ef922cc4a3 --- /dev/null +++ b/plugins/gcalendar/src/hooks/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 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 { useCalendarsQuery } from './useCalendarsQuery'; +export { useEventsQuery } from './useEventsQuery'; +export { useSignIn } from './useSignIn'; +export { useStoredCalendars } from './useStoredCalendars'; diff --git a/plugins/gcalendar/src/hooks/useCalendarsQuery.ts b/plugins/gcalendar/src/hooks/useCalendarsQuery.ts new file mode 100644 index 0000000000..ed31e23c79 --- /dev/null +++ b/plugins/gcalendar/src/hooks/useCalendarsQuery.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2022 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 { useQuery } from 'react-query'; + +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; + +import { gcalendarApiRef } from '../api'; + +type Options = { + enabled: boolean; + refreshTime?: number; +}; + +export const useCalendarsQuery = ({ enabled }: Options) => { + const calendarApi = useApi(gcalendarApiRef); + const errorApi = useApi(errorApiRef); + + return useQuery( + ['calendars'], + async () => + calendarApi.getCalendars({ + minAccessRole: 'reader', + }), + { + enabled, + keepPreviousData: true, + refetchInterval: 3600000, + onError: () => { + errorApi.post({ + name: 'API error', + message: 'Failed to fetch calendars.', + }); + }, + }, + ); +}; diff --git a/plugins/gcalendar/src/hooks/useEventsQuery.ts b/plugins/gcalendar/src/hooks/useEventsQuery.ts new file mode 100644 index 0000000000..8f8ca68e4f --- /dev/null +++ b/plugins/gcalendar/src/hooks/useEventsQuery.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2022 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 { compact, unescape } from 'lodash'; +import { useMemo } from 'react'; +import { useQueries } from 'react-query'; + +import { useApi } from '@backstage/core-plugin-api'; + +import { gcalendarApiRef } from '../api'; +import { GCalendar, GCalendarEvent } from '../api'; + +type Options = { + selectedCalendars?: string[]; + timeMin: string; + timeMax: string; + enabled: boolean; + calendars: GCalendar[]; + timeZone: string; + refreshTime?: number; +}; + +export const useEventsQuery = ({ + selectedCalendars = [], + calendars = [], + enabled, + timeMin, + timeMax, + timeZone, +}: Options) => { + const calendarApi = useApi(gcalendarApiRef); + const eventQueries = useQueries( + selectedCalendars + .filter(id => calendars.find(c => c.id === id)) + .map(calendarId => { + const calendar = calendars.find(c => c.id === calendarId); + + return { + queryKey: ['calendarEvents', calendarId, timeMin, timeMax], + enabled, + initialData: [], + refetchInterval: 60000, + refetchIntervalInBackground: true, + queryFn: async (): Promise => { + const data = await calendarApi.getEvents(calendarId, { + calendarId, + timeMin, + timeMax, + showDeleted: false, + singleEvents: true, + maxResults: 100, + orderBy: 'startTime', + timeZone, + }); + + return (data.items || []).map(event => { + const responseStatus = event.attendees?.find( + a => !!a.self, + )?.responseStatus; + + return { + ...event, + summary: unescape(event.summary || ''), + calendarId, + backgroundColor: calendar?.backgroundColor, + primary: !!calendar?.primary, + responseStatus, + }; + }); + }, + }; + }), + ); + + const events = useMemo( + () => compact(eventQueries.map(({ data }) => data).flat()), + [eventQueries], + ); + + const isLoading = + !!eventQueries.find(q => q.isFetching) && events.length === 0; + + return { events, isLoading }; +}; diff --git a/plugins/gcalendar/src/hooks/useSignIn.ts b/plugins/gcalendar/src/hooks/useSignIn.ts new file mode 100644 index 0000000000..351707b54e --- /dev/null +++ b/plugins/gcalendar/src/hooks/useSignIn.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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 { useCallback, useState } from 'react'; + +import { googleAuthApiRef, useApi } from '@backstage/core-plugin-api'; + +export const useSignIn = () => { + const [isSignedIn, setSignedIn] = useState(false); + const [isInitialized, setInitialized] = useState(false); + const authApi = useApi(googleAuthApiRef); + + const signIn = useCallback( + async (optional = false) => { + const token = await authApi.getAccessToken( + 'https://www.googleapis.com/auth/calendar.readonly', + { + optional, + instantPopup: !optional, + }, + ); + + setSignedIn(!!token); + setInitialized(true); + }, + [authApi, setSignedIn], + ); + + return { isSignedIn, isInitialized, signIn }; +}; diff --git a/plugins/gcalendar/src/hooks/useStoredCalendars.ts b/plugins/gcalendar/src/hooks/useStoredCalendars.ts new file mode 100644 index 0000000000..5ccdde5572 --- /dev/null +++ b/plugins/gcalendar/src/hooks/useStoredCalendars.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2022 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 { useApi, storageApiRef } from '@backstage/core-plugin-api'; +import useObservable from 'react-use/lib/useObservable'; + +import { gcalendarPlugin } from '../plugin'; + +export enum LocalStorageKeys { + selectedCalendars = 'google_calendars_selected', +} + +export function useStoredCalendars( + defaultValue: string[], +): [string[], (value: string[]) => void] { + const storageBucket = gcalendarPlugin.getId(); + const storageKey = LocalStorageKeys.selectedCalendars; + const storageApi = useApi(storageApiRef).forBucket(storageBucket); + const setValue = (value: string[]) => { + storageApi.set(storageKey, value); + }; + const snapshot = useObservable( + storageApi.observe$(storageKey), + storageApi.snapshot(storageKey), + ); + let result: string[]; + if (snapshot.presence === 'absent') { + result = defaultValue; + } else { + result = snapshot.value!; + } + return [result, setValue]; +} diff --git a/plugins/gcalendar/src/icons/calendarIcon.svg b/plugins/gcalendar/src/icons/calendarIcon.svg new file mode 100644 index 0000000000..f5b82c824c --- /dev/null +++ b/plugins/gcalendar/src/icons/calendarIcon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/plugins/gcalendar/src/icons/zoomIcon.svg b/plugins/gcalendar/src/icons/zoomIcon.svg new file mode 100644 index 0000000000..cc6ef0316b --- /dev/null +++ b/plugins/gcalendar/src/icons/zoomIcon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/gcalendar/src/index.ts b/plugins/gcalendar/src/index.ts new file mode 100644 index 0000000000..7c7f0c6374 --- /dev/null +++ b/plugins/gcalendar/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 { gcalendarPlugin, HomePageCalendar } from './plugin'; +export * from './api'; diff --git a/plugins/gcalendar/src/plugin.test.ts b/plugins/gcalendar/src/plugin.test.ts new file mode 100644 index 0000000000..47674bc3b7 --- /dev/null +++ b/plugins/gcalendar/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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 { gcalendarPlugin } from './plugin'; + +describe('gcalendar', () => { + it('should export plugin', () => { + expect(gcalendarPlugin).toBeDefined(); + }); +}); diff --git a/plugins/gcalendar/src/plugin.ts b/plugins/gcalendar/src/plugin.ts new file mode 100644 index 0000000000..1f98f560f1 --- /dev/null +++ b/plugins/gcalendar/src/plugin.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2022 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 { + createApiFactory, + createComponentExtension, + createPlugin, + fetchApiRef, + googleAuthApiRef, +} from '@backstage/core-plugin-api'; + +import { GCalendarApiClient, gcalendarApiRef } from './api'; +import { rootRouteRef } from './routes'; + +export const gcalendarPlugin = createPlugin({ + id: 'gcalendar', + routes: { + root: rootRouteRef, + }, + apis: [ + createApiFactory({ + api: gcalendarApiRef, + deps: { authApi: googleAuthApiRef, fetchApi: fetchApiRef }, + factory(deps) { + return new GCalendarApiClient(deps); + }, + }), + ], +}); + +export const HomePageCalendar = gcalendarPlugin.provide( + createComponentExtension({ + name: 'HomePageCalendar', + component: { + lazy: () => + import('./components/CalendarCard').then(m => m.HomePageCalendar), + }, + }), +); diff --git a/plugins/gcalendar/src/routes.ts b/plugins/gcalendar/src/routes.ts new file mode 100644 index 0000000000..e6396d0f3a --- /dev/null +++ b/plugins/gcalendar/src/routes.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2022 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({ + id: 'gcalendar', +}); diff --git a/plugins/gcalendar/src/setupTests.ts b/plugins/gcalendar/src/setupTests.ts new file mode 100644 index 0000000000..9bb3e72355 --- /dev/null +++ b/plugins/gcalendar/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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/yarn.lock b/yarn.lock index 2527c4985d..83fa1214f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1295,7 +1295,7 @@ core-js-pure "^3.20.2" 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.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.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": +"@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.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@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.17.2" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== @@ -4062,6 +4062,11 @@ resolved "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== +"@material-ui/types@^6.0.1": + version "6.0.2" + resolved "https://registry.npmjs.org/@material-ui/types/-/types-6.0.2.tgz#a6d384196c6e2e130eb2765b62d944c0b1ea1015" + integrity sha512-/XUca4wUb9pWimLLdM1PE8KS8rTbDEGohSGkGtk3WST7lm23m+8RYv9uOmrvOg/VSsl4bMiOv4t2/LCb+RLbTg== + "@material-ui/utils@^4.11.2", "@material-ui/utils@^4.7.1": version "4.11.2" resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" @@ -4071,6 +4076,13 @@ prop-types "^15.7.2" react-is "^16.8.0 || ^17.0.0" +"@maxim_mazurok/gapi.client.calendar@latest": + version "3.0.20220217" + resolved "https://registry.npmjs.org/@maxim_mazurok/gapi.client.calendar/-/gapi.client.calendar-3.0.20220217.tgz#86279915171ddc5f29afba9f9c19244d3e842a07" + integrity sha512-FMXqpSBmws0Arxrpg3zJJdM2NwYFd66N/C9blAAw9wt68aWa74N6okLVvOCs1FJF9asF7gHBZn8jAP+DHk+yxg== + dependencies: + "@types/gapi.client" "*" + "@microsoft/api-documenter@^7.15.0": version "7.15.0" resolved "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.15.0.tgz#e6cf24fc0e2f18a71dcf4c5c8100cc083167a81e" @@ -5712,7 +5724,7 @@ "@types/docker-modem" "*" "@types/node" "*" -"@types/dompurify@^2.1.0", "@types/dompurify@^2.2.2": +"@types/dompurify@^2.1.0", "@types/dompurify@^2.2.2", "@types/dompurify@^2.3.3": version "2.3.3" resolved "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f" integrity sha512-nnVQSgRVuZ/843oAfhA25eRSNzUFcBPk/LOiw5gm8mD9/X7CNcbRkQu/OsjCewO8+VIYfPxUnXvPEVGenw14+w== @@ -5806,6 +5818,30 @@ dependencies: "@types/node" "*" +"@types/gapi.auth2@^0.0.56": + version "0.0.56" + resolved "https://registry.npmjs.org/@types/gapi.auth2/-/gapi.auth2-0.0.56.tgz#2f7031f79390b8401e7950d8277ada874fd2731c" + integrity sha512-kGaBtGVCqGS3Y05L56dGVlBpJflxLfwA0zpMQnQgGRFk1tsMPbQnogG51UQjt1vCuYfRO0Jd9/K5KDtzjAbMkA== + dependencies: + "@types/gapi" "*" + +"@types/gapi.client.calendar@^3.0.10": + version "3.0.10" + resolved "https://registry.npmjs.org/@types/gapi.client.calendar/-/gapi.client.calendar-3.0.10.tgz#4b089d9af2753a07cf1d46adc83b1a7cedb8e355" + integrity sha512-NUStEVbHPOhFsw4cWE2CThe5eKpTlmz+fSu8mvEc7j+IDVNgk1kS4C6hZzBCdlIjFfOzdQM3Cyqkt5kt7ze3kA== + dependencies: + "@maxim_mazurok/gapi.client.calendar" latest + +"@types/gapi.client@*": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.5.tgz#a6eb97e664fe51656c5b52258bd0afef28c76308" + integrity sha512-OTpbBMuzfC4lkvaomxqskI/iWRGW3zOZbDXZLNSyiuswTiSSGgILRLkg0POuZ4EgzEdaYaTlXpnXiCp07ri/Yw== + +"@types/gapi@*", "@types/gapi@^0.0.41": + version "0.0.41" + resolved "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.41.tgz#c477ee4f0951c005869219fd10b456ae2bba437e" + integrity sha512-tmHO66z/f91JZCDqinj/nNvQEszsz/hBT4+MvCSKT5sDzl5Ld/oXZ8WaecCBjRLw2uWKUInUHM9MhEXWkOiNjw== + "@types/git-url-parse@^9.0.0": version "9.0.1" resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.1.tgz#1c7cc89527ca8b5afcf260ead3b0e4e373c43938" @@ -6431,6 +6467,13 @@ dependencies: rollup-plugin-postcss "*" +"@types/sanitize-html@^2.6.2": + version "2.6.2" + resolved "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9" + integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ== + dependencies: + htmlparser2 "^6.0.0" + "@types/scheduler@*": version "0.16.1" resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" @@ -8121,6 +8164,11 @@ bfj@^7.0.2: hoopy "^0.1.4" tryer "^1.0.1" +big-integer@^1.6.16: + version "1.6.51" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big-integer@^1.6.17: version "1.6.48" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -8319,6 +8367,20 @@ breakword@^1.0.5: dependencies: wcwidth "^1.0.1" +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -10784,6 +10846,11 @@ detect-node@^2.0.4: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== +detect-node@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + detect-port-alt@^1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" @@ -11009,7 +11076,7 @@ dompurify@=2.3.3: resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg== -dompurify@^2.2.7, dompurify@^2.2.9: +dompurify@^2.2.7, dompurify@^2.2.9, dompurify@^2.3.6: version "2.3.6" resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875" integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg== @@ -13837,7 +13904,7 @@ html-webpack-plugin@^5.3.1: pretty-error "^4.0.0" tapable "^2.0.0" -htmlparser2@^6.1.0: +htmlparser2@^6.0.0, htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== @@ -15625,6 +15692,11 @@ js-levenshtein@^1.1.6: resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -16945,6 +17017,11 @@ luxon@^2.0.2: resolved "https://registry.npmjs.org/luxon/-/luxon-2.3.0.tgz#bf16a7e642513c2a20a6230a6a41b0ab446d0045" integrity sha512-gv6jZCV+gGIrVKhO90yrsn8qXPKD8HYZJtrUDSfEbow8Tkw84T9OnCyJhWvnJIaIF/tBuiAjZuQHUt1LddX2mg== +luxon@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/luxon/-/luxon-2.3.1.tgz#f276b1b53fd9a740a60e666a541a7f6dbed4155a" + integrity sha512-I8vnjOmhXsMSlNMZlMkSOvgrxKJl0uOsEzdGgGNZuZPaS9KlefpE9KV95QFftlJSC+1UyCC9/I69R02cz/zcCA== + lz-string@^1.4.4: version "1.4.4" resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" @@ -17109,6 +17186,24 @@ marked@^4.0.10: resolved "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz#423e295385cc0c3a70fa495e0df68b007b879423" integrity sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw== +match-sorter@^6.0.2: + version "6.3.1" + resolved "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" + integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + +material-ui-popup-state@^1.9.3: + version "1.9.3" + resolved "https://registry.npmjs.org/material-ui-popup-state/-/material-ui-popup-state-1.9.3.tgz#133ee02be8adf936e738d6b5f3dc89726af39bce" + integrity sha512-+Ete5Tzw5rXlYfmqptOS8kBUH8vnK5OJsd6IQ7SHtLjU0PsvsmM73M/k8ot0xkX4RmPGuNRsFbK3mlCe/ClQuw== + dependencies: + "@babel/runtime" "^7.12.5" + "@material-ui/types" "^6.0.1" + classnames "^2.2.6" + prop-types "^15.7.2" + material-ui-search-bar@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/material-ui-search-bar/-/material-ui-search-bar-1.0.0.tgz#2652dd5bdc4cb043cffb7144d9c296c120702e62" @@ -17700,6 +17795,11 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -18162,6 +18262,13 @@ nano-css@^5.3.1: stacktrace-js "^2.0.2" stylis "^4.0.6" +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoclone@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" @@ -18795,6 +18902,11 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -20916,6 +21028,15 @@ react-markdown@^8.0.0: unist-util-visit "^4.0.0" vfile "^5.0.0" +react-query@^3.34.16: + version "3.34.16" + resolved "https://registry.npmjs.org/react-query/-/react-query-3.34.16.tgz#279ea180bcaeaec49c7864b29d1711ee9f152594" + integrity sha512-7FvBvjgEM4YQ8nPfmAr+lJfbW95uyW/TVjFoi2GwCkF33/S8ajx45tuPHPFGWs4qYwPy1mzwxD4IQfpUDrefNQ== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-redux@^7.1.1, react-redux@^7.2.4: version "7.2.5" resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8" @@ -21659,6 +21780,11 @@ remedial@^1.0.7: resolved "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -24664,6 +24790,14 @@ unixify@1.0.0, unixify@^1.0.0: dependencies: normalize-path "^2.1.1" +unload@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"