├── graph-app
├── src
│ ├── assets
│ │ ├── .gitkeep
│ │ └── no-profile-photo.png
│ ├── app
│ │ ├── app.component.css
│ │ ├── home
│ │ │ ├── home.component.css
│ │ │ ├── home.component.spec.ts
│ │ │ ├── home.component.html
│ │ │ └── home.component.ts
│ │ ├── alerts
│ │ │ ├── alerts.component.css
│ │ │ ├── alerts.component.html
│ │ │ ├── alerts.component.ts
│ │ │ └── alerts.component.spec.ts
│ │ ├── nav-bar
│ │ │ ├── nav-bar.component.css
│ │ │ ├── nav-bar.component.spec.ts
│ │ │ ├── nav-bar.component.ts
│ │ │ └── nav-bar.component.html
│ │ ├── calendar
│ │ │ ├── calendar.component.css
│ │ │ ├── calendar.component.html
│ │ │ ├── calendar.component.spec.ts
│ │ │ └── calendar.component.ts
│ │ ├── new-event
│ │ │ ├── new-event.component.css
│ │ │ ├── new-event.component.spec.ts
│ │ │ ├── new-event.component.ts
│ │ │ ├── new-event.ts
│ │ │ └── new-event.component.html
│ │ ├── alert.ts
│ │ ├── user.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── auth.service.spec.ts
│ │ ├── graph.service.spec.ts
│ │ ├── alerts.service.spec.ts
│ │ ├── alerts.service.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.spec.ts
│ │ ├── app.module.ts
│ │ ├── graph.service.ts
│ │ └── auth.service.ts
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── oauth.example.ts
│ ├── index.html
│ ├── styles.css
│ ├── main.ts
│ ├── test.ts
│ └── polyfills.ts
├── .prettierrc
├── .editorconfig
├── tsconfig.app.json
├── tsconfig.spec.json
├── .gitignore
├── tsconfig.json
├── README.md
├── karma.conf.js
├── package.json
└── angular.json
├── .gitattributes
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── ask-a-question.md
│ └── bug_report.md
├── workflows
│ ├── auto-merge-dependabot.yml
│ └── node.js.yml
└── policies
│ └── resourceManagement.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── .gitignore
└── README.md
/graph-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/app.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/home/home.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts/alerts.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/nav-bar/nav-bar.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/calendar/calendar.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/src/app/new-event/new-event.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/graph-app/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "endOfLine": "auto"
4 | }
5 |
--------------------------------------------------------------------------------
/graph-app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/graph-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-angularspa/HEAD/graph-app/src/favicon.ico
--------------------------------------------------------------------------------
/graph-app/src/assets/no-profile-photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-angularspa/HEAD/graph-app/src/assets/no-profile-photo.png
--------------------------------------------------------------------------------
/graph-app/src/app/alert.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | export class Alert {
5 | type!: string;
6 | message!: string;
7 | debug!: string;
8 | }
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Denote all files that are truly binary and should not be modified.
5 | *.png binary
6 | *.jpg binary
--------------------------------------------------------------------------------
/graph-app/src/app/user.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | export class User {
5 | displayName!: string;
6 | email!: string;
7 | avatar!: string;
8 | timeZone!: string;
9 | }
10 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "graph-app/"
5 | schedule:
6 | interval: "weekly"
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 |
--------------------------------------------------------------------------------
/graph-app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/graph-app/src/oauth.example.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | export const OAuthSettings = {
5 | appId: 'YOUR_APP_ID_HERE',
6 | redirectUri: 'http://localhost:4200',
7 | scopes: ['user.read', 'mailboxsettings.read', 'calendars.readwrite'],
8 | };
9 |
--------------------------------------------------------------------------------
/graph-app/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/graph-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Graph App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/graph-app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component } from '@angular/core';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.css'],
10 | })
11 | export class AppComponent {
12 | title = 'graph-app';
13 | }
14 |
--------------------------------------------------------------------------------
/graph-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts/alerts.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | {{alert.message}}
7 | {{alert.debug}}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/graph-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/graph-app/src/styles.css:
--------------------------------------------------------------------------------
1 | @import "~bootstrap/dist/css/bootstrap.css";
2 |
3 | /* Add padding for the nav bar */
4 | body {
5 | padding-top: 4.5rem;
6 | }
7 |
8 | /* Style debug info in alerts */
9 | .alert-pre {
10 | word-wrap: break-word;
11 | word-break: break-all;
12 | white-space: pre-wrap;
13 | }
14 |
15 | /* Add padding to user avatar link */
16 | .avatar-link {
17 | padding-top: .25em;
18 | padding-bottom: .25em;
19 | }
20 |
--------------------------------------------------------------------------------
/graph-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch((err) => console.error(err));
14 |
--------------------------------------------------------------------------------
/graph-app/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting,
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | // First, initialize the Angular testing environment.
11 | getTestBed().initTestEnvironment(
12 | BrowserDynamicTestingModule,
13 | platformBrowserDynamicTesting(),
14 | );
15 |
--------------------------------------------------------------------------------
/graph-app/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { TestBed } from '@angular/core/testing';
5 |
6 | import { AuthService } from './auth.service';
7 |
8 | describe('AuthService', () => {
9 | let service: AuthService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({});
13 | service = TestBed.inject(AuthService);
14 | });
15 |
16 | it('should be created', () => {
17 | expect(service).toBeTruthy();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/graph-app/src/app/graph.service.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { TestBed } from '@angular/core/testing';
5 |
6 | import { GraphService } from './graph.service';
7 |
8 | describe('GraphService', () => {
9 | let service: GraphService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({});
13 | service = TestBed.inject(GraphService);
14 | });
15 |
16 | it('should be created', () => {
17 | expect(service).toBeTruthy();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts.service.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { TestBed } from '@angular/core/testing';
5 |
6 | import { AlertsService } from './alerts.service';
7 |
8 | describe('AlertsService', () => {
9 | let service: AlertsService;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({});
13 | service = TestBed.inject(AlertsService);
14 | });
15 |
16 | it('should be created', () => {
17 | expect(service).toBeTruthy();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support)
11 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts/alerts.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component, OnInit } from '@angular/core';
5 | import { AlertsService } from '../alerts.service';
6 | import { Alert } from '../alert';
7 |
8 | @Component({
9 | selector: 'app-alerts',
10 | templateUrl: './alerts.component.html',
11 | styleUrls: ['./alerts.component.css'],
12 | })
13 | export class AlertsComponent implements OnInit {
14 | constructor(public alertsService: AlertsService) {}
15 |
16 | ngOnInit() {}
17 |
18 | close(alert: Alert) {
19 | this.alertsService.remove(alert);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts.service.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Injectable } from '@angular/core';
5 | import { Alert } from './alert';
6 |
7 | @Injectable({
8 | providedIn: 'root',
9 | })
10 | export class AlertsService {
11 | alerts: Alert[] = [];
12 |
13 | addError(message: string, debug?: string) {
14 | this.alerts.push({ message: message, debug: debug ?? '', type: 'danger' });
15 | }
16 |
17 | addSuccess(message: string, debug?: string) {
18 | this.alerts.push({ message: message, debug: debug ?? '', type: 'success' });
19 | }
20 |
21 | remove(alert: Alert) {
22 | this.alerts.splice(this.alerts.indexOf(alert), 1);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/graph-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/graph-app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/graph-app/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { NgModule } from '@angular/core';
5 | import { RouterModule, Routes } from '@angular/router';
6 |
7 | import { HomeComponent } from './home/home.component';
8 | import { CalendarComponent } from './calendar/calendar.component';
9 | import { NewEventComponent } from './new-event/new-event.component';
10 |
11 | const routes: Routes = [
12 | { path: '', component: HomeComponent },
13 | { path: 'calendar', component: CalendarComponent },
14 | { path: 'newevent', component: NewEventComponent },
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule],
20 | })
21 | export class AppRoutingModule {}
22 |
--------------------------------------------------------------------------------
/graph-app/src/app/calendar/calendar.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 | Calendar
5 | New event
6 |
7 |
8 | | Organizer |
9 | Subject |
10 | Start |
11 | End |
12 |
13 |
14 |
15 | | {{event.organizer?.emailAddress?.name}} |
16 | {{event.subject}} |
17 | {{formatDateTimeTimeZone(event.start) | date: 'short' }} |
18 | {{formatDateTimeTimeZone(event.end) | date: 'short' }} |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/graph-app/src/app/home/home.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { ComponentFixture, TestBed } from '@angular/core/testing';
5 |
6 | import { HomeComponent } from './home.component';
7 |
8 | describe('HomeComponent', () => {
9 | let component: HomeComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [HomeComponent],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(HomeComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/graph-app/src/app/alerts/alerts.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { ComponentFixture, TestBed } from '@angular/core/testing';
5 |
6 | import { AlertsComponent } from './alerts.component';
7 |
8 | describe('AlertsComponent', () => {
9 | let component: AlertsComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [AlertsComponent],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(AlertsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/graph-app/src/app/nav-bar/nav-bar.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { ComponentFixture, TestBed } from '@angular/core/testing';
5 |
6 | import { NavBarComponent } from './nav-bar.component';
7 |
8 | describe('NavBarComponent', () => {
9 | let component: NavBarComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [NavBarComponent],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(NavBarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ask-a-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Ask a question
3 | about: Ask a question about Graph, adding features to this sample, etc.
4 | title: ''
5 | labels: question, needs triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | Thank you for taking an interest in Microsoft Graph development! Please feel free to ask a question here, but keep in mind the following:
11 |
12 | - This is not an official Microsoft support channel, and our ability to respond to questions here is limited. Questions about Graph, or questions about adding a new feature to the sample, will be answered on a best-effort basis.
13 | - Questions should be asked on [Microsoft Q&A](https://docs.microsoft.com/answers/products/graph).
14 | - Issues with Microsoft Graph itself should be handled through [support](https://developer.microsoft.com/graph/support).
15 |
--------------------------------------------------------------------------------
/graph-app/src/app/calendar/calendar.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { ComponentFixture, TestBed } from '@angular/core/testing';
5 |
6 | import { CalendarComponent } from './calendar.component';
7 |
8 | describe('CalendarComponent', () => {
9 | let component: CalendarComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [CalendarComponent],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(CalendarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/graph-app/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
Angular Graph Tutorial
7 |
This sample app shows how to use the Microsoft Graph API from Angular
8 |
9 |
10 | Welcome {{ user != null ? user.displayName : '' }}!
11 | Use the navigation bar at the top of the page to get started.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/graph-app/src/app/new-event/new-event.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { ComponentFixture, TestBed } from '@angular/core/testing';
5 |
6 | import { NewEventComponent } from './new-event.component';
7 |
8 | describe('NewEventComponent', () => {
9 | let component: NewEventComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [NewEventComponent],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(NewEventComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge-dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Auto-merge dependabot updates
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | permissions:
8 | pull-requests: write
9 | contents: write
10 |
11 | jobs:
12 |
13 | dependabot-merge:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | if: ${{ github.actor == 'dependabot[bot]' }}
18 |
19 | steps:
20 | - name: Dependabot metadata
21 | id: metadata
22 | uses: dependabot/fetch-metadata@v1.6.0
23 | with:
24 | github-token: "${{ secrets.GITHUB_TOKEN }}"
25 |
26 | - name: Enable auto-merge for Dependabot PRs
27 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
28 | run: gh pr merge --auto --merge "$PR_URL"
29 | env:
30 | PR_URL: ${{github.event.pull_request.html_url}}
31 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
32 |
--------------------------------------------------------------------------------
/graph-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "es2020",
21 | "lib": [
22 | "es2020",
23 | "dom"
24 | ],
25 | "useDefineForClassFields": false
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2022 Microsoft Graph
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/graph-app/README.md:
--------------------------------------------------------------------------------
1 | # GraphApp
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.1.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/graph-app/src/app/nav-bar/nav-bar.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component, OnInit } from '@angular/core';
5 |
6 | import { AuthService } from '../auth.service';
7 | import { User } from '../user';
8 |
9 | @Component({
10 | selector: 'app-nav-bar',
11 | templateUrl: './nav-bar.component.html',
12 | styleUrls: ['./nav-bar.component.css'],
13 | })
14 | export class NavBarComponent implements OnInit {
15 | // Should the collapsed nav show?
16 | showNav: boolean = false;
17 | // Is a user logged in?
18 | get authenticated(): boolean {
19 | return this.authService.authenticated;
20 | }
21 | // The user
22 | get user(): User | undefined {
23 | return this.authService.user;
24 | }
25 |
26 | constructor(private authService: AuthService) {}
27 |
28 | ngOnInit() {}
29 |
30 | // Used by the Bootstrap navbar-toggler button to hide/show
31 | // the nav in a collapsed state
32 | toggleNavBar(): void {
33 | this.showNav = !this.showNav;
34 | }
35 |
36 | async signIn(): Promise {
37 | await this.authService.signIn();
38 | }
39 |
40 | signOut(): void {
41 | this.authService.signOut();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # VS Code settings
64 | .vscode/
65 |
66 | oauth.ts
67 | graph-tutorial.OLD/
68 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build:
15 | defaults:
16 | run:
17 | working-directory: graph-app/
18 |
19 | runs-on: ubuntu-latest
20 |
21 | strategy:
22 | matrix:
23 | node-version: [16.x, 18.x]
24 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
25 |
26 | steps:
27 | - uses: actions/checkout@v4
28 | - name: Copy settings file
29 | run: |
30 | cp src/oauth.example.ts src/oauth.ts
31 | - name: Use Node.js ${{ matrix.node-version }}
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version: ${{ matrix.node-version }}
35 | - name: Install dependencies
36 | run: npm ci
37 | - name: Run linter
38 | run: npm run lint
39 | - name: Build with Angular CLI
40 | run: npm run build --if-present
41 |
--------------------------------------------------------------------------------
/graph-app/src/app/new-event/new-event.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component, OnInit } from '@angular/core';
5 |
6 | import { AuthService } from '../auth.service';
7 | import { GraphService } from '../graph.service';
8 | import { AlertsService } from '../alerts.service';
9 | import { NewEvent } from './new-event';
10 |
11 | @Component({
12 | selector: 'app-new-event',
13 | templateUrl: './new-event.component.html',
14 | styleUrls: ['./new-event.component.css'],
15 | })
16 | export class NewEventComponent implements OnInit {
17 | model = new NewEvent();
18 |
19 | constructor(
20 | private authService: AuthService,
21 | private graphService: GraphService,
22 | private alertsService: AlertsService,
23 | ) {}
24 |
25 | ngOnInit(): void {}
26 |
27 | async onSubmit(): Promise {
28 | const timeZone = this.authService.user?.timeZone ?? 'UTC';
29 | const graphEvent = this.model.getGraphEvent(timeZone);
30 |
31 | try {
32 | await this.graphService.addEventToCalendar(graphEvent);
33 | this.alertsService.addSuccess('Event created.');
34 | } catch (error: any) {
35 | this.alertsService.addError('Error creating event.', error.message);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug report, needs triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Where did you get the code?**
11 | - [ ] Downloaded from GitHub
12 | - [ ] Downloaded from the [Microsoft Graph quick start tool](https://developer.microsoft.com/graph/quick-start)
13 | - [ ] Followed the tutorial from [Microsoft Graph tutorials](https://docs.microsoft.com/graph/tutorials)
14 |
15 | **Describe the bug**
16 | A clear and concise description of what the bug is.
17 |
18 | **To Reproduce**
19 | Steps to reproduce the behavior:
20 | 1. Go to '...'
21 | 2. Click on '....'
22 | 3. Scroll down to '....'
23 | 4. See error
24 |
25 | **Expected behavior**
26 | A clear and concise description of what you expected to happen.
27 |
28 | **Screenshots**
29 | If applicable, add screenshots to help explain your problem.
30 |
31 | **Desktop (please complete the following information):**
32 | - OS: [e.g. iOS]
33 | - Browser [e.g. chrome, safari]
34 | - Version [e.g. 22]
35 |
36 | **Dependency versions**
37 | - Authentication library (MSAL, etc.) version:
38 | - Graph library (Graph SDK, REST library, etc.) version:
39 |
40 | **Additional context**
41 | Add any other context about the problem here.
42 |
--------------------------------------------------------------------------------
/graph-app/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { TestBed } from '@angular/core/testing';
5 | import { RouterTestingModule } from '@angular/router/testing';
6 | import { AppComponent } from './app.component';
7 |
8 | describe('AppComponent', () => {
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [RouterTestingModule],
12 | declarations: [AppComponent],
13 | }).compileComponents();
14 | });
15 |
16 | it('should create the app', () => {
17 | const fixture = TestBed.createComponent(AppComponent);
18 | const app = fixture.componentInstance;
19 | expect(app).toBeTruthy();
20 | });
21 |
22 | it(`should have as title 'graph-app'`, () => {
23 | const fixture = TestBed.createComponent(AppComponent);
24 | const app = fixture.componentInstance;
25 | expect(app.title).toEqual('graph-app');
26 | });
27 |
28 | it('should render title', () => {
29 | const fixture = TestBed.createComponent(AppComponent);
30 | fixture.detectChanges();
31 | const compiled = fixture.nativeElement as HTMLElement;
32 | expect(compiled.querySelector('.content span')?.textContent).toContain(
33 | 'graph-app app is running!',
34 | );
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/graph-app/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component, OnInit } from '@angular/core';
5 | import { AuthenticationResult } from '@azure/msal-browser';
6 |
7 | import { AuthService } from '../auth.service';
8 | import { User } from '../user';
9 |
10 | @Component({
11 | selector: 'app-home',
12 | templateUrl: './home.component.html',
13 | styleUrls: ['./home.component.css'],
14 | })
15 | export class HomeComponent implements OnInit {
16 | // Is a user logged in?
17 | get authenticated(): boolean {
18 | return this.authService.authenticated;
19 | }
20 | // The user
21 | get user(): User | undefined {
22 | return this.authService.user;
23 | }
24 |
25 | constructor(private authService: AuthService) {}
26 |
27 | ngOnInit() {
28 | // Necessary to handle logout redirect properly
29 | // See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/errors.md
30 | this.authService.handleRedirects().subscribe({
31 | next: (result: AuthenticationResult) => {},
32 | error: (error: any) => {
33 | console.log(error);
34 | },
35 | });
36 | }
37 |
38 | async signIn(): Promise {
39 | await this.authService.signIn();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/graph-app/src/app/new-event/new-event.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
5 |
6 | // Model for the new event form
7 | export class NewEvent {
8 | subject?: string;
9 | attendees?: string;
10 | start?: string;
11 | end?: string;
12 | body?: string;
13 |
14 | // Generate a MicrosoftGraph.Event from the model
15 | getGraphEvent(timeZone: string): MicrosoftGraph.Event {
16 | const graphEvent: MicrosoftGraph.Event = {
17 | subject: this.subject,
18 | start: {
19 | dateTime: this.start,
20 | timeZone: timeZone,
21 | },
22 | end: {
23 | dateTime: this.end,
24 | timeZone: timeZone,
25 | },
26 | };
27 |
28 | // If there are attendees, convert to array
29 | // and add them
30 | if (this.attendees && this.attendees.length > 0) {
31 | graphEvent.attendees = [];
32 |
33 | const emails = this.attendees.split(';');
34 | emails.forEach((email) => {
35 | graphEvent.attendees?.push({
36 | type: 'required',
37 | emailAddress: {
38 | address: email,
39 | },
40 | });
41 | });
42 | }
43 |
44 | // If there is a body, add it as plain text
45 | if (this.body && this.body.length > 0) {
46 | graphEvent.body = {
47 | contentType: 'text',
48 | content: this.body,
49 | };
50 | }
51 |
52 | return graphEvent;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/graph-app/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/graph-app'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/graph-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graph-app",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test",
10 | "lint": "prettier --check **/**.ts",
11 | "format": "prettier --write **/**.ts"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "^16.2.4",
16 | "@angular/common": "^16.2.4",
17 | "@angular/compiler": "^16.2.4",
18 | "@angular/core": "^16.2.4",
19 | "@angular/forms": "^16.2.4",
20 | "@angular/localize": "^16.2.4",
21 | "@angular/platform-browser": "^16.2.4",
22 | "@angular/platform-browser-dynamic": "^16.2.4",
23 | "@angular/router": "^16.2.4",
24 | "@azure/msal-angular": "^3.0.4",
25 | "@azure/msal-browser": "^3.1.0",
26 | "@microsoft/microsoft-graph-client": "^3.0.6",
27 | "@ng-bootstrap/ng-bootstrap": "^15.1.1",
28 | "@popperjs/core": "^2.11.8",
29 | "bootstrap": "^5.3.2",
30 | "date-fns": "^2.30.0",
31 | "date-fns-tz": "^2.0.0",
32 | "rxjs": "^7.8.0",
33 | "tslib": "^2.6.2",
34 | "windows-iana": "^5.1.0",
35 | "zone.js": "^0.13.3"
36 | },
37 | "devDependencies": {
38 | "@angular-devkit/build-angular": "^16.2.1",
39 | "@angular/cli": "~16.2.1",
40 | "@angular/compiler-cli": "^16.2.4",
41 | "@microsoft/microsoft-graph-types": "^2.38.0",
42 | "@types/jasmine": "~4.3.0",
43 | "jasmine-core": "^5.1.1",
44 | "karma": "~6.4.0",
45 | "karma-chrome-launcher": "^3.2.0",
46 | "karma-coverage": "~2.2.0",
47 | "karma-jasmine": "~5.1.0",
48 | "karma-jasmine-html-reporter": "^2.1.0",
49 | "prettier": "^3.0.3",
50 | "typescript": "5.1"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/graph-app/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { NgModule } from '@angular/core';
5 | import { BrowserModule } from '@angular/platform-browser';
6 | import { FormsModule } from '@angular/forms';
7 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
8 | import {
9 | IPublicClientApplication,
10 | PublicClientApplication,
11 | BrowserCacheLocation,
12 | } from '@azure/msal-browser';
13 | import { MsalModule, MsalService, MSAL_INSTANCE } from '@azure/msal-angular';
14 |
15 | import { AppRoutingModule } from './app-routing.module';
16 | import { AppComponent } from './app.component';
17 | import { NavBarComponent } from './nav-bar/nav-bar.component';
18 | import { HomeComponent } from './home/home.component';
19 | import { AlertsComponent } from './alerts/alerts.component';
20 | import { OAuthSettings } from '../oauth';
21 | import { CalendarComponent } from './calendar/calendar.component';
22 | import { NewEventComponent } from './new-event/new-event.component';
23 |
24 | let msalInstance: IPublicClientApplication | undefined = undefined;
25 |
26 | export function MSALInstanceFactory(): IPublicClientApplication {
27 | msalInstance =
28 | msalInstance ??
29 | new PublicClientApplication({
30 | auth: {
31 | clientId: OAuthSettings.appId,
32 | redirectUri: OAuthSettings.redirectUri,
33 | postLogoutRedirectUri: OAuthSettings.redirectUri,
34 | },
35 | cache: {
36 | cacheLocation: BrowserCacheLocation.LocalStorage,
37 | },
38 | });
39 |
40 | return msalInstance;
41 | }
42 |
43 | @NgModule({
44 | declarations: [
45 | AppComponent,
46 | NavBarComponent,
47 | HomeComponent,
48 | AlertsComponent,
49 | CalendarComponent,
50 | NewEventComponent,
51 | ],
52 | imports: [
53 | BrowserModule,
54 | AppRoutingModule,
55 | NgbModule,
56 | FormsModule,
57 | MsalModule,
58 | ],
59 | providers: [
60 | {
61 | provide: MSAL_INSTANCE,
62 | useFactory: MSALInstanceFactory,
63 | },
64 | MsalService,
65 | ],
66 | bootstrap: [AppComponent],
67 | })
68 | export class AppModule {}
69 |
--------------------------------------------------------------------------------
/graph-app/src/app/graph.service.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Injectable } from '@angular/core';
5 | import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
6 |
7 | import { AuthService } from './auth.service';
8 | import { AlertsService } from './alerts.service';
9 |
10 | @Injectable({
11 | providedIn: 'root',
12 | })
13 | export class GraphService {
14 | constructor(
15 | private authService: AuthService,
16 | private alertsService: AlertsService,
17 | ) {}
18 |
19 | async getCalendarView(
20 | start: string,
21 | end: string,
22 | timeZone: string,
23 | ): Promise {
24 | if (!this.authService.graphClient) {
25 | this.alertsService.addError('Graph client is not initialized.');
26 | return undefined;
27 | }
28 |
29 | try {
30 | // GET /me/calendarview?startDateTime=''&endDateTime=''
31 | // &$select=subject,organizer,start,end
32 | // &$orderby=start/dateTime
33 | // &$top=50
34 | const result = await this.authService.graphClient
35 | .api('/me/calendarview')
36 | .header('Prefer', `outlook.timezone="${timeZone}"`)
37 | .query({
38 | startDateTime: start,
39 | endDateTime: end,
40 | })
41 | .select('subject,organizer,start,end')
42 | .orderby('start/dateTime')
43 | .top(50)
44 | .get();
45 |
46 | return result.value;
47 | } catch (error) {
48 | this.alertsService.addError(
49 | 'Could not get events',
50 | JSON.stringify(error, null, 2),
51 | );
52 | }
53 | return undefined;
54 | }
55 |
56 | async addEventToCalendar(newEvent: MicrosoftGraph.Event): Promise {
57 | if (!this.authService.graphClient) {
58 | this.alertsService.addError('Graph client is not initialized.');
59 | return undefined;
60 | }
61 |
62 | try {
63 | // POST /me/events
64 | await this.authService.graphClient.api('/me/events').post(newEvent);
65 | } catch (error) {
66 | throw Error(JSON.stringify(error, null, 2));
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/graph-app/src/app/new-event/new-event.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 | New event
5 |
56 |
--------------------------------------------------------------------------------
/graph-app/src/app/nav-bar/nav-bar.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
43 |
--------------------------------------------------------------------------------
/graph-app/src/app/calendar/calendar.component.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Component, OnInit } from '@angular/core';
5 | import { parseISO } from 'date-fns';
6 | import { endOfWeek, startOfWeek } from 'date-fns/esm';
7 | import { zonedTimeToUtc } from 'date-fns-tz';
8 | import { findIana } from 'windows-iana';
9 | import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
10 |
11 | import { AuthService } from '../auth.service';
12 | import { GraphService } from '../graph.service';
13 | import { AlertsService } from '../alerts.service';
14 |
15 | @Component({
16 | selector: 'app-calendar',
17 | templateUrl: './calendar.component.html',
18 | styleUrls: ['./calendar.component.css'],
19 | })
20 | export class CalendarComponent implements OnInit {
21 | public events?: MicrosoftGraph.Event[];
22 |
23 | constructor(
24 | private authService: AuthService,
25 | private graphService: GraphService,
26 | private alertsService: AlertsService,
27 | ) {}
28 |
29 | async ngOnInit() {
30 | // Convert the user's timezone to IANA format
31 | const ianaName = findIana(this.authService.user?.timeZone ?? 'UTC');
32 | const timeZone =
33 | ianaName![0].valueOf() || this.authService.user?.timeZone || 'UTC';
34 |
35 | // Get midnight on the start of the current week in the user's timezone,
36 | // but in UTC. For example, for Pacific Standard Time, the time value would be
37 | // 07:00:00Z
38 | const now = new Date();
39 | const weekStart = zonedTimeToUtc(startOfWeek(now), timeZone);
40 | const weekEnd = zonedTimeToUtc(endOfWeek(now), timeZone);
41 |
42 | this.events = await this.graphService.getCalendarView(
43 | weekStart.toISOString(),
44 | weekEnd.toISOString(),
45 | this.authService.user?.timeZone ?? 'UTC',
46 | );
47 | }
48 |
49 | formatDateTimeTimeZone(
50 | dateTime: MicrosoftGraph.DateTimeTimeZone | undefined | null,
51 | ): Date | undefined {
52 | if (dateTime == undefined || dateTime == null) {
53 | return undefined;
54 | }
55 |
56 | try {
57 | return parseISO(dateTime.dateTime!);
58 | } catch (error) {
59 | this.alertsService.addError(
60 | 'DateTimeTimeZone conversion error',
61 | JSON.stringify(error),
62 | );
63 | return undefined;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/graph-app/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /***************************************************************************************************
2 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
3 | */
4 | import '@angular/localize/init';
5 | /**
6 | * This file includes polyfills needed by Angular and is loaded before the app.
7 | * You can add your own extra polyfills to this file.
8 | *
9 | * This file is divided into 2 sections:
10 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
11 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
12 | * file.
13 | *
14 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
15 | * automatically update themselves. This includes recent versions of Safari, Chrome (including
16 | * Opera), Edge on the desktop, and iOS and Chrome on mobile.
17 | *
18 | * Learn more in https://angular.io/guide/browser-support
19 | */
20 |
21 | /***************************************************************************************************
22 | * BROWSER POLYFILLS
23 | */
24 |
25 | /**
26 | * By default, zone.js will patch all possible macroTask and DomEvents
27 | * user can disable parts of macroTask/DomEvents patch by setting following flags
28 | * because those flags need to be set before `zone.js` being loaded, and webpack
29 | * will put import in the top of bundle, so user need to create a separate file
30 | * in this directory (for example: zone-flags.ts), and put the following flags
31 | * into that file, and then add the following code before importing zone.js.
32 | * import './zone-flags';
33 | *
34 | * The flags allowed in zone-flags.ts are listed here.
35 | *
36 | * The following flags will work for all browsers.
37 | *
38 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
39 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
40 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
41 | *
42 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
43 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
44 | *
45 | * (window as any).__Zone_enable_cross_context_check = true;
46 | *
47 | */
48 |
49 | /***************************************************************************************************
50 | * Zone JS is required by default for Angular itself.
51 | */
52 | import 'zone.js'; // Included with Angular CLI.
53 |
54 | /***************************************************************************************************
55 | * APPLICATION IMPORTS
56 | */
57 |
--------------------------------------------------------------------------------
/graph-app/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "graph-app": {
7 | "projectType": "application",
8 | "schematics": {},
9 | "root": "",
10 | "sourceRoot": "src",
11 | "prefix": "app",
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/graph-app",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "assets": [
22 | "src/favicon.ico",
23 | "src/assets"
24 | ],
25 | "styles": [
26 | "node_modules/bootstrap/dist/css/bootstrap.min.css",
27 | "src/styles.css"
28 | ],
29 | "scripts": [],
30 | "allowedCommonJsDependencies": [
31 | "@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser",
32 | "date-fns-tz",
33 | "date-fns"
34 | ]
35 | },
36 | "configurations": {
37 | "production": {
38 | "budgets": [
39 | {
40 | "type": "initial",
41 | "maximumWarning": "500kb",
42 | "maximumError": "2mb"
43 | },
44 | {
45 | "type": "anyComponentStyle",
46 | "maximumWarning": "2kb",
47 | "maximumError": "4kb"
48 | }
49 | ],
50 | "fileReplacements": [
51 | {
52 | "replace": "src/environments/environment.ts",
53 | "with": "src/environments/environment.prod.ts"
54 | }
55 | ],
56 | "outputHashing": "all"
57 | },
58 | "development": {
59 | "buildOptimizer": false,
60 | "optimization": false,
61 | "vendorChunk": true,
62 | "extractLicenses": false,
63 | "sourceMap": true,
64 | "namedChunks": true
65 | }
66 | },
67 | "defaultConfiguration": "production"
68 | },
69 | "serve": {
70 | "builder": "@angular-devkit/build-angular:dev-server",
71 | "configurations": {
72 | "production": {
73 | "browserTarget": "graph-app:build:production"
74 | },
75 | "development": {
76 | "browserTarget": "graph-app:build:development"
77 | }
78 | },
79 | "defaultConfiguration": "development"
80 | },
81 | "extract-i18n": {
82 | "builder": "@angular-devkit/build-angular:extract-i18n",
83 | "options": {
84 | "browserTarget": "graph-app:build"
85 | }
86 | },
87 | "test": {
88 | "builder": "@angular-devkit/build-angular:karma",
89 | "options": {
90 | "main": "src/test.ts",
91 | "polyfills": "src/polyfills.ts",
92 | "tsConfig": "tsconfig.spec.json",
93 | "karmaConfig": "karma.conf.js",
94 | "assets": [
95 | "src/favicon.ico",
96 | "src/assets"
97 | ],
98 | "styles": [
99 | "src/styles.css"
100 | ],
101 | "scripts": []
102 | }
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/graph-app/src/app/auth.service.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import { Injectable } from '@angular/core';
5 | import { MsalService } from '@azure/msal-angular';
6 | import {
7 | AuthenticationResult,
8 | InteractionType,
9 | PublicClientApplication,
10 | } from '@azure/msal-browser';
11 | import { Client } from '@microsoft/microsoft-graph-client';
12 | import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser';
13 | import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
14 |
15 | import { AlertsService } from './alerts.service';
16 | import { OAuthSettings } from '../oauth';
17 | import { User } from './user';
18 | import { lastValueFrom, Observable } from 'rxjs';
19 |
20 | @Injectable({
21 | providedIn: 'root',
22 | })
23 | export class AuthService {
24 | public authenticated: boolean;
25 | public user?: User;
26 | public graphClient?: Client;
27 |
28 | constructor(
29 | private msalService: MsalService,
30 | private alertsService: AlertsService,
31 | ) {
32 | const accounts = this.msalService.instance.getAllAccounts();
33 | this.authenticated = accounts.length > 0;
34 | if (this.authenticated) {
35 | this.msalService.instance.setActiveAccount(accounts[0]);
36 | }
37 | this.getUser().then((user) => {
38 | this.user = user;
39 | });
40 | }
41 |
42 | // Prompt the user to sign in and
43 | // grant consent to the requested permission scopes
44 | async signIn(): Promise {
45 | try {
46 | const result = await lastValueFrom(
47 | this.msalService.loginPopup(OAuthSettings),
48 | );
49 |
50 | if (result) {
51 | this.msalService.instance.setActiveAccount(result.account);
52 | this.authenticated = true;
53 | this.user = await this.getUser();
54 | }
55 | } catch (reason: any) {
56 | this.alertsService.addError(
57 | 'Login failed',
58 | JSON.stringify(reason, null, 2),
59 | );
60 | }
61 | }
62 |
63 | // Sign out
64 | async signOut(): Promise {
65 | await this.msalService.logout();
66 | this.user = undefined;
67 | this.graphClient = undefined;
68 | this.authenticated = false;
69 | }
70 |
71 | handleRedirects(): Observable {
72 | return this.msalService.handleRedirectObservable();
73 | }
74 |
75 | private async getUser(): Promise {
76 | if (!this.authenticated) return undefined;
77 |
78 | // Create an authentication provider for the current user
79 | const authProvider = new AuthCodeMSALBrowserAuthenticationProvider(
80 | this.msalService.instance as PublicClientApplication,
81 | {
82 | account: this.msalService.instance.getActiveAccount()!,
83 | scopes: OAuthSettings.scopes,
84 | interactionType: InteractionType.Popup,
85 | },
86 | );
87 |
88 | // Initialize the Graph client
89 | this.graphClient = Client.initWithMiddleware({
90 | authProvider: authProvider,
91 | });
92 |
93 | // Get the user from Graph (GET /me)
94 | const graphUser: MicrosoftGraph.User = await this.graphClient
95 | .api('/me')
96 | .select('displayName,mail,mailboxSettings,userPrincipalName')
97 | .get();
98 |
99 | const user = new User();
100 | user.displayName = graphUser.displayName ?? '';
101 | // Prefer the mail property, but fall back to userPrincipalName
102 | user.email = graphUser.mail ?? graphUser.userPrincipalName ?? '';
103 | user.timeZone = graphUser.mailboxSettings?.timeZone ?? 'UTC';
104 |
105 | // Use default avatar
106 | user.avatar = '/assets/no-profile-photo.png';
107 |
108 | return user;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | description: This sample demonstrates how to use the Microsoft Graph JavaScript SDK to access data in Office 365 from Angular single-page applications.
4 | products:
5 | - ms-graph
6 | - office-exchange-online
7 | languages:
8 | - typescript
9 | ---
10 |
11 | # Microsoft Graph sample Angular app
12 |
13 | [](https://github.com/microsoftgraph/msgraph-sample-angularspa/actions/workflows/node.js.yml) 
14 |
15 | This sample demonstrates how to use the Microsoft Graph JavaScript SDK to access data in Office 365 from Angular single-page applications.
16 |
17 | > **NOTE:** This sample was originally built from a tutorial published on the [Microsoft Graph tutorials](https://docs.microsoft.com/graph/tutorials) page. That tutorial has been removed.
18 |
19 | ## Prerequisites
20 |
21 | To run the completed project in this folder, you need the following:
22 |
23 | - [Node.js](https://nodejs.org) installed on your development machine. If you do not have Node.js, visit the previous link for download options. (**Note:** This tutorial was written with Node version 16.4.2. The steps in this guide may work with other versions, but that has not been tested.)
24 | - [Angular CLI](https://cli.angular.io/) installed on your development machine.
25 | - Either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account.
26 |
27 | If you don't have a Microsoft account, there are a couple of options to get a free account:
28 |
29 | - You can [sign up for a new personal Microsoft account](https://signup.live.com/signup?wa=wsignin1.0&rpsnv=12&ct=1454618383&rver=6.4.6456.0&wp=MBI_SSL_SHARED&wreply=https://mail.live.com/default.aspx&id=64855&cbcxt=mai&bk=1454618383&uiflavor=web&uaid=b213a65b4fdc484382b6622b3ecaa547&mkt=E-US&lc=1033&lic=1).
30 | - You can [sign up for the Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program) to get a free Office 365 subscription.
31 |
32 | ## Register a web application with the Azure Active Directory admin center
33 |
34 | 1. Open a browser and navigate to the [Azure Active Directory admin center](https://aad.portal.azure.com). Login using a **personal account** (aka: Microsoft Account) or **Work or School Account**.
35 |
36 | 1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**
37 |
38 | 1. Select **New registration**. On the **Register an application** page, set the values as follows.
39 |
40 | - Set **Name** to `Angular Graph Sample`.
41 | - Set **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts**.
42 | - Under **Redirect URI**, set the first drop-down to `Single-page application (SPA)` and set the value to `http://localhost:4200`.
43 |
44 | 1. Choose **Register**. On the **Angular Graph Tutorial** page, copy the value of the **Application (client) ID** and save it, you will need it in the next step.
45 |
46 | ## Configure the sample
47 |
48 | 1. Rename the `oauth.example.ts` file to `oauth.ts`.
49 | 1. Edit the `oauth.ts` file and make the following changes.
50 | 1. Replace `YOUR_APP_ID_HERE` with the **Application Id** you got from the App Registration Portal.
51 | 1. In your command-line interface (CLI), navigate to this directory and run the following command to install requirements.
52 |
53 | ```Shell
54 | npm install
55 | ```
56 |
57 | ## Run the sample
58 |
59 | 1. Run the following command in your CLI to start the application.
60 |
61 | ```Shell
62 | ng serve
63 | ```
64 |
65 | 1. Open a browser and browse to `http://localhost:4200`.
66 |
67 | ## Code of conduct
68 |
69 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
70 |
71 | ## Disclaimer
72 |
73 | **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
74 |
--------------------------------------------------------------------------------
/.github/policies/resourceManagement.yml:
--------------------------------------------------------------------------------
1 | id:
2 | name: GitOps.PullRequestIssueManagement
3 | description: GitOps.PullRequestIssueManagement primitive
4 | owner:
5 | resource: repository
6 | disabled: false
7 | where:
8 | configuration:
9 | resourceManagementConfiguration:
10 | scheduledSearches:
11 | - description:
12 | frequencies:
13 | - hourly:
14 | hour: 6
15 | filters:
16 | - isIssue
17 | - isOpen
18 | - hasLabel:
19 | label: needs author feedback
20 | - hasLabel:
21 | label: no recent activity
22 | - noActivitySince:
23 | days: 3
24 | actions:
25 | - closeIssue
26 | - description:
27 | frequencies:
28 | - hourly:
29 | hour: 6
30 | filters:
31 | - isIssue
32 | - isOpen
33 | - hasLabel:
34 | label: needs author feedback
35 | - noActivitySince:
36 | days: 4
37 | - isNotLabeledWith:
38 | label: no recent activity
39 | actions:
40 | - addLabel:
41 | label: no recent activity
42 | - addReply:
43 | reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**.
44 | - description:
45 | frequencies:
46 | - hourly:
47 | hour: 6
48 | filters:
49 | - isIssue
50 | - isOpen
51 | - hasLabel:
52 | label: duplicate
53 | - noActivitySince:
54 | days: 1
55 | actions:
56 | - addReply:
57 | reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes.
58 | - closeIssue
59 | - description:
60 | frequencies:
61 | - hourly:
62 | hour: 3
63 | filters:
64 | - isOpen
65 | - isIssue
66 | - hasLabel:
67 | label: graph question
68 | actions:
69 | - removeLabel:
70 | label: 'needs attention :wave:'
71 | - removeLabel:
72 | label: needs author feedback
73 | - removeLabel:
74 | label: 'needs triage :mag:'
75 | - removeLabel:
76 | label: no recent activity
77 | - addLabel:
78 | label: out of scope
79 | - addReply:
80 | reply: >-
81 | It looks like you are asking a question about using Microsoft Graph or one of the Microsoft Graph SDKs that is not directly related to this sample. Unfortunately we are not set up to answer general questions in this repository, so this issue will be closed.
82 |
83 |
84 | Please try asking your question on [Stack Overflow](https://stackoverflow.com/questions/tagged/microsoft-graph-api), tagging your question with `microsoft-graph-api`.
85 | - closeIssue
86 | - description:
87 | frequencies:
88 | - hourly:
89 | hour: 3
90 | filters:
91 | - isOpen
92 | - isIssue
93 | - hasLabel:
94 | label: graph issue
95 | actions:
96 | - removeLabel:
97 | label: 'needs attention :wave:'
98 | - removeLabel:
99 | label: needs author feedback
100 | - removeLabel:
101 | label: 'needs triage :mag:'
102 | - removeLabel:
103 | label: no recent activity
104 | - addLabel:
105 | label: out of scope
106 | - addReply:
107 | reply: >-
108 | It looks like you are reporting an issue with Microsoft Graph or one of the Microsoft Graph SDKs that is not fixable by changing code in this sample. Unfortunately we are not set up to provide product support in this repository, so this issue will be closed.
109 |
110 |
111 | Please visit one of the following links to report your issue.
112 |
113 |
114 | - Issue with Microsoft Graph service: [Microsoft Graph support](https://developer.microsoft.com/graph/support#report-issues-with-the-service), choose one of the options under **Report issues with the service**
115 |
116 | - Issue with a Microsoft Graph SDK: Open an issue in the SDK's GitHub repository. See [microsoftgraph on GitHub](https://github.com/microsoftgraph?q=sdk+in%3Aname&type=public&language=) for a list of SDK repositories.
117 | - closeIssue
118 | eventResponderTasks:
119 | - if:
120 | - payloadType: Issue_Comment
121 | - isAction:
122 | action: Created
123 | - isActivitySender:
124 | issueAuthor: True
125 | - hasLabel:
126 | label: needs author feedback
127 | - isOpen
128 | then:
129 | - addLabel:
130 | label: 'needs attention :wave:'
131 | - removeLabel:
132 | label: needs author feedback
133 | description:
134 | - if:
135 | - payloadType: Issues
136 | - not:
137 | isAction:
138 | action: Closed
139 | - hasLabel:
140 | label: no recent activity
141 | then:
142 | - removeLabel:
143 | label: no recent activity
144 | description:
145 | - if:
146 | - payloadType: Issue_Comment
147 | - hasLabel:
148 | label: no recent activity
149 | then:
150 | - removeLabel:
151 | label: no recent activity
152 | description:
153 | - if:
154 | - payloadType: Pull_Request
155 | then:
156 | - inPrLabel:
157 | label: in pr
158 | description:
159 | onFailure:
160 | onSuccess:
161 |
--------------------------------------------------------------------------------