diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.scss index 12125248f8d..f5c7f6113c2 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.scss @@ -9,16 +9,6 @@ } }; - .author-table-data { - vertical-align: middle; - font-size: 0.9rem; - font-weight: 400; - img { - border-radius: 50%; - margin-right: 5px; - } - } - .index-table-info { min-height: 30px; max-height: 30px; diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.ts index 0ec68bf5451..a3830e0cdaf 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/index-page/index-page.component.ts @@ -7,7 +7,7 @@ import { FirstReleaseApproval, Review } from 'src/app/_models/review'; styleUrls: ['./index-page.component.scss'] }) export class IndexPageComponent { - review : Review | null = null; + review : Review | undefined = undefined; /** * Pass ReviewId to revision component to load revisions diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html index 594ef066aac..a6cf7aae2fb 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.html @@ -6,13 +6,12 @@ [diffApiRevisionId]="diffApiRevisionId" [userProfile]="userProfile" [review]="review" - (revisionsSidePanel)="showRevisionsPanel($event)" (pageOptionsEmitter)="handlePageOptionsEmitter($event)">
- -
+
+
- - Revisions + + \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss index 84625bf0af2..88abf90e894 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.scss @@ -7,10 +7,6 @@ } } - .p-sidebar-right { - width: 70dvw; - } - .p-splitter-panel-nested { display: block; min-width: 0; @@ -39,5 +35,12 @@ .p-menu .p-menuitem > .p-menuitem-content .p-menuitem-link .p-menuitem-icon { color: var(--base-text-color);; font-size: x-large; + &:hover, &:active { + color: var(--primary-color); + } + } + + .revisions-sidebar { + width: 75dvw; } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts index 3a67ec5a8c5..08d778e42c3 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts @@ -103,12 +103,7 @@ export class ReviewPageComponent implements OnInit { this.sideMenu = [ { icon: 'bi bi-clock-history', - }, - { - icon: 'bi bi-file-diff' - }, - { - icon: 'bi bi-chat-left-dots' + command: () => { this.revisionSidePanel = !this.revisionSidePanel; } } ]; } @@ -219,10 +214,6 @@ export class ReviewPageComponent implements OnInit { }); } - showRevisionsPanel(showRevisionsPanel : any){ - this.revisionSidePanel = showRevisionsPanel as boolean; - } - handlePageOptionsEmitter(showPageOptions: boolean) { this.userProfile!.preferences.hideReviewPageOptions = !showPageOptions; this.userProfileService.updateUserPrefernece(this.userProfile!.preferences).pipe(takeUntil(this.destroy$)).subscribe({ diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.html index 47929d3b537..da5dfb32e84 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.html @@ -74,106 +74,20 @@ - - -

Create Review

-

- Select a Language Below to view instructions for creating a Review. -

-
- - -
- - {{ createReviewForm.get('selectedCRLanguage')!.value!.label }} -
-
- -
- - {{ crLanguage.label }} -
-
-
-
-
-
What to Upload
-
    -
  1. -
-
- TypeSpec review can be generated manually using one of the following options. -
    -
  1. - Option 1: Create API review using a link to TypeSpec project. Provide the URL to project and click Upload. -
  2. -
  3. - Option 2: Create API review file locally using following steps. -
      -
    • - Install @azure-tools/typespec-apiview -
    • -
    • - Compile and emit the apiview file: typespec compile .\main.tsp --emit "@azure-tools/typespec-apiview" -
    • -
    • - Select Json as the language option in Create review page. -
    • -
    • - Upload the JSON file that is emitted. -
    • -
    -
  4. -
-
-
-
-
- - -
or drag and drop files here
-
-
-
-
- -
-
- -
-
- -
-
-
- +

Filter Reviews

  • First Release Aprroval
    -
    -
    - - - - - - - - -
    -
    +
diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.scss index 794fba9d2e8..6bca1e930c8 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.scss @@ -1,9 +1,24 @@ :host ::ng-deep { - .p-fileupload-row > div:first-child { - display: none; + .first-release-approval-filter-button .p-selectbutton { + display: flex; + justify-content: space-between; + } + + .first-release-approval-filter-button .p-selectbutton .p-button { + flex-grow: 1; + text-align: center; + background-color: transparent; + border: 1px solid var(--link-color); + color: var(--link-color); + font-size: 0.85rem; } - .p-dropdown { - display: flex; + .first-release-approval-filter-button .p-selectbutton .p-button.p-highlight { + color: var(--primary-btn-color); + background-color: var(--link-color); + } + + .filters-sidebar { + width: 20dvw; } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.ts index 2838a8a3481..dd35ff7370c 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/reviews-list/reviews-list.component.ts @@ -1,13 +1,10 @@ import { Component, ElementRef, EventEmitter, OnInit, AfterViewInit, Output, ViewChild, ChangeDetectorRef } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FirstReleaseApproval, Review, SelectItemModel } from 'src/app/_models/review'; import { ReviewsService } from 'src/app/_services/reviews/reviews.service'; import { Pagination } from 'src/app/_models/pagination'; import { Table, TableFilterEvent, TableLazyLoadEvent, TableRowSelectEvent } from 'primeng/table'; import { MenuItem, SortEvent } from 'primeng/api'; -import { FileSelectEvent, FileUpload } from 'primeng/fileupload'; -import { RevisionsService } from 'src/app/_services/revisions/revisions.service'; import { environment } from 'src/environments/environment'; import { CookieService } from 'ngx-cookie-service'; @@ -19,7 +16,6 @@ import { CookieService } from 'ngx-cookie-service'; export class ReviewsListComponent implements OnInit, AfterViewInit { @Output() reviewEmitter : EventEmitter = new EventEmitter(); @ViewChild("reviewsTable") reviewsTable!: Table; - @ViewChild("reviewCreationFileUpload") reviewCreationFileUpload!: FileUpload; @ViewChild("firstReleaseApprovalAllCheck") firstReleaseApprovalAllCheck!: ElementRef; assetsPath : string = environment.assetsPath; @@ -35,7 +31,6 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { sortOrder : number = 1; filters: any = null; - createReviewSidebarVisible : boolean = false; filterSideBarVisible : boolean = false; // Filter Options @@ -52,20 +47,15 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { // Messages reviewListDetail: string = ""; - // Create Review Selections - crLanguages: any[] = []; - createReviewForm! : FormGroup; - creatingReview : boolean = false; - crButtonText : string = "Create Review"; - badgeClass : Map = new Map(); - // Review Upload Instructions - createReviewInstruction : string[] | undefined; - acceptedFilesForReviewUpload : string | undefined; + firstReleaseApprovalOptions: any[] = [ + { label: 'Pending', value: FirstReleaseApproval.Pending }, + { label: 'All', value: FirstReleaseApproval.All }, + { label: 'Approved', value: FirstReleaseApproval.Approved } + ]; - constructor(private reviewsService: ReviewsService, private apiRevisionsService: RevisionsService, - private fb: FormBuilder, private cookieService: CookieService, private cd: ChangeDetectorRef) { } + constructor(private reviewsService: ReviewsService, private cookieService: CookieService, private cd: ChangeDetectorRef) { } ngOnInit(): void { if (this.cookieService.check("reviewFilters")) { @@ -74,9 +64,8 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { } this.loadReviews(0, this.pageSize * 2, true, this.filters); // Initial load of 2 pages - this.createFilters(); + this.createLanguageFilters(); this.createContextMenuItems(); - this.createReviewFormGroup(); } ngAfterViewInit() { @@ -125,10 +114,10 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { }); } - updateFirstReleaseApproval(value : string) { - this.firstReleaseApproval = FirstReleaseApproval[value as keyof typeof FirstReleaseApproval]; + updateFirstReleaseApproval(event : any) { + this.firstReleaseApproval = event.value; this.loadReviews(0, this.pageSize * 2, true); - this.reviewListDetail = (value != "All") ? value: ""; + this.reviewListDetail = (event.value != FirstReleaseApproval.All) ? event.value : ""; } createContextMenuItems() { @@ -137,8 +126,8 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { ]; } - createFilters() { - this.languages = this.crLanguages = [ + createLanguageFilters() { + this.languages = [ { label: "C", data: "C" }, { label: "C#", data: "C#" }, { label: "C++", data: "C++" }, @@ -155,17 +144,6 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { ]; } - createReviewFormGroup() { - this.createReviewForm = this.fb.group({ - selectedCRLanguage: [null, Validators.required], - selectedFile: [null, Validators.required], - filePath: [null, Validators.required], - label: [null, Validators.required] - }); - this.createReviewForm.get('selectedFile')?.disable(); - this.createReviewForm.get('filePath')?.disable(); - } - viewReview(review: Review) { this.reviewsService.openReviewPage(review.id); } @@ -184,7 +162,6 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { */ clear(table: Table) { table.clear(); - this.firstReleaseApprovalAllCheck.nativeElement.checked = true; this.firstReleaseApproval = FirstReleaseApproval.All; this.loadReviews(0, this.pageSize, true, this.filters, this.sortField, this.sortOrder); } @@ -234,167 +211,4 @@ export class ReviewsListComponent implements OnInit, AfterViewInit { this.sortOrder = event.order as number ?? 1; this.loadReviews(0, this.pageSize, true, this.filters, this.sortField, this.sortOrder); } - - /** - * Callback to invoke on file selction for review creation - * @param event the Filter event - */ - onFileSelect(event: FileSelectEvent) { - const uploadFile = event.currentFiles[0]; - this.createReviewForm.get('selectedFile')?.setValue(uploadFile); - } - - // Show or hide the sidebar for creating a review - onHideSideBar() { - this.createReviewForm.reset(); - this.createReviewInstruction = []; - } - - // Fire API request to create the review - createReview() { - if (this.createReviewForm.valid) { - - const formData: FormData = new FormData(); - formData.append("label", this.createReviewForm.get('label')?.value!); - formData.append("language", this.createReviewForm.get('selectedCRLanguage')?.value?.data!); - - if (this.createReviewForm.get('filePath')?.value) { - formData.append("filePath", this.createReviewForm.get('filePath')?.value!); - } - - if (this.createReviewForm.get('selectedFile')?.value) { - const file = this.createReviewForm.get('selectedFile')?.value as File; - formData.append("file", file, file.name); - } - - this.creatingReview = true; - this.crButtonText = "Creating Review "; - - this.reviewsService.createReview(formData).subscribe({ - next: (response: any) => { - if (response) { - this.createReviewSidebarVisible = false; - this.creatingReview = false; - this.crButtonText = "Create Review"; - this.apiRevisionsService.openAPIRevisionPage(response.reviewId, response.id); - } - } - }); - } - } - - onCRLanguageSelectChange() { - switch(this.createReviewForm.get('selectedCRLanguage')?.value?.data){ - case "C": - this.createReviewInstruction = [ - `Install clang 10 or later.`, - `Run clang [inputs like az_*.h] -Xclang -ast-dump=json -I ..\\..\\..\\core\\core\\inc -I "c:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview\\VC\\Tools\\MSVC\\14.26.28801\\include\\" > az_core.ast`, - `Archive the file Compress-Archive az_core.ast -DestinationPath az_core.zip`, - `Upload the resulting archive.` - ]; - this.acceptedFilesForReviewUpload = ".zip"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "C#": - this.createReviewInstruction = [ - `Run dotnet pack`, - `Upload the resulting .nupkg file.` - ]; - this.acceptedFilesForReviewUpload = ".nupkg"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "C++": - this.createReviewInstruction = [ - `Generate a token file using the C++ parser`, - `Upload the token file generated.` - ]; - this.acceptedFilesForReviewUpload = ".json"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "Java": - this.createReviewInstruction = [ - `Run a mvn package build on your project, which will generate a number of build artifacts in the /target directory. In there, find the file ending sources.jar, and select it.`, - ]; - this.acceptedFilesForReviewUpload = ".sources.jar"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "Python": - this.createReviewInstruction = [ - `Generate wheel for the package. python setup.py bdist_wheel -d [dest_folder]`, - `Upload generated whl file` - ]; - this.acceptedFilesForReviewUpload = ".whl"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "JavaScript": - this.createReviewInstruction = [ - `Use api-extractor to generate a docModel file`, - `Upload generated api.json file` - ]; - this.acceptedFilesForReviewUpload = ".api.json"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "Go": - this.createReviewInstruction = [ - `Archive source module directory in which go.mod is present. Compress-Archive ./sdk/azcore -DestinationPath azcore.zip`, - `Rename the file Rename-Item azcore.zip -NewName azcore.gosource`, - `Upload the resulting archive.` - ]; - this.acceptedFilesForReviewUpload = ".gosource"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "Swift": - this.createReviewInstruction = [ - `Generate JSON file for the source by running Swift APIView parser in XCode. More information is available here on Swift API parser`, - `Upload generated JSON` - ]; - this.acceptedFilesForReviewUpload = ".json"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "Swagger": - this.createReviewInstruction = [ - `Rename swagger json to replace file extension to .swagger Rename-Item PetSwagger.json -NewName PetSwagger.swagger`, - `Upload renamed swagger file` - ]; - this.acceptedFilesForReviewUpload = ".swagger"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - case "TypeSpec": - this.createReviewInstruction = [ - `Rename swagger json to replace file extension to .swagger Rename-Item PetSwagger.json -NewName PetSwagger.swagger`, - `Upload renamed swagger file` - ]; - this.acceptedFilesForReviewUpload = ".json"; - this.createReviewForm.get('selectedFile')?.disable(); - this.createReviewForm.get('filePath')?.enable(); - break; - case "Json": - this.createReviewInstruction = [ - `Upload JSON API review token file.` - ]; - this.acceptedFilesForReviewUpload = ".json, .tgz"; - this.createReviewForm.get('selectedFile')?.enable(); - this.createReviewForm.get('filePath')?.disable(); - break; - default: - this.createReviewInstruction = [] - } - - if (this.reviewCreationFileUpload) { - this.reviewCreationFileUpload.clear(); - } - - this.createReviewForm.get('label')?.reset(); - this.createReviewForm.get('selectedFile')?.reset(); - this.createReviewForm.get('filePath')?.reset() - } } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.html b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.html index 5dcf90bc6a9..37205508977 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.html +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.html @@ -4,7 +4,7 @@
Select a Review on the left panel to view its APIRevisions
- +
@@ -14,7 +14,7 @@
There are no {{apiRevisionsListDetail}} {{review.packageName}}
- +
@@ -44,7 +44,7 @@
There are no {{apiRevisionsListDetail}} {{review.packageName}}
- +
@@ -52,7 +52,7 @@
There are no {{apiRevisionsListDetail}} {{review.packageName}}
- +
@@ -99,7 +99,7 @@
There are no {{apiRevisionsListDetail}} {{review.packageName}}
- {{ revision.resolvedLabel }} + {{ revision.resolvedLabel }} {{ revision.resolvedLabel }} @@ -125,7 +125,86 @@
There are no {{apiRevisionsListDetail}} {{review.packageName}}
- + +

Create Revision

+

Select a Language Below to view instructions for creating a Review.

+
+ + +
+ + {{ createRevisionForm.get('selectedCRLanguage')!.value!.label }} +
+
+ +
+ + {{ crLanguage.label }} +
+
+
+
+
+
What to Upload
+
    +
  1. +
+
+ TypeSpec review can be generated manually using one of the following options. +
    +
  1. + Option 1: Create API review using a link to TypeSpec project. Provide the URL to project and click Upload. +
  2. +
  3. + Option 2: Create API review file locally using following steps. +
      +
    • + Install @azure-tools/typespec-apiview +
    • +
    • + Compile and emit the apiview file: typespec compile .\main.tsp --emit "@azure-tools/typespec-apiview" +
    • +
    • + Select Json as the language option in Create review page. +
    • +
    • + Upload the JSON file that is emitted. +
    • +
    +
  4. +
+
+
+
+
+ + +
or drag and drop files here
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+

Filter APIRevisions

  • diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.scss index c3fab2b656a..8a6f1c7578a 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.scss +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.scss @@ -1,5 +1,27 @@ :host ::ng-deep { - .p-sidebar-right { + .p-fileupload-row > div:first-child { + display: none; + } + + .p-dropdown { + display: flex; + } + + .author-table-data { + vertical-align: middle; + font-size: 0.9rem; + font-weight: 400; + img { + border-radius: 50%; + margin-right: 5px; + } + } + + .create-revision-sidebar { + width: 30dvw; + } + + .options-sidebar { width: 20dvw; } } \ No newline at end of file diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts index 52fe7eb6334..d6883de3ec3 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/revisions-list/revisions-list.component.ts @@ -1,13 +1,18 @@ -import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; import { MenuItem, SortEvent } from 'primeng/api'; +import { FileSelectEvent, FileUpload } from 'primeng/fileupload'; import { Table, TableFilterEvent, TableLazyLoadEvent } from 'primeng/table'; import { UserProfile } from 'src/app/_models/auth_service_models'; import { Pagination } from 'src/app/_models/pagination'; -import { FirstReleaseApproval, Review } from 'src/app/_models/review'; +import { Review } from 'src/app/_models/review'; import { APIRevision } from 'src/app/_models/revision'; import { ConfigService } from 'src/app/_services/config/config.service'; +import { ReviewsService } from 'src/app/_services/reviews/reviews.service'; import { RevisionsService } from 'src/app/_services/revisions/revisions.service'; import { UserProfileService } from 'src/app/_services/user-profile/user-profile.service'; +import { environment } from 'src/environments/environment'; @Component({ selector: 'app-revisions-list', @@ -15,8 +20,11 @@ import { UserProfileService } from 'src/app/_services/user-profile/user-profile. styleUrls: ['./revisions-list.component.scss'] }) export class RevisionsListComponent implements OnInit, OnChanges { - @Input() review : Review | null = null; + @Input() review : Review | undefined = undefined; + @ViewChild("revisionCreationFileUpload") revisionCreationFileUpload!: FileUpload; + + assetsPath : string = environment.assetsPath; userProfile : UserProfile | undefined; reviewPageWebAppUrl : string = this.configService.webAppUrl + "Assemblies/Review/"; profilePageWebAppUrl : string = this.configService.webAppUrl + "Assemblies/Profile/"; @@ -31,7 +39,18 @@ export class RevisionsListComponent implements OnInit, OnChanges { sortOrder : number = 1; filters: any = null; - sidebarVisible : boolean = false; + createRevisionSidebarVisible : boolean = false; + optionsSidebarVisible : boolean = false; + + // Create Revision Selections + createRevisionForm! : FormGroup; + crLanguages: any[] = []; + creatingRevision : boolean = false; + crButtonText : string = "Create Review"; + + // Review Upload Instructions + createRevisionInstruction : string[] | undefined; + acceptedFilesForReviewUpload : string | undefined; // Filters details: any[] = []; @@ -52,10 +71,13 @@ export class RevisionsListComponent implements OnInit, OnChanges { badgeClass : Map = new Map(); - constructor(private apiRevisionsService: RevisionsService, private userProfileService: UserProfileService, private configService: ConfigService) { } + constructor(private apiRevisionsService: RevisionsService, private userProfileService: UserProfileService, + private configService: ConfigService, private fb: FormBuilder, private reviewsService: ReviewsService, + private router: Router) { } ngOnInit(): void { - this.createFilters(); + this.createRevisionFilters(); + this.createLanguageFilters(); this.createContextMenuItems(); this.setDetailsIcons(); this.loadAPIRevisions(0, this.pageSize * 2, true); @@ -63,6 +85,13 @@ export class RevisionsListComponent implements OnInit, OnChanges { (userProfile : any) => { this.userProfile = userProfile; }); + this.createRevisionFormGroup(); + } + + ngAfterOnit() { + if (this.review) { + this.loadAPIRevisions(0, this.pageSize * 2, true); + } } ngOnChanges(changes: SimpleChanges) { @@ -73,6 +102,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { } else { this.loadAPIRevisions(0, this.pageSize * 2, true); + this.createRevisionFormGroup(); } this.showSelectionActions = false; this.showDiffButton = false; @@ -115,11 +145,24 @@ export class RevisionsListComponent implements OnInit, OnChanges { this.pagination = response.pagination; this.totalNumberOfRevisions = this.pagination?.totalCount!; } + + this.setCreateRevisionLanguageBasedOnReview(); } } }); } + createRevisionFormGroup() { + this.createRevisionForm = this.fb.group({ + selectedCRLanguage: [null, Validators.required], + selectedFile: [null, Validators.required], + filePath: [null, Validators.required], + label: [null, Validators.required] + }); + this.createRevisionForm.get('selectedFile')?.disable(); + this.createRevisionForm.get('filePath')?.disable(); + } + createContextMenuItems() { if (this.showDeletedAPIRevisions) { @@ -136,7 +179,25 @@ export class RevisionsListComponent implements OnInit, OnChanges { } } - createFilters() { + createLanguageFilters() { + this.crLanguages = [ + { label: "C", data: "C" }, + { label: "C#", data: "C#" }, + { label: "C++", data: "C++" }, + { label: "Go", data: "Go" }, + { label: "Java", data: "Java" }, + { label: "JavaScript", data: "JavaScript" }, + { label: "Json", data: "Json" }, + { label: "Kotlin", data: "Kotlin" }, + { label: "Python", data: "Python" }, + { label: "Swagger", data: "Swagger" }, + { label: "Swift", data: "Swift" }, + { label: "TypeSpec", data: "TypeSpec" }, + { label: "Xml", data: "Xml" } + ]; + } + + createRevisionFilters() { this.details = [ { label: 'Status', @@ -170,14 +231,14 @@ export class RevisionsListComponent implements OnInit, OnChanges { viewDiffOfSelectedAPIRevisions() { if (this.selectedRevisions.length == 2) { - this.apiRevisionsService.openDiffOfAPIRevisions(this.review!.id, this.selectedRevisions[0].id, this.selectedRevisions[1].id) + this.apiRevisionsService.openDiffOfAPIRevisions(this.review!.id, this.selectedRevisions[0].id, this.selectedRevisions[1].id, this.router.url); } } viewRevision(apiRevision: APIRevision) { if (!this.showDeletedAPIRevisions) { - this.apiRevisionsService.openAPIRevisionPage(this.review!.id, apiRevision.id); + this.apiRevisionsService.openAPIRevisionPage(apiRevision.reviewId, apiRevision.id, this.router.url); } } @@ -268,7 +329,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { this.showAPIRevisionsAssignedToMe = !this.showAPIRevisionsAssignedToMe; this.showDeletedAPIRevisions = false; if (this.showAPIRevisionsAssignedToMe) { - this.review = null; + this.review = undefined; } this.loadAPIRevisions(0, this.pageSize * 2, true); } @@ -287,6 +348,15 @@ export class RevisionsListComponent implements OnInit, OnChanges { this.apiRevisionsListDetail = msg; } + setCreateRevisionLanguageBasedOnReview() { + if (this.review) { + this.createRevisionForm.patchValue({ + selectedCRLanguage: this.crLanguages.filter((item) => item.label == this.review?.language)[0], + }); + this.onCRLanguageSelectChange(); + } + } + // Getters and Setters get showDeletedAPIRevisions(): boolean { return this._showDeletedAPIRevisions; @@ -322,7 +392,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { } } event.forceUpdate!(); - } + } /** * Callback to invoke on table filter. @@ -339,7 +409,7 @@ export class RevisionsListComponent implements OnInit, OnChanges { onSelectionChange(value : APIRevision[] = []) { this.selectedRevisions = value; this.showSelectionActions = (value.length > 0) ? true : false; - this.showDiffButton = (value.length == 2) ? true : false; + this.showDiffButton = (value.length == 2 && value[0].language == value[1].language) ? true : false; let canDelete = (value.length > 0)? true : false; for (const revision of value) { if (revision.createdBy != this.userProfile?.userName || revision.apiRevisionType != "manual") @@ -357,5 +427,172 @@ export class RevisionsListComponent implements OnInit, OnChanges { */ onSort(event: SortEvent) { this.loadAPIRevisions(0, this.pageSize, true, null, event.field, event.order); + } + + // Show or hide the sidebar for creating a review + onHideCreateRevisionSidebar() { + this.createRevisionForm.reset(); + this.createRevisionInstruction = []; + this.setCreateRevisionLanguageBasedOnReview(); + } + + /** + * Callback to invoke on file selction for review creation + * @param event the Filter event + */ + onFileSelect(event: FileSelectEvent) { + const uploadFile = event.currentFiles[0]; + this.createRevisionForm.get('selectedFile')?.setValue(uploadFile); + } + + // Fire API request to create the review + createRevision() { + if (this.createRevisionForm.valid) { + + const formData: FormData = new FormData(); + formData.append("label", this.createRevisionForm.get('label')?.value!); + formData.append("language", this.createRevisionForm.get('selectedCRLanguage')?.value?.data!); + + if (this.createRevisionForm.get('filePath')?.value) { + formData.append("filePath", this.createRevisionForm.get('filePath')?.value!); + } + + if (this.createRevisionForm.get('selectedFile')?.value) { + const file = this.createRevisionForm.get('selectedFile')?.value as File; + formData.append("file", file, file.name); + } + + this.creatingRevision = true; + this.crButtonText = "Creating Review "; + + this.reviewsService.createReview(formData).subscribe({ + next: (response: any) => { + if (response) { + this.createRevisionSidebarVisible = false; + this.creatingRevision = false; + this.crButtonText = "Create Review"; + this.apiRevisionsService.openAPIRevisionPage(response.reviewId, response.id, this.router.url); + } + }, + error: (error: any) => { + this.creatingRevision = false; + } + }); + } + } + + onCRLanguageSelectChange() { + switch(this.createRevisionForm.get('selectedCRLanguage')?.value?.data){ + case "C": + this.createRevisionInstruction = [ + `Install clang 10 or later.`, + `Run clang [inputs like az_*.h] -Xclang -ast-dump=json -I ..\\..\\..\\core\\core\\inc -I "c:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview\\VC\\Tools\\MSVC\\14.26.28801\\include\\" > az_core.ast`, + `Archive the file Compress-Archive az_core.ast -DestinationPath az_core.zip`, + `Upload the resulting archive.` + ]; + this.acceptedFilesForReviewUpload = ".zip"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "C#": + this.createRevisionInstruction = [ + `Run dotnet pack`, + `Upload the resulting .nupkg file.` + ]; + this.acceptedFilesForReviewUpload = ".nupkg"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "C++": + this.createRevisionInstruction = [ + `Generate a token file using the C++ parser`, + `Upload the token file generated.` + ]; + this.acceptedFilesForReviewUpload = ".json"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "Java": + this.createRevisionInstruction = [ + `Run a mvn package build on your project, which will generate a number of build artifacts in the /target directory. In there, find the file ending sources.jar, and select it.`, + ]; + this.acceptedFilesForReviewUpload = ".sources.jar"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "Python": + this.createRevisionInstruction = [ + `Generate wheel for the package. python setup.py bdist_wheel -d [dest_folder]`, + `Upload generated whl file` + ]; + this.acceptedFilesForReviewUpload = ".whl"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "JavaScript": + this.createRevisionInstruction = [ + `Use api-extractor to generate a docModel file`, + `Upload generated api.json file` + ]; + this.acceptedFilesForReviewUpload = ".api.json"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "Go": + this.createRevisionInstruction = [ + `Archive source module directory in which go.mod is present. Compress-Archive ./sdk/azcore -DestinationPath azcore.zip`, + `Rename the file Rename-Item azcore.zip -NewName azcore.gosource`, + `Upload the resulting archive.` + ]; + this.acceptedFilesForReviewUpload = ".gosource"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "Swift": + this.createRevisionInstruction = [ + `Generate JSON file for the source by running Swift APIView parser in XCode. More information is available here on Swift API parser`, + `Upload generated JSON` + ]; + this.acceptedFilesForReviewUpload = ".json"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "Swagger": + this.createRevisionInstruction = [ + `Rename swagger json to replace file extension to .swagger Rename-Item PetSwagger.json -NewName PetSwagger.swagger`, + `Upload renamed swagger file` + ]; + this.acceptedFilesForReviewUpload = ".swagger"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + case "TypeSpec": + this.createRevisionInstruction = [ + `Rename swagger json to replace file extension to .swagger Rename-Item PetSwagger.json -NewName PetSwagger.swagger`, + `Upload renamed swagger file` + ]; + this.acceptedFilesForReviewUpload = ".json"; + this.createRevisionForm.get('selectedFile')?.disable(); + this.createRevisionForm.get('filePath')?.enable(); + break; + case "Json": + this.createRevisionInstruction = [ + `Upload JSON API review token file.` + ]; + this.acceptedFilesForReviewUpload = ".json, .tgz"; + this.createRevisionForm.get('selectedFile')?.enable(); + this.createRevisionForm.get('filePath')?.disable(); + break; + default: + this.createRevisionInstruction = [] } + + if (this.revisionCreationFileUpload) { + this.revisionCreationFileUpload.clear(); + } + + this.createRevisionForm.get('label')?.reset(); + this.createRevisionForm.get('selectedFile')?.reset(); + this.createRevisionForm.get('filePath')?.reset() + } } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/review-info/review-info.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/review-info/review-info.component.ts index e3b9417a27d..e4a43d9d596 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_components/shared/review-info/review-info.component.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_components/shared/review-info/review-info.component.ts @@ -17,7 +17,6 @@ export class ReviewInfoComponent { @Input() userProfile: UserProfile | undefined; @Input() review : Review | undefined = undefined; - @Output() revisionsSidePanel : EventEmitter = new EventEmitter(); @Output() pageOptionsEmitter : EventEmitter = new EventEmitter(); showPageOptions: boolean = true; @@ -42,10 +41,6 @@ export class ReviewInfoComponent { } } - showRevisionSidePanel() { - this.revisionsSidePanel.emit(true); - } - onRightPanelCheckChange(event: any) { this.pageOptionsEmitter.emit(event.target.checked); } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_modules/review-page/review-page.module.ts b/src/dotnet/APIView/ClientSPA/src/app/_modules/review-page/review-page.module.ts index 13ef5ec4818..3023b546676 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_modules/review-page/review-page.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_modules/review-page/review-page.module.ts @@ -11,25 +11,17 @@ import { EditorModule } from 'primeng/editor'; import { PanelModule } from 'primeng/panel'; import { TreeSelectModule } from 'primeng/treeselect'; import { MenuModule } from 'primeng/menu'; -import { SplitterModule } from 'primeng/splitter'; -import { SidebarModule } from 'primeng/sidebar'; import { TimelineModule } from 'primeng/timeline'; import { SharedAppModule } from '../shared/shared-app.module'; import { ButtonModule } from 'primeng/button'; -import { TimeagoModule } from 'ngx-timeago'; -import { MenubarModule } from 'primeng/menubar'; import { UiScrollModule } from 'ngx-ui-scroll' ; import { PageOptionsSectionComponent } from 'src/app/_components/shared/page-options-section/page-options-section.component'; import { ApiRevisionOptionsComponent } from 'src/app/_components/api-revision-options/api-revision-options.component'; -import { DropdownModule } from 'primeng/dropdown'; -import { FormsModule } from '@angular/forms'; import { MarkdownToHtmlPipe } from 'src/app/_pipes/markdown-to-html.pipe'; import { EditorComponent } from 'src/app/_components/shared/editor/editor.component'; import { SelectButtonModule } from 'primeng/selectbutton'; -import { ChipModule } from 'primeng/chip'; import { ReviewPageOptionsComponent } from 'src/app/_components/review-page-options/review-page-options.component'; import { InputSwitchModule } from 'primeng/inputswitch'; -import { MultiSelectModule } from 'primeng/multiselect'; const routes: Routes = [ { path: '', component: ReviewPageComponent } @@ -51,25 +43,16 @@ const routes: Routes = [ imports: [ SharedAppModule, CommonModule, - ChipModule, EditorModule, PanelModule, DialogModule, TreeSelectModule, MenuModule, - MenubarModule, - MultiSelectModule, - SplitterModule, - SidebarModule, TimelineModule, ButtonModule, - SelectButtonModule, - FormsModule, InputSwitchModule, UiScrollModule, - DropdownModule, RouterModule.forChild(routes), - TimeagoModule.forRoot(), ] }) export class ReviewPageModule { } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts b/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts index 363820333b7..2fc1c741a6d 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_modules/shared/shared-app.module.ts @@ -5,11 +5,26 @@ import { FooterComponent } from 'src/app/_components/shared/footer/footer.compon import { LanguageNamesPipe } from 'src/app/_pipes/language-names.pipe'; import { LastUpdatedOnPipe } from 'src/app/_pipes/last-updated-on.pipe'; import { ApprovalPipe } from 'src/app/_pipes/approval.pipe'; +import { RevisionsListComponent } from 'src/app/_components/revisions-list/revisions-list.component'; +import { ContextMenuModule } from 'primeng/contextmenu'; +import { TableModule } from 'primeng/table'; +import { ChipModule } from 'primeng/chip'; +import { DropdownModule } from 'primeng/dropdown'; +import { MenubarModule } from 'primeng/menubar'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { SplitterModule } from 'primeng/splitter'; +import { SidebarModule } from 'primeng/sidebar'; +import { TimeagoModule } from 'ngx-timeago'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { FileUploadModule } from 'primeng/fileupload'; +import { InputTextModule } from 'primeng/inputtext'; @NgModule({ declarations: [ NavBarComponent, FooterComponent, + RevisionsListComponent, LanguageNamesPipe, LastUpdatedOnPipe, ApprovalPipe @@ -17,12 +32,41 @@ import { ApprovalPipe } from 'src/app/_pipes/approval.pipe'; exports: [ NavBarComponent, FooterComponent, + RevisionsListComponent, LanguageNamesPipe, LastUpdatedOnPipe, - ApprovalPipe + ApprovalPipe, + ContextMenuModule, + TableModule, + ChipModule, + DropdownModule, + MenubarModule, + MultiSelectModule, + FormsModule, + FileUploadModule, + ReactiveFormsModule, + SelectButtonModule, + SplitterModule, + SidebarModule, + TimeagoModule, + InputTextModule ], imports: [ - CommonModule + CommonModule, + ContextMenuModule, + TableModule, + ChipModule, + DropdownModule, + MenubarModule, + MultiSelectModule, + FormsModule, + FileUploadModule, + ReactiveFormsModule, + SelectButtonModule, + SplitterModule, + SidebarModule, + InputTextModule, + TimeagoModule.forRoot(), ] }) export class SharedAppModule { } diff --git a/src/dotnet/APIView/ClientSPA/src/app/_services/revisions/revisions.service.ts b/src/dotnet/APIView/ClientSPA/src/app/_services/revisions/revisions.service.ts index 881caa7cc95..e53569fa163 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_services/revisions/revisions.service.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_services/revisions/revisions.service.ts @@ -126,12 +126,14 @@ export class RevisionsService { }); } - openDiffOfAPIRevisions(reviewId: string, activeAPIRevisionId: string, diffAPIRevisionsId: string) { - window.open(this.configService.webAppUrl + `Assemblies/Review/${reviewId}?revisionId=${activeAPIRevisionId}&diffOnly=False&doc=False&diffRevisionId=${diffAPIRevisionsId}`, '_blank'); + openDiffOfAPIRevisions(reviewId: string, activeAPIRevisionId: string, diffAPIRevisionsId: string, currentRoute: string) { + const target = (currentRoute.includes("review")) ? '_self' : '_blank'; + window.open(this.configService.webAppUrl + `Assemblies/Review/${reviewId}?revisionId=${activeAPIRevisionId}&diffOnly=False&doc=False&diffRevisionId=${diffAPIRevisionsId}`, target); } - openAPIRevisionPage(reviewId: string, activeAPIRevisionId: string) { - window.open(this.configService.webAppUrl + `Assemblies/Review/${reviewId}?revisionId=${activeAPIRevisionId}`, '_blank'); + openAPIRevisionPage(reviewId: string, activeAPIRevisionId: string, currentRoute: string) { + const target = (currentRoute.includes("review")) ? '_self' : '_blank'; + window.open(this.configService.webAppUrl + `Assemblies/Review/${reviewId}?revisionId=${activeAPIRevisionId}`, target); } updateSelectedReviewers(reviewId: string, apiRevisionId: string, reviewers: Set): Observable { diff --git a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts index 2297f358e78..b69e9dcc59c 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/app.module.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/app.module.ts @@ -1,28 +1,18 @@ -import { NgModule, APP_INITIALIZER, isDevMode } from '@angular/core'; +import { NgModule, APP_INITIALIZER } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { IndexPageComponent } from './_components/index-page/index-page.component'; import { ReviewsListComponent } from './_components/reviews-list/reviews-list.component'; -import { MenubarModule } from 'primeng/menubar'; -import { TableModule } from 'primeng/table'; import { InputTextModule } from 'primeng/inputtext'; import { TabMenuModule } from 'primeng/tabmenu'; import { ToolbarModule } from 'primeng/toolbar'; -import { DropdownModule } from 'primeng/dropdown'; -import { MultiSelectModule } from 'primeng/multiselect'; -import { SidebarModule } from 'primeng/sidebar'; -import { TimeagoModule } from "ngx-timeago"; -import { ChipModule } from 'primeng/chip'; import { BadgeModule } from 'primeng/badge'; -import { ContextMenuModule } from 'primeng/contextmenu'; import { FileUploadModule } from 'primeng/fileupload'; -import { SplitterModule } from 'primeng/splitter'; -import { RevisionsListComponent } from './_components/revisions-list/revisions-list.component'; import { Observable } from 'rxjs'; import { ConfigService } from './_services/config/config.service'; import { CookieService } from 'ngx-cookie-service'; @@ -39,8 +29,7 @@ export function initializeApp(configService: ConfigService) { declarations: [ AppComponent, IndexPageComponent, - ReviewsListComponent, - RevisionsListComponent + ReviewsListComponent ], imports: [ SharedAppModule, @@ -48,22 +37,9 @@ export function initializeApp(configService: ConfigService) { BadgeModule, BrowserModule, BrowserAnimationsModule, - ChipModule, - ContextMenuModule, TabMenuModule, ToolbarModule, - DropdownModule, - FileUploadModule, HttpClientModule, - InputTextModule, - MenubarModule, - MultiSelectModule, - FormsModule, - ReactiveFormsModule, - SidebarModule, - SplitterModule, - TableModule, - TimeagoModule.forRoot() ], providers: [ ConfigService,