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..b0491df93f 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 { MicrosoftCalendar } 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..e170491569 --- /dev/null +++ b/plugins/microsoft-calendar/README.md @@ -0,0 +1,15 @@ +# microsoft-calendar + +Welcome to the microsoft-calendar plugin! + +_This plugin was created through the Backstage CLI_ + +You can use this plugin by import MicrosoftCalendar from "@internal/plugin-microsoft-calendar" + +> import { MicrosoftCalendar } from "@internal/plugin-microsoft-calendar" + +![MicrosoftCalendar plugin screenshot](./docs/microsoft-calendar-plugin.png) + +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..38187b39b2 --- /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 const MicrosoftCalendar: () => JSX.Element; + +// @public (undocumented) +export class MicrosoftCalendarApiClient { + constructor(options: { authApi: OAuthApi; fetchApi: FetchApi }); + // (undocumented) + getCalendars(params?: any): Promise; + // (undocumented) + getEvents( + calendarId: string, + params: { + startDateTime: string; + endDateTime: string; + }, + headers: { + [key in string]: any; + }, + ): Promise; +} + +// @public (undocumented) +export const microsoftCalendarApiRef: ApiRef; + +// (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..18c5e8c08d --- /dev/null +++ b/plugins/microsoft-calendar/dev/index.tsx @@ -0,0 +1,27 @@ +/* + * 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, MicrosoftCalendar } from '../src/plugin'; + +createDevApp() + .registerPlugin(microsoftCalendarPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/microsoft-calendar', + }) + .render(); 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..57aaead452 --- /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(params?: any): Promise { + const data = await this.get<{ + id: string; + value: MicrosoftCalendar[]; + }>('v1.0/me/calendars', params); + + 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..dba12bdea5 --- /dev/null +++ b/plugins/microsoft-calendar/src/components/CalendarCard.tsx @@ -0,0 +1,145 @@ +/* + * 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 ( + + + Microsoft Calendar + + {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..e6e5bf03fa --- /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({ event }); + 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 */} + Online Meeting link + + + )} + + + + + + + ); +}; diff --git a/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx b/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx new file mode 100644 index 0000000000..60def9b6e3 --- /dev/null +++ b/plugins/microsoft-calendar/src/components/CalendarEventPopoverContent.tsx @@ -0,0 +1,128 @@ +/* + * 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 => { + 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: MicrosoftCalendarEvent; +}; + +export const CalendarEventPopoverContent = ({ + event, +}: CalendarEventPopoverProps) => { + const classes = useStyles({ event }); + 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 ( + + } + renderValue={selected => { + return ( + + {calendars.find(c => c.id === selected)?.name} + + ); + }} + MenuProps={{ + PaperProps: { + style: { + width: 350, + }, + }, + }} + > + {sortBy(calendars, 'name').map(c => ( + + + + + ))} + + + ); +}; 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..08055569cf --- /dev/null +++ b/plugins/microsoft-calendar/src/components/util.ts @@ -0,0 +1,80 @@ +/* + * 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) { + // if('isAllDay' in event) { + // return event.isAllDay; + // } + + 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..9e0bb8fa12 --- /dev/null +++ b/plugins/microsoft-calendar/src/hooks/useEventsQuery.ts @@ -0,0 +1,63 @@ +/* + * 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 { 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..5d37eb048d --- /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 { MicrosoftCalendar } 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..6c67ef33b4 --- /dev/null +++ b/plugins/microsoft-calendar/src/plugin.ts @@ -0,0 +1,53 @@ +/* + * 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'; +import { rootRouteRef } from './routes'; + +export const microsoftCalendarPlugin = createPlugin({ + id: 'microsoft-calendar', + routes: { + root: rootRouteRef, + }, + apis: [ + createApiFactory({ + api: microsoftCalendarApiRef, + deps: { authApi: microsoftAuthApiRef, fetchApi: fetchApiRef }, + factory(deps) { + return new MicrosoftCalendarApiClient(deps); + }, + }), + ], +}); + +/** @public */ +export const MicrosoftCalendar = microsoftCalendarPlugin.provide( + createComponentExtension({ + name: 'MicrosoftCalendar', + component: { + lazy: () => import('./components').then(m => m.MicrosoftCalendar), + }, + }), +); diff --git a/plugins/microsoft-calendar/src/routes.ts b/plugins/microsoft-calendar/src/routes.ts new file mode 100644 index 0000000000..c257690e24 --- /dev/null +++ b/plugins/microsoft-calendar/src/routes.ts @@ -0,0 +1,20 @@ +/* + * 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 { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'microsoft-calendar', +}); 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 7c65c54e5f..9d9d1b38e5 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:^"