diff --git a/.changeset/rotten-birds-build.md b/.changeset/rotten-birds-build.md
new file mode 100644
index 0000000000..e165129998
--- /dev/null
+++ b/.changeset/rotten-birds-build.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-microsoft-calendar': minor
+---
+
+Created a new plugin `@backstage/plugin-microsoft-calendar` to display events from a Microsoft Calendar.
+
+please refer to the [README.md](https://github.com/backstage/backstage/blob/master/plugins/microsoft-calendar/README.md) for step-by-step instructions to setup the plugin in your Backstage instance.
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index bb6f8da19b..1c6171cb01 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -59,6 +59,7 @@ yarn.lock @backstage/maintainers @back
/plugins/kafka-backend @backstage/maintainers @nirga @andrewthauer
/plugins/kubernetes @backstage/maintainers @backstage/warpspeed
/plugins/kubernetes-* @backstage/maintainers @backstage/warpspeed
+/plugins/microsoft-calendar @backstage/maintainers @abhay-soni-developer
/plugins/newrelic-dashboard @backstage/maintainers @mufaddal7
/plugins/playlist @backstage/maintainers @kuangp
/plugins/playlist-* @backstage/maintainers @kuangp
diff --git a/packages/app/package.json b/packages/app/package.json
index 1b3841ceae..80ee11ea15 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -41,6 +41,7 @@
"@backstage/plugin-kafka": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-lighthouse": "workspace:^",
+ "@backstage/plugin-microsoft-calendar": "workspace:^",
"@backstage/plugin-newrelic": "workspace:^",
"@backstage/plugin-newrelic-dashboard": "workspace:^",
"@backstage/plugin-org": "workspace:^",
diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx
index e432e1a00e..29023738af 100644
--- a/packages/app/src/components/home/HomePage.tsx
+++ b/packages/app/src/components/home/HomePage.tsx
@@ -27,6 +27,7 @@ import {
import { Content, Header, Page } from '@backstage/core-components';
import { HomePageSearchBar } from '@backstage/plugin-search';
import { HomePageCalendar } from '@backstage/plugin-gcalendar';
+import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar';
import Grid from '@material-ui/core/Grid';
import React from 'react';
@@ -114,6 +115,9 @@ export const homePage = (
+
+
+
diff --git a/plugins/microsoft-calendar/.eslintrc.js b/plugins/microsoft-calendar/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/microsoft-calendar/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/microsoft-calendar/README.md b/plugins/microsoft-calendar/README.md
new file mode 100644
index 0000000000..73a66b15f5
--- /dev/null
+++ b/plugins/microsoft-calendar/README.md
@@ -0,0 +1,51 @@
+# Microsoft-Calendar Plugin
+
+Welcome to the Microsoft calendar plugin!
+_This plugin was created through the Backstage CLI_
+
+## Features
+
+1. You can switch between calendars, using the select menu on the calendar card header.
+2. Card showing the list of events on the selected date and the selected calendar (provided by Outlook calendar).
+3. Link to join the online meeting on the event card if provided. so you can join your meetings right away hassle-free.
+4. Hovering over the event will pop over a card showing the event summary message, and list of attendees.
+5. attendee's chips will have a badge over them symbolizing their responses.
+ - green --> accepted
+ - red --> declined
+ - nothing --> not responded yet
+
+## Setup
+
+The following sections will help you set up the Microsoft calendar plugin.
+
+### Microsoft azure authentication provider
+
+> You need to setup [Microsoft Azure authentication provider](https://backstage.io/docs/auth/microsoft/provider), before you move forward with any of the below step if you haven't already.
+
+1. Install the plugin by running this command
+
+```bash
+# From the Backstage repository root
+yarn add --cwd packages/app @backstage/plugin-microsoft-calendar
+```
+
+2. Import the Microsoft calendar React component from `@backstage/plugin-microsoft-calendar`.
+3. You can then use the provided React component `MicrosoftCalendar` in the backstage frontend where ever you want
+
+```tsx
+import { MicrosoftCalendar } from '@backstage/plugin-microsoft-calendar';
+
+// ...
+
+
+;
+// ...
+```
+
+
+
+
+
+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/microsoft-calendar/api-report.md b/plugins/microsoft-calendar/api-report.md
new file mode 100644
index 0000000000..d6839f19ea
--- /dev/null
+++ b/plugins/microsoft-calendar/api-report.md
@@ -0,0 +1,39 @@
+## API Report File for "@backstage/plugin-microsoft-calendar"
+
+> 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 { Calendar } from '@microsoft/microsoft-graph-types';
+import { Event as Event_2 } from '@microsoft/microsoft-graph-types';
+import { FetchApi } from '@backstage/core-plugin-api';
+import { OAuthApi } from '@backstage/core-plugin-api';
+
+// @public (undocumented)
+export class MicrosoftCalendarApiClient {
+ constructor(options: { authApi: OAuthApi; fetchApi: FetchApi });
+ // (undocumented)
+ getCalendars(): Promise;
+ // (undocumented)
+ getEvents(
+ calendarId: string,
+ params: {
+ startDateTime: string;
+ endDateTime: string;
+ },
+ headers: {
+ [key in string]: any;
+ },
+ ): Promise;
+}
+
+// @public (undocumented)
+export const microsoftCalendarApiRef: ApiRef;
+
+// @public (undocumented)
+export const MicrosoftCalendarCard: () => JSX.Element;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/plugins/microsoft-calendar/dev/index.tsx b/plugins/microsoft-calendar/dev/index.tsx
new file mode 100644
index 0000000000..d611bca20c
--- /dev/null
+++ b/plugins/microsoft-calendar/dev/index.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 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 { createDevApp } from '@backstage/dev-utils';
+import { microsoftCalendarPlugin, MicrosoftCalendarCard } from '../src/plugin';
+import { microsoftCalendarApiRef } from '../src';
+import responseMock from './mock.json';
+import { microsoftAuthApiRef } from '@backstage/core-plugin-api';
+import { Content, Page } from '@backstage/core-components';
+import { Grid } from '@material-ui/core';
+
+createDevApp()
+ .registerPlugin(microsoftCalendarPlugin)
+ .registerApi({
+ api: microsoftAuthApiRef,
+ deps: {},
+ factory: () =>
+ ({
+ async getAccessToken() {
+ return Promise.resolve('token');
+ },
+ } as unknown as typeof microsoftAuthApiRef.T),
+ })
+ .registerApi({
+ api: microsoftCalendarApiRef,
+ deps: {},
+ factory: () =>
+ ({
+ async getCalendars() {
+ return Promise.resolve(responseMock.calendars);
+ },
+ async getEvents() {
+ return Promise.resolve(responseMock.events);
+ },
+ } as unknown as typeof microsoftCalendarApiRef.T),
+ })
+ .addPage({
+ element: (
+
+
+
+
+
+
+
+ ),
+ title: 'Microsoft-Calendar Plugin Demo',
+ path: '/microsoft-calendar',
+ })
+ .render();
diff --git a/plugins/microsoft-calendar/dev/mock.json b/plugins/microsoft-calendar/dev/mock.json
new file mode 100644
index 0000000000..b305e187eb
--- /dev/null
+++ b/plugins/microsoft-calendar/dev/mock.json
@@ -0,0 +1,305 @@
+{
+ "calendars": [
+ {
+ "id": "AQMkADQyMDdlODU1AC05OGNiLTRhZmItOGM2Mi1kODZhMzJkYWU2OTAARgAAAwWXy9axQ31DtBuHDIFwgtYHAKRSO4MUJXZDi1hwzpYLetAAAAIBBgAAAKRSO4MUJXZDi1hwzpYLetAAAAIyswAAAA==",
+ "name": "Calendar",
+ "color": "lightBlue",
+ "hexColor": "#9fe1e7",
+ "isDefaultCalendar": true,
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAAAADYw==",
+ "canShare": true,
+ "canViewPrivateItems": true,
+ "canEdit": true,
+ "allowedOnlineMeetingProviders": ["teamsForBusiness"],
+ "defaultOnlineMeetingProvider": "teamsForBusiness",
+ "isTallyingResponses": true,
+ "isRemovable": false,
+ "owner": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAEGAACkUjuDFCV2Q4tYcM6WC3rQAAAZs65BAAA=",
+ "name": "United Kingdom holidays",
+ "color": "auto",
+ "hexColor": "",
+ "isDefaultCalendar": false,
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAGanqjg==",
+ "canShare": false,
+ "canViewPrivateItems": true,
+ "canEdit": false,
+ "allowedOnlineMeetingProviders": [],
+ "defaultOnlineMeetingProvider": "unknown",
+ "isTallyingResponses": false,
+ "isRemovable": true,
+ "owner": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAEGAACkUjuDFCV2Q4tYcM6WC3rQAAAZs65CAAA=",
+ "name": "Birthdays1",
+ "color": "auto",
+ "hexColor": "",
+ "isDefaultCalendar": false,
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAGanrfQ==",
+ "canShare": false,
+ "canViewPrivateItems": true,
+ "canEdit": false,
+ "allowedOnlineMeetingProviders": [],
+ "defaultOnlineMeetingProvider": "unknown",
+ "isTallyingResponses": false,
+ "isRemovable": true,
+ "owner": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ {
+ "id": "AQMkADQyMDdlODU1AC05OGNiLTRhZmItOGM2Mi1kODZhMzJkYWU2OTAARgAAAwWXy9axQ31DtBuHDIFwgtYHAKRSO4MUJXZDi1hwzpYLetAAAAIBBgAAAKRSO4MUJXZDi1hwzpYLetAAAAIytQAAAA==",
+ "name": "Birthdays",
+ "color": "auto",
+ "hexColor": "#92e1c0",
+ "isDefaultCalendar": false,
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAAAADdw==",
+ "canShare": false,
+ "canViewPrivateItems": true,
+ "canEdit": false,
+ "allowedOnlineMeetingProviders": [],
+ "defaultOnlineMeetingProvider": "unknown",
+ "isTallyingResponses": false,
+ "isRemovable": true,
+ "owner": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ {
+ "id": "AQMkADQyMDdlODU1AC05OGNiLTRhZmItOGM2Mi1kODZhMzJkYWU2OTAARgAAAwWXy9axQ31DtBuHDIFwgtYHAKRSO4MUJXZDi1hwzpYLetAAAAIBBgAAAKRSO4MUJXZDi1hwzpYLetAAAAIytgAAAA==",
+ "name": "Holidays in India",
+ "color": "auto",
+ "hexColor": "#16a765",
+ "isDefaultCalendar": false,
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAAAADug==",
+ "canShare": false,
+ "canViewPrivateItems": true,
+ "canEdit": false,
+ "allowedOnlineMeetingProviders": [],
+ "defaultOnlineMeetingProvider": "unknown",
+ "isTallyingResponses": false,
+ "isRemovable": true,
+ "owner": {
+ "name": "Abhay Soni",
+ "address": "abhay.s@statusneo.com"
+ }
+ }
+ ],
+ "events": [
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MAFRAAgI2vdUnGxAAEYAAAAABZfL1rFDfUO0G4cMgXCC1gcApFI7gxQldkOLWHDOlgt60AAAAAABDQAApFI7gxQldkOLWHDOlgt60AAAYl_I0AAAEA==",
+ "createdDateTime": "2022-12-22T08:28:36.6131501Z",
+ "lastModifiedDateTime": "2023-01-20T05:24:31.9666518Z",
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAd5KhaA==",
+ "categories": [],
+ "transactionId": null,
+ "originalStartTimeZone": "India Standard Time",
+ "originalEndTimeZone": "India Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E00807E7011006D3A5A92DC7D80100000000000000001000000002BB709705E28C4493E46124FF07DD89",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "Abhay Soni",
+ "bodyPreview": "",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": false,
+ "responseRequested": true,
+ "seriesMasterId": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=",
+ "showAs": "tentative",
+ "type": "occurrence",
+ "webLink": "",
+ "onlineMeetingUrl": "",
+ "isOnlineMeeting": true,
+ "onlineMeetingProvider": "teamsForBusiness",
+ "allowNewTimeProposals": true,
+ "occurrenceId": "OID.AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=.2023-01-16",
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "notResponded",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": ""
+ },
+ "start": {
+ "dateTime": "2023-01-16T11:00:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "end": {
+ "dateTime": "2023-01-16T11:30:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [
+ {
+ "type": "required",
+ "status": {
+ "response": "none",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Nishkarsh Raj",
+ "address": "nishkarshraj@nishkarsh.com"
+ }
+ },
+ {
+ "type": "required",
+ "status": {
+ "response": "accepted",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Hakam Ram",
+ "address": "hakam_ram@ram.com"
+ }
+ },
+ {
+ "type": "required",
+ "status": {
+ "response": "declined",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ }
+ ],
+ "organizer": {
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ "onlineMeeting": {
+ "joinUrl": "https://abhay-soni-developer.github.io/MyReusme/"
+ }
+ },
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MAFRAAgI2vdUnGxAAEYAAAAABZfL1rFDfUO0G4cMgXCC1gcApFI7gxQldkOLWHDOlgt60AAAAAABDQAApFI7gxQldkOLWHDOlgt60AAAYl_I0AAAEA==",
+ "createdDateTime": "2022-12-22T08:28:36.6131501Z",
+ "lastModifiedDateTime": "2023-01-20T05:24:31.9666518Z",
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAd5KhaA==",
+ "categories": [],
+ "transactionId": null,
+ "originalStartTimeZone": "India Standard Time",
+ "originalEndTimeZone": "India Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E00807E7011006D3A5A92DC7D80100000000000000001000000002BB709705E28C4493E46124FF07DD89",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "https://abhay-soni-developer.github.io/MyReusme/",
+ "bodyPreview": "",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": false,
+ "responseRequested": true,
+ "seriesMasterId": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=",
+ "showAs": "tentative",
+ "type": "occurrence",
+ "webLink": "",
+ "onlineMeetingUrl": "",
+ "isOnlineMeeting": true,
+ "onlineMeetingProvider": "teamsForBusiness",
+ "allowNewTimeProposals": true,
+ "occurrenceId": "OID.AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=.2023-01-16",
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "notResponded",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "attendees": [
+ {
+ "type": "required",
+ "status": {
+ "response": "none",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Nishkarsh Raj",
+ "address": "nishkarshraj@nishkarsh.com"
+ }
+ },
+ {
+ "type": "required",
+ "status": {
+ "response": "accepted",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Hakam Ram",
+ "address": "hakam_ram@ram.com"
+ }
+ },
+ {
+ "type": "required",
+ "status": {
+ "response": "declined",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ }
+ ],
+ "body": {
+ "contentType": "html",
+ "content": ""
+ },
+ "start": {
+ "dateTime": "2023-01-16T11:00:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "end": {
+ "dateTime": "2023-01-16T11:30:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "organizer": {
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ "onlineMeeting": {
+ "joinUrl": "https://abhay-soni-developer.github.io/MyReusme/"
+ }
+ }
+ ]
+}
diff --git a/plugins/microsoft-calendar/docs/microsoft-calendar-plugin.png b/plugins/microsoft-calendar/docs/microsoft-calendar-plugin.png
new file mode 100644
index 0000000000..376ae73717
Binary files /dev/null and b/plugins/microsoft-calendar/docs/microsoft-calendar-plugin.png differ
diff --git a/plugins/microsoft-calendar/package.json b/plugins/microsoft-calendar/package.json
new file mode 100644
index 0000000000..2a2e0ca013
--- /dev/null
+++ b/plugins/microsoft-calendar/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@backstage/plugin-microsoft-calendar",
+ "version": "0.0.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"
+ },
+ "author": {
+ "name": "Abhay Soni",
+ "email": "abhaysoni.developer@gmail.com",
+ "url": "https://github.com/Abhay-soni-developer"
+ },
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/core-components": "workspace:^",
+ "@backstage/core-plugin-api": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "@backstage/theme": "workspace:^",
+ "@material-ui/core": "^4.12.2",
+ "@material-ui/icons": "^4.9.1",
+ "@microsoft/microsoft-graph-types": "^2.25.0",
+ "@tanstack/react-query": "^4.1.3",
+ "classnames": "^2.3.1",
+ "dompurify": "^2.3.6",
+ "lodash": "^4.17.21",
+ "luxon": "^3.0.0",
+ "material-ui-popup-state": "^1.9.3",
+ "react-use": "^17.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^",
+ "@backstage/core-app-api": "workspace:^",
+ "@backstage/dev-utils": "workspace:^",
+ "@backstage/test-utils": "workspace:^",
+ "@testing-library/jest-dom": "^5.10.1",
+ "@testing-library/react": "^12.1.3",
+ "@testing-library/user-event": "^14.0.0",
+ "@types/node": "^16.11.26",
+ "cross-fetch": "^3.1.5",
+ "msw": "^0.49.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/microsoft-calendar/src/api/client.ts b/plugins/microsoft-calendar/src/api/client.ts
new file mode 100644
index 0000000000..fe842b5aef
--- /dev/null
+++ b/plugins/microsoft-calendar/src/api/client.ts
@@ -0,0 +1,95 @@
+/*
+ * 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 { ResponseError } from '@backstage/errors';
+import { MicrosoftCalendarEvent, MicrosoftCalendar } from './types';
+
+/** @public */
+export const microsoftCalendarApiRef = createApiRef(
+ {
+ id: 'plugin.microsoft-calendar.service',
+ },
+);
+
+/** @public */
+export class MicrosoftCalendarApiClient {
+ private readonly authApi: OAuthApi;
+ private readonly fetchApi: FetchApi;
+
+ constructor(options: { authApi: OAuthApi; fetchApi: FetchApi }) {
+ this.authApi = options.authApi;
+ this.fetchApi = options.fetchApi;
+ }
+
+ private async get(
+ path: string,
+ params: { [key in string]: any } = {},
+ headers?: any,
+ ): Promise {
+ const query = new URLSearchParams(params);
+ const url = new URL(
+ `${path}?${query.toString()}`,
+ 'https://graph.microsoft.com',
+ );
+ const token = await this.authApi.getAccessToken();
+ let temp: any = {};
+
+ if (headers && typeof headers === 'object') {
+ temp = {
+ ...headers,
+ };
+ }
+
+ if (token) {
+ temp.Authorization = `Bearer ${token}`;
+ }
+
+ const response = await this.fetchApi.fetch(url.toString(), {
+ headers: temp,
+ });
+
+ if (!response.ok) {
+ throw await ResponseError.fromResponse(response);
+ }
+
+ return response.json() as Promise;
+ }
+
+ public async getCalendars(): Promise {
+ const data = await this.get<{
+ id: string;
+ value: MicrosoftCalendar[];
+ }>('v1.0/me/calendars');
+
+ return data.value;
+ }
+
+ public async getEvents(
+ calendarId: string,
+ params: {
+ startDateTime: string;
+ endDateTime: string;
+ },
+ headers: { [key in string]: any },
+ ): Promise {
+ const data = await this.get<{
+ id: string;
+ value: [MicrosoftCalendarEvent];
+ }>(`v1.0/me/calendars/${calendarId}/calendarview`, params, headers);
+ return data.value;
+ }
+}
diff --git a/plugins/microsoft-calendar/src/api/index.ts b/plugins/microsoft-calendar/src/api/index.ts
new file mode 100644
index 0000000000..db4616b586
--- /dev/null
+++ b/plugins/microsoft-calendar/src/api/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 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/microsoft-calendar/src/api/types.ts b/plugins/microsoft-calendar/src/api/types.ts
new file mode 100644
index 0000000000..f4cb2811c7
--- /dev/null
+++ b/plugins/microsoft-calendar/src/api/types.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 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 {
+ Calendar as MicrosoftCalendar,
+ Event as MicrosoftCalendarEvent,
+ Attendee,
+ ResponseStatus,
+ DateTimeTimeZone,
+ EmailAddress,
+} from '@microsoft/microsoft-graph-types';
+
+export type {
+ MicrosoftCalendar,
+ MicrosoftCalendarEvent,
+ Attendee,
+ ResponseStatus,
+ DateTimeTimeZone,
+ EmailAddress,
+};
+
+export const ResponseStatusMap = {
+ needsAction: 'none',
+ accepted: 'accepted',
+ declined: 'declined',
+ maybe: 'tentativelyAccepted',
+ notResponded: 'notResponded',
+};
diff --git a/plugins/microsoft-calendar/src/components/AttendeeChip.tsx b/plugins/microsoft-calendar/src/components/AttendeeChip.tsx
new file mode 100644
index 0000000000..67dd601da7
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/AttendeeChip.tsx
@@ -0,0 +1,89 @@
+/*
+ * 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 { Attendee, ResponseStatusMap } from '../api';
+
+const useStyles = makeStyles((theme: BackstageTheme) => {
+ const getIconColor = (responseStatus?: string) => {
+ if (!responseStatus) return theme.palette.primary.light;
+
+ return {
+ [ResponseStatusMap.accepted]: theme.palette.status.ok,
+ [ResponseStatusMap.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 }: { responseStatus: string }) => {
+ if (responseStatus === ResponseStatusMap.accepted) {
+ return ;
+ }
+ if (responseStatus === ResponseStatusMap.declined) {
+ return ;
+ }
+
+ return null;
+};
+
+type AttendeeChipProps = {
+ user: Attendee;
+};
+
+export const AttendeeChip = ({ user }: AttendeeChipProps) => {
+ const classes = useStyles({ responseStatus: user.status?.response || '' });
+
+ return (
+
+ }
+ >
+
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/CalendarCard.tsx b/plugins/microsoft-calendar/src/components/CalendarCard.tsx
new file mode 100644
index 0000000000..dd94bad148
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/CalendarCard.tsx
@@ -0,0 +1,141 @@
+/*
+ * 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, { useState } from 'react';
+
+import { InfoCard, Progress } from '@backstage/core-components';
+
+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 } from '../hooks';
+import calendarIcon from '../icons/calendar.svg';
+import { CalendarEvent } from './CalendarEvent';
+import { CalendarSelect } from './CalendarSelect';
+import { SignInContent } from './SignInContent';
+import { getStartDate } from './util';
+import useAsync from 'react-use/lib/useAsync';
+
+export const CalendarCard = () => {
+ const [date, setDate] = useState(DateTime.now());
+ const [selectedCalendarId, setSelectedCalendarId] = useState('');
+
+ const changeDay = (offset = 1) => {
+ setDate(prev => prev.plus({ day: offset }));
+ };
+
+ const { isSignedIn, isInitialized, signIn } = useSignIn();
+
+ useAsync(async () => signIn(true), [signIn]);
+
+ const {
+ isLoading: isCalendarLoading,
+ isFetching: isCalendarFetching,
+ data: calendars = [],
+ } = useCalendarsQuery({
+ enabled: isSignedIn,
+ });
+
+ const defaultCalendarId = calendars.find(c => c.isDefaultCalendar)?.id;
+
+ const { data: events, isLoading: isEventLoading } = useEventsQuery({
+ calendarId: selectedCalendarId || defaultCalendarId || '',
+ enabled: isSignedIn && calendars.length > 0,
+ timeMin: date.startOf('day').toISO(),
+ timeMax: date.endOf('day').toISO(),
+ timeZone: date.zoneName,
+ });
+
+ const showLoader =
+ (isCalendarLoading && isCalendarFetching) ||
+ isEventLoading ||
+ !isInitialized;
+
+ return (
+
+
+
+
+ {isSignedIn ? (
+ <>
+ changeDay(-1)} size="small">
+
+
+ changeDay(1)} size="small">
+
+
+
+
+ {date.toLocaleString({
+ weekday: 'short',
+ month: 'short',
+ day: 'numeric',
+ })}
+
+
+
+
+
+ >
+ ) : (
+ Agenda
+ )}
+
+ }
+ deepLink={{
+ link: 'https://outlook.office.com/calendar/',
+ title: 'Go to Calendar',
+ }}
+ >
+
+ {showLoader && (
+
+
+
+ )}
+ {!isSignedIn && isInitialized && (
+ signIn(false)} />
+ )}
+ {!isEventLoading && !isCalendarLoading && isSignedIn && (
+
+ {events?.length === 0 && (
+
+
+ No events
+
+
+ )}
+ {sortBy(events, [getStartDate]).map(event => (
+
+ ))}
+
+ )}
+
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/CalendarEvent.tsx b/plugins/microsoft-calendar/src/components/CalendarEvent.tsx
new file mode 100644
index 0000000000..e56e25a09b
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/CalendarEvent.tsx
@@ -0,0 +1,166 @@
+/*
+ * 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 { Link } from '@backstage/core-components';
+
+import {
+ Box,
+ Paper,
+ Popover,
+ Tooltip,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+
+import webcamIcon from '../icons/webcam.svg';
+import { CalendarEventPopoverContent } from './CalendarEventPopoverContent';
+import { MicrosoftCalendarEvent } from '../api';
+import {
+ getOnlineMeetingLink,
+ getTimePeriod,
+ 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: {
+ width: 8,
+ borderTopLeftRadius: 4,
+ borderBottomLeftRadius: 4,
+ },
+ }),
+ {
+ name: 'MicrosoftCalendarEvent',
+ },
+);
+
+export const CalendarEvent = ({ event }: { event: MicrosoftCalendarEvent }) => {
+ const classes = useStyles();
+ const popoverState = usePopupState({
+ variant: 'popover',
+ popupId: event.id,
+ disableAutoFocus: true,
+ });
+ const [hovered, setHovered] = useState(false);
+ const onlineMeetingLink = getOnlineMeetingLink(event);
+
+ const { onClick, ...restBindProps } = bindTrigger(popoverState);
+
+ return (
+ <>
+ {
+ onClick(e);
+ }}
+ {...restBindProps}
+ onMouseEnter={() => setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ elevation={hovered ? 4 : 1}
+ className={classnames(classes.event, {
+ [classes.passed]: isPassed(event),
+ })}
+ data-testid="microsoft-calendar-event"
+ >
+
+
+
+ {event.subject}
+
+ {!isAllDay(event) && (
+
+ {getTimePeriod(event)}
+
+ )}
+
+
+ {event.isOnlineMeeting && (
+
+ {
+ e.stopPropagation();
+ }}
+ noTrack
+ >
+ {/* we can use onlineMeetingProvider to show icon accordingly */}
+
+
+
+ )}
+
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx b/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx
new file mode 100644
index 0000000000..715a1ad276
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx
@@ -0,0 +1,126 @@
+/*
+ * 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 { Link } from '@backstage/core-components';
+import {
+ Box,
+ Divider,
+ IconButton,
+ Tooltip,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
+
+import { AttendeeChip } from './AttendeeChip';
+import { MicrosoftCalendarEvent } from '../api';
+import { getTimePeriod, getOnlineMeetingLink } from './util';
+
+const useStyles = makeStyles(
+ theme => ({
+ description: {
+ wordBreak: 'break-word',
+ '& a': {
+ color: theme.palette.primary.main,
+ fontWeight: 500,
+ },
+ },
+ divider: {
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(2),
+ },
+ }),
+ {
+ name: 'MicrosoftCalendarEventPopoverContent',
+ },
+);
+
+type CalendarEventPopoverProps = {
+ event: MicrosoftCalendarEvent;
+};
+
+export const CalendarEventPopoverContent = ({
+ event,
+}: CalendarEventPopoverProps) => {
+ const classes = useStyles();
+ const onlineMeetingLink = getOnlineMeetingLink(event);
+
+ return (
+
+
+
+ {event.subject}
+ {getTimePeriod(event)}
+
+ {event.webLink && (
+
+ {}}
+ noTrack
+ >
+
+
+
+
+
+ )}
+
+ {onlineMeetingLink && (
+ {}} noTrack>
+ Join Online Meeting
+
+ )}
+
+ {event.bodyPreview && (
+ <>
+
+
+ >
+ )}
+
+ {event.attendees && (
+ <>
+
+
+ Attendees
+
+ {sortBy(event.attendees || [], 'emailAddress').map(user => (
+
+ ))}
+
+ >
+ )}
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/CalendarSelect.tsx b/plugins/microsoft-calendar/src/components/CalendarSelect.tsx
new file mode 100644
index 0000000000..59e2376079
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/CalendarSelect.tsx
@@ -0,0 +1,92 @@
+/*
+ * 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 { MicrosoftCalendar } from '../api';
+
+const useStyles = makeStyles(
+ {
+ formControl: {
+ width: 120,
+ },
+ selectedCalendars: {
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ },
+ },
+ {
+ name: 'MicrosoftCalendarSelect',
+ },
+);
+
+type CalendarSelectProps = {
+ disabled: boolean;
+ selectedCalendarId?: string;
+ setSelectedCalendarId: (value: string) => void;
+ calendars: MicrosoftCalendar[];
+};
+
+export const CalendarSelect = ({
+ disabled,
+ selectedCalendarId,
+ setSelectedCalendarId,
+ calendars,
+}: CalendarSelectProps) => {
+ const classes = useStyles();
+ return (
+
+
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/MicrosoftCalendar.tsx b/plugins/microsoft-calendar/src/components/MicrosoftCalendar.tsx
new file mode 100644
index 0000000000..3bb15f7762
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/MicrosoftCalendar.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 '@tanstack/react-query';
+
+import { CalendarCard } from './CalendarCard';
+
+const queryClient = new QueryClient();
+
+export const MicrosoftCalendar = () => {
+ return (
+
+
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/SignInContent.tsx b/plugins/microsoft-calendar/src/components/SignInContent.tsx
new file mode 100644
index 0000000000..d33698f68c
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/SignInContent.tsx
@@ -0,0 +1,62 @@
+/*
+ * 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 mockEvents from './eventMock.json';
+import { MicrosoftCalendarEvent } from '../api';
+
+type Props = {
+ handleAuthClick: React.MouseEventHandler;
+};
+
+const TransparentBox = styled(Box)({
+ opacity: 0.3,
+ filter: 'blur(1.5px)',
+});
+
+export const SignInContent = ({ handleAuthClick }: Props) => {
+ return (
+
+
+ {(mockEvents as MicrosoftCalendarEvent[]).map(event => (
+
+ ))}
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/microsoft-calendar/src/components/eventMock.json b/plugins/microsoft-calendar/src/components/eventMock.json
new file mode 100644
index 0000000000..b648d1b486
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/eventMock.json
@@ -0,0 +1,138 @@
+[
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MAFRAAgI2vdUnGxAAEYAAAAABZfL1rFDfUO0G4cMgXCC1gcApFI7gxQldkOLWHDOlgt60AAAAAABDQAApFI7gxQldkOLWHDOlgt60AAAYl_I0AAAEA==",
+ "createdDateTime": "2022-12-22T08:28:36.6131501Z",
+ "lastModifiedDateTime": "2023-01-20T05:24:31.9666518Z",
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAd5KhaA==",
+ "categories": [],
+ "transactionId": null,
+ "originalStartTimeZone": "India Standard Time",
+ "originalEndTimeZone": "India Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E00807E7011006D3A5A92DC7D80100000000000000001000000002BB709705E28C4493E46124FF07DD89",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "Abhay Soni",
+ "bodyPreview": "",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": false,
+ "responseRequested": true,
+ "seriesMasterId": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=",
+ "showAs": "tentative",
+ "type": "occurrence",
+ "webLink": "",
+ "onlineMeetingUrl": "",
+ "isOnlineMeeting": true,
+ "onlineMeetingProvider": "teamsForBusiness",
+ "allowNewTimeProposals": true,
+ "occurrenceId": "OID.AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=.2023-01-16",
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "notResponded",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": ""
+ },
+ "start": {
+ "dateTime": "2023-01-16T11:00:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "end": {
+ "dateTime": "2023-01-16T11:30:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [],
+ "organizer": {
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ "onlineMeeting": {
+ "joinUrl": "https://abhay-soni-developer.github.io/MyReusme/"
+ }
+ },
+ {
+ "id": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MAFRAAgI2vdUnGxAAEYAAAAABZfL1rFDfUO0G4cMgXCC1gcApFI7gxQldkOLWHDOlgt60AAAAAABDQAApFI7gxQldkOLWHDOlgt60AAAYl_I0AAAEA==",
+ "createdDateTime": "2022-12-22T08:28:36.6131501Z",
+ "lastModifiedDateTime": "2023-01-20T05:24:31.9666518Z",
+ "changeKey": "pFI7gxQldkOLWHDOlgt60AAAd5KhaA==",
+ "categories": [],
+ "transactionId": null,
+ "originalStartTimeZone": "India Standard Time",
+ "originalEndTimeZone": "India Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E00807E7011006D3A5A92DC7D80100000000000000001000000002BB709705E28C4493E46124FF07DD89",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "https://abhay-soni-developer.github.io/MyReusme/",
+ "bodyPreview": "",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": false,
+ "responseRequested": true,
+ "seriesMasterId": "AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=",
+ "showAs": "tentative",
+ "type": "occurrence",
+ "webLink": "",
+ "onlineMeetingUrl": "",
+ "isOnlineMeeting": true,
+ "onlineMeetingProvider": "teamsForBusiness",
+ "allowNewTimeProposals": true,
+ "occurrenceId": "OID.AAMkADQyMDdlODU1LTk4Y2ItNGFmYi04YzYyLWQ4NmEzMmRhZTY5MABGAAAAAAAFl8vWsUN9Q7QbhwyBcILWBwCkUjuDFCV2Q4tYcM6WC3rQAAAAAAENAACkUjuDFCV2Q4tYcM6WC3rQAABiX4jQAAA=.2023-01-16",
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "notResponded",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": ""
+ },
+ "start": {
+ "dateTime": "2023-01-16T11:00:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "end": {
+ "dateTime": "2023-01-16T11:30:00.0000000",
+ "timeZone": "Asia/Calcutta"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [],
+ "organizer": {
+ "emailAddress": {
+ "name": "Abhay Soni",
+ "address": "abhaysoni.developer@gmail.com"
+ }
+ },
+ "onlineMeeting": {
+ "joinUrl": "https://abhay-soni-developer.github.io/MyReusme/"
+ }
+ }
+]
diff --git a/plugins/microsoft-calendar/src/components/index.ts b/plugins/microsoft-calendar/src/components/index.ts
new file mode 100644
index 0000000000..8149695804
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/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 { MicrosoftCalendar } from './MicrosoftCalendar';
diff --git a/plugins/microsoft-calendar/src/components/util.ts b/plugins/microsoft-calendar/src/components/util.ts
new file mode 100644
index 0000000000..dd52402d9d
--- /dev/null
+++ b/plugins/microsoft-calendar/src/components/util.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { MicrosoftCalendarEvent } from '../api';
+
+export function getOnlineMeetingLink(event: MicrosoftCalendarEvent) {
+ const onlineEntrypoint =
+ event.onlineMeeting?.joinUrl || event.onlineMeetingUrl;
+ if (onlineEntrypoint) {
+ return onlineEntrypoint;
+ }
+ return '';
+}
+
+export function getTimePeriod(event: MicrosoftCalendarEvent) {
+ 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: MicrosoftCalendarEvent) {
+ const format: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric' };
+ const startTime = DateTime.fromISO(event.start?.dateTime || '');
+ const endTime = DateTime.fromISO(event.end?.dateTime || '').minus({ day: 1 });
+
+ if (startTime.toISO() === endTime.toISO()) {
+ return startTime.toLocaleString(format);
+ }
+
+ return `${startTime.toLocaleString(format)} - ${endTime.toLocaleString(
+ format,
+ )}`;
+}
+
+export function isPassed(event: MicrosoftCalendarEvent) {
+ if (!event.end?.dateTime) return false;
+ const eventDate = DateTime.fromISO(event.end?.dateTime!);
+ return DateTime.now() >= eventDate;
+}
+
+export function isAllDay(event: MicrosoftCalendarEvent) {
+ 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: MicrosoftCalendarEvent) {
+ return DateTime.fromISO(event.start?.dateTime || '');
+}
diff --git a/plugins/microsoft-calendar/src/hooks/index.ts b/plugins/microsoft-calendar/src/hooks/index.ts
new file mode 100644
index 0000000000..a10f6a6c3a
--- /dev/null
+++ b/plugins/microsoft-calendar/src/hooks/index.ts
@@ -0,0 +1,18 @@
+/*
+ * 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';
diff --git a/plugins/microsoft-calendar/src/hooks/useCalendarsQuery.ts b/plugins/microsoft-calendar/src/hooks/useCalendarsQuery.ts
new file mode 100644
index 0000000000..47a7990b58
--- /dev/null
+++ b/plugins/microsoft-calendar/src/hooks/useCalendarsQuery.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 '@tanstack/react-query';
+
+import { errorApiRef, useApi } from '@backstage/core-plugin-api';
+
+import { microsoftCalendarApiRef } from '../api';
+
+type Options = {
+ enabled: boolean;
+ refreshTime?: number;
+};
+
+export const useCalendarsQuery = ({ enabled }: Options) => {
+ const calendarApi = useApi(microsoftCalendarApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ return useQuery(
+ ['calendars-list'],
+ async () => {
+ const calendars = [];
+ const value = await calendarApi.getCalendars();
+ calendars.push(...value);
+ return calendars;
+ },
+ {
+ enabled,
+ // old data will be returned if last request was made within 3 mins.
+ staleTime: 180000,
+ refetchInterval: 3600000,
+ onError: () => {
+ errorApi.post({
+ name: 'API error',
+ message: 'Failed to fetch calendars.',
+ });
+ },
+ },
+ );
+};
diff --git a/plugins/microsoft-calendar/src/hooks/useEventsQuery.ts b/plugins/microsoft-calendar/src/hooks/useEventsQuery.ts
new file mode 100644
index 0000000000..5e9dc76812
--- /dev/null
+++ b/plugins/microsoft-calendar/src/hooks/useEventsQuery.ts
@@ -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 { useQuery } from '@tanstack/react-query';
+
+import { useApi } from '@backstage/core-plugin-api';
+
+import { microsoftCalendarApiRef } from '../api';
+
+type Options = {
+ calendarId: string;
+ timeMin: string;
+ timeMax: string;
+ enabled?: boolean;
+ timeZone?: string;
+ refreshTime?: number;
+};
+
+export const useEventsQuery = ({
+ calendarId,
+ enabled,
+ timeMin,
+ timeMax,
+ timeZone,
+}: Options) => {
+ const calendarApi = useApi(microsoftCalendarApiRef);
+ return useQuery(
+ ['calendarEvents', calendarId, timeMin, timeMax, timeZone],
+ async () => {
+ const data = await calendarApi.getEvents(
+ calendarId,
+ {
+ startDateTime: timeMin,
+ endDateTime: timeMax,
+ },
+ {
+ Prefer: `outlook.timezone="${timeZone}"`,
+ },
+ );
+ return data;
+ },
+ {
+ cacheTime: 300000,
+ enabled,
+ refetchInterval: 60000,
+ refetchIntervalInBackground: true,
+ },
+ );
+};
diff --git a/plugins/microsoft-calendar/src/hooks/useSignIn.ts b/plugins/microsoft-calendar/src/hooks/useSignIn.ts
new file mode 100644
index 0000000000..8b3ac54f92
--- /dev/null
+++ b/plugins/microsoft-calendar/src/hooks/useSignIn.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { microsoftAuthApiRef, useApi } from '@backstage/core-plugin-api';
+
+export const useSignIn = () => {
+ const [isSignedIn, setSignedIn] = useState(false);
+ const [isInitialized, setInitialized] = useState(false);
+ const authApi = useApi(microsoftAuthApiRef);
+
+ const signIn = useCallback(
+ async (optional = false) => {
+ const token = await authApi.getAccessToken('Calendars.Read', {
+ optional,
+ instantPopup: !optional,
+ });
+
+ setSignedIn(!!token);
+ setInitialized(true);
+ },
+ [authApi, setSignedIn],
+ );
+
+ return { isSignedIn, isInitialized, signIn };
+};
diff --git a/plugins/microsoft-calendar/src/icons/calendar.svg b/plugins/microsoft-calendar/src/icons/calendar.svg
new file mode 100644
index 0000000000..79895da87a
--- /dev/null
+++ b/plugins/microsoft-calendar/src/icons/calendar.svg
@@ -0,0 +1,56 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/microsoft-calendar/src/icons/webcam.svg b/plugins/microsoft-calendar/src/icons/webcam.svg
new file mode 100644
index 0000000000..356bb69215
--- /dev/null
+++ b/plugins/microsoft-calendar/src/icons/webcam.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/microsoft-calendar/src/index.ts b/plugins/microsoft-calendar/src/index.ts
new file mode 100644
index 0000000000..6867ca31ac
--- /dev/null
+++ b/plugins/microsoft-calendar/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 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 { MicrosoftCalendarCard } from './plugin';
+export { MicrosoftCalendarApiClient, microsoftCalendarApiRef } from './api';
diff --git a/plugins/microsoft-calendar/src/plugin.test.ts b/plugins/microsoft-calendar/src/plugin.test.ts
new file mode 100644
index 0000000000..7da86e176b
--- /dev/null
+++ b/plugins/microsoft-calendar/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 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 { microsoftCalendarPlugin } from './plugin';
+
+describe('microsoft-calendar', () => {
+ it('should export plugin', () => {
+ expect(microsoftCalendarPlugin).toBeDefined();
+ });
+});
diff --git a/plugins/microsoft-calendar/src/plugin.ts b/plugins/microsoft-calendar/src/plugin.ts
new file mode 100644
index 0000000000..644d368d97
--- /dev/null
+++ b/plugins/microsoft-calendar/src/plugin.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 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 {
+ createPlugin,
+ microsoftAuthApiRef,
+ fetchApiRef,
+ createApiFactory,
+ createComponentExtension,
+} from '@backstage/core-plugin-api';
+import {
+ microsoftCalendarApiRef,
+ MicrosoftCalendarApiClient,
+} from './api/index';
+
+export const microsoftCalendarPlugin = createPlugin({
+ id: 'microsoft-calendar',
+ apis: [
+ createApiFactory({
+ api: microsoftCalendarApiRef,
+ deps: { authApi: microsoftAuthApiRef, fetchApi: fetchApiRef },
+ factory(deps) {
+ return new MicrosoftCalendarApiClient(deps);
+ },
+ }),
+ ],
+});
+
+/** @public */
+export const MicrosoftCalendarCard = microsoftCalendarPlugin.provide(
+ createComponentExtension({
+ name: 'MicrosoftCalendarCard',
+ component: {
+ lazy: () => import('./components').then(m => m.MicrosoftCalendar),
+ },
+ }),
+);
diff --git a/plugins/microsoft-calendar/src/setupTests.ts b/plugins/microsoft-calendar/src/setupTests.ts
new file mode 100644
index 0000000000..73dd8dce47
--- /dev/null
+++ b/plugins/microsoft-calendar/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2023 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 17c8fd3f2c..2929f047a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6895,6 +6895,39 @@ __metadata:
languageName: unknown
linkType: soft
+"@backstage/plugin-microsoft-calendar@workspace:^, @backstage/plugin-microsoft-calendar@workspace:plugins/microsoft-calendar":
+ version: 0.0.0-use.local
+ resolution: "@backstage/plugin-microsoft-calendar@workspace:plugins/microsoft-calendar"
+ dependencies:
+ "@backstage/cli": "workspace:^"
+ "@backstage/core-app-api": "workspace:^"
+ "@backstage/core-components": "workspace:^"
+ "@backstage/core-plugin-api": "workspace:^"
+ "@backstage/dev-utils": "workspace:^"
+ "@backstage/errors": "workspace:^"
+ "@backstage/test-utils": "workspace:^"
+ "@backstage/theme": "workspace:^"
+ "@material-ui/core": ^4.12.2
+ "@material-ui/icons": ^4.9.1
+ "@microsoft/microsoft-graph-types": ^2.25.0
+ "@tanstack/react-query": ^4.1.3
+ "@testing-library/jest-dom": ^5.10.1
+ "@testing-library/react": ^12.1.3
+ "@testing-library/user-event": ^14.0.0
+ "@types/node": ^16.11.26
+ classnames: ^2.3.1
+ cross-fetch: ^3.1.5
+ dompurify: ^2.3.6
+ lodash: ^4.17.21
+ luxon: ^3.0.0
+ material-ui-popup-state: ^1.9.3
+ msw: ^0.49.0
+ react-use: ^17.2.4
+ peerDependencies:
+ react: ^16.13.1 || ^17.0.0
+ languageName: unknown
+ linkType: soft
+
"@backstage/plugin-newrelic-dashboard@workspace:^, @backstage/plugin-newrelic-dashboard@workspace:plugins/newrelic-dashboard":
version: 0.0.0-use.local
resolution: "@backstage/plugin-newrelic-dashboard@workspace:plugins/newrelic-dashboard"
@@ -11514,7 +11547,7 @@ __metadata:
languageName: node
linkType: hard
-"@microsoft/microsoft-graph-types@npm:^2.6.0":
+"@microsoft/microsoft-graph-types@npm:^2.25.0, @microsoft/microsoft-graph-types@npm:^2.6.0":
version: 2.25.0
resolution: "@microsoft/microsoft-graph-types@npm:2.25.0"
checksum: b311385e0a51f827082af5a016f35b1d6cd4d861121e8767b3279ab1a17475977cddbbdcc208abdddf0f953fd6ce4634d3c302e317cf50af4cb6111385347b4f
@@ -22194,6 +22227,7 @@ __metadata:
"@backstage/plugin-kafka": "workspace:^"
"@backstage/plugin-kubernetes": "workspace:^"
"@backstage/plugin-lighthouse": "workspace:^"
+ "@backstage/plugin-microsoft-calendar": "workspace:^"
"@backstage/plugin-newrelic": "workspace:^"
"@backstage/plugin-newrelic-dashboard": "workspace:^"
"@backstage/plugin-org": "workspace:^"