Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@ Any property preceded by `opt` is optional and can be omitted. For example, to o
dfx canister call backend update_user_profile '(record { user_id = "${userId}"; username = opt "${username}"; })'
```

Or to only upgrade a user to a reviewer:

```bash

dfx canister call backend update_user_profile '(record { user_id = "${userId}"; config = opt variant { reviewer = record {} } })'
```

### Listing open proposals

To list open replica version management proposals:
Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"dfx": "0.21.0",
"dfx": "0.24.3",
"output_env_file": ".env",
"version": 1,
"networks": {
Expand Down
20 changes: 20 additions & 0 deletions lib/styles/global/buttons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ $btn-disabled-color: rgba(colors.$primary, 0.45);
border-color: $btn-disabled-color;
background-color: transparent;
}

&.btn--success {
color: colors.$success;
border-color: colors.$success;

&:hover:not(:disabled) {
background-color: colors.$success;
border-color: colors.$success;
}
}

&.btn--error {
color: colors.$error;
border-color: colors.$error;

&:hover:not(:disabled) {
background-color: colors.$error;
border-color: colors.$error;
}
}
}

.btn-group {
Expand Down
33 changes: 25 additions & 8 deletions src/frontend/src/app/core/api/review/review-api.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function mapCreateProposalReviewRequest(
review_duration_mins: toCandidOpt(req.reviewDurationMins),
summary: toCandidOpt(req.summary),
build_reproduced: toCandidOpt(req.buildReproduced),
vote: mapProposalVoteRequest(req.vote),
vote: toCandidOpt(mapProposalVoteRequest(req.vote)),
};
}

Expand All @@ -43,12 +43,11 @@ export function mapUpdateProposalReviewRequest(
): UpdateProposalReviewApiRequest {
return {
proposal_id: req.proposalId,
// [TODO] - connect with form once it's implemented
status: [],
status: toCandidOpt(mapProposalReviewStatusRequest(req.status)),
review_duration_mins: toCandidOpt(req.reviewDurationMins),
summary: toCandidOpt(req.summary),
build_reproduced: toCandidOpt(req.buildReproduced),
vote: mapProposalVoteRequest(req.vote),
vote: toCandidOpt(mapProposalVoteRequest(req.vote)),
};
}

Expand Down Expand Up @@ -101,6 +100,24 @@ export function mapGetProposalReviewResponse(
};
}

function mapProposalReviewStatusRequest(
status?: ProposalReviewStatus | null,
): ProposalReviewStatusApi | null {
switch (status) {
case ProposalReviewStatus.Published: {
return { published: null };
}

case ProposalReviewStatus.Draft: {
return { draft: null };
}

default: {
return null;
}
}
}

function mapProposalReviewStatusResponse(
res: ProposalReviewStatusApi,
): ProposalReviewStatus {
Expand All @@ -111,16 +128,16 @@ function mapProposalReviewStatusResponse(
return ProposalReviewStatus.Draft;
}

function mapProposalVoteRequest(vote?: boolean | null): [] | [ApiProposalVote] {
function mapProposalVoteRequest(vote?: boolean | null): ApiProposalVote | null {
switch (vote) {
case true: {
return [{ yes: null }];
return { yes: null };
}
case false: {
return [{ no: null }];
return { no: null };
}
default: {
return [];
return null;
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/frontend/src/app/core/api/review/review-api.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface CreateProposalReviewRequest {

export interface UpdateProposalReviewRequest {
proposalId: string;
status?: ProposalReviewStatus | null;
reviewDurationMins?: number | null;
summary?: string | null;
buildReproduced?: boolean | null;
Expand Down Expand Up @@ -46,14 +47,14 @@ export interface GetProposalReviewResponse {
}

export enum ProposalReviewStatus {
Draft,
Published,
Draft = 'Draft',
Published = 'Published',
}

export enum ProposalReviewVote {
Adopt,
Reject,
NoVote,
Adopt = 'Adopt',
Reject = 'Reject',
NoVote = 'NoVote',
}

export interface ProposalCommitReviewHighlight {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, input } from '@angular/core';

@Component({
selector: 'app-loading-icon',
Expand All @@ -16,10 +16,17 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
}

.loading-icon__svg {
stroke: common.$primary;
fill: none;
stroke-width: 10px;
}

.loading-icon__svg--primary {
stroke: common.$primary;
}

.loading-icon__svg--white {
stroke: common.$white;
}
`,
],
template: `
Expand All @@ -32,7 +39,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
preserveAspectRatio="xMidYMid"
>
<circle
class="loading-icon__svg"
[class]="'loading-icon__svg loading-icon__svg--' + theme()"
cx="50"
cy="50"
r="32"
Expand All @@ -51,4 +58,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
</svg>
`,
})
export class LoadingIconComponent {}
export class LoadingIconComponent {
public readonly theme = input<'white' | 'primary'>('primary');
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
GetProposalReviewCommitResponse,
UpdateProposalReviewRequest,
ReviewCommitDetails,
ProposalReviewStatus,
} from '../../api';
import { batchApiCall, filterNotNil, isNil, isNotNil } from '../../utils';

Expand Down Expand Up @@ -79,15 +80,7 @@ export class ReviewSubmissionService {
takeUntilDestroyed(),
filterNotNil(),
batchApiCall(review =>
from(
this.reviewApiService.updateProposalReview({
proposalId: this.proposalId!,
summary: review.summary,
reviewDurationMins: review.reviewDurationMins,
buildReproduced: review.buildReproduced,
vote: review.vote,
}),
),
from(this.reviewApiService.updateProposalReview(review)),
),
)
.subscribe({});
Expand Down Expand Up @@ -135,6 +128,38 @@ export class ReviewSubmissionService {
this.pendingReviewSubject.next(updatedReview);
}

public async publishReview(): Promise<void> {
await this.updateStatus(ProposalReviewStatus.Published);
}

public async editReview(): Promise<void> {
await this.updateStatus(ProposalReviewStatus.Draft);
}

private async updateStatus(status: ProposalReviewStatus): Promise<void> {
if (isNil(this.proposalId)) {
throw new Error(
'Tried to update review status without selecting a proposal',
);
}

const currentReview = this.reviewSubject.value;
if (isNil(currentReview)) {
throw new Error('Tried to update review status before review was loaded');
}

await this.reviewApiService.updateProposalReview({
proposalId: this.proposalId,
status,
});

this.reviewSubject.next({
...currentReview,
...(this.pendingReviewSubject.value || {}),
status,
});
}

public addCommit(): void {
if (isNil(this.proposalId)) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@ import { LoadingIconComponent } from '../../icons';
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [
`
.loading-button {
&:disabled {
cursor: not-allowed;
}
}

.loading-button__text--transparent {
color: transparent;
}
`,
],
template: `
<button [type]="type()" [disabled]="disabled()" [class]="btnClass()">
<button
[type]="type()"
[disabled]="isSaving() || disabled()"
[class]="'loading-button ' + btnClass()"
>
@if (isSaving()) {
<app-loading-icon class="btn--loading" aria-label="Saving" />
<app-loading-icon
class="btn--loading"
aria-label="Saving"
[theme]="theme()"
/>
}

<div
Expand All @@ -31,6 +45,8 @@ import { LoadingIconComponent } from '../../icons';
export class LoadingButtonComponent {
public readonly type = input<'submit' | 'button'>('button');

public readonly theme = input<'primary' | 'white'>('primary');

public readonly disabled = input(false);

public readonly btnClass = input('');
Expand Down
17 changes: 9 additions & 8 deletions src/frontend/src/app/core/utils/route-param.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { inject, Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { Observable, map } from 'rxjs';
import { map } from 'rxjs';

import { filterNotNil } from './nil';

export function routeParam(name: string): Observable<string> {
export function routeParamSignal(name: string): Signal<string | undefined> {
const route = inject(ActivatedRoute);

return route.paramMap.pipe(
map(params => params.get(name)),
filterNotNil(),
takeUntilDestroyed(),
return toSignal(
route.paramMap.pipe(
map(params => params.get(name)),
filterNotNil(),
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ interface AdminProfileForm {
<app-loading-button
btnClass="btn"
type="submit"
[disabled]="profileForm().invalid || isSaving()"
[disabled]="profileForm().invalid"
[isSaving]="isSaving()"
>
Save
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface ReviewerProfileForm {
<app-loading-button
btnClass="btn"
type="submit"
[disabled]="profileForm().invalid || isSaving()"
[disabled]="profileForm().invalid"
[isSaving]="isSaving()"
>
Save
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export type SocialMediaForm = {
<app-loading-button
btnClass="btn"
type="submit"
[disabled]="socialMediaForm().invalid || isSaving()"
[disabled]="socialMediaForm().invalid"
[isSaving]="isSaving()"
>
Save
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Component,
SecurityContext,
computed,
effect,
signal,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
Expand All @@ -25,7 +26,7 @@ import {
KeyValueGridComponent,
ValueColComponent,
} from '~core/ui';
import { routeParam, toSyncSignal } from '~core/utils';
import { isNotNil, routeParamSignal, toSyncSignal } from '~core/utils';
import { ClosedProposalSummaryComponent } from './closed-proposal-summary';

@Component({
Expand Down Expand Up @@ -180,7 +181,11 @@ import { ClosedProposalSummaryComponent } from './closed-proposal-summary';
proposal.state === ProposalState().InProgress &&
(isReviewer() || isAdmin())
) {
<button type="button" class="btn" (click)="onToggleSummary()">
<button
type="button"
class="btn btn--outline"
(click)="onToggleSummary()"
>
{{
showSummary()
? 'Show proposal description'
Expand Down Expand Up @@ -259,6 +264,8 @@ export class ProposalDetailsComponent {
public readonly isReviewer = toSyncSignal(this.profileService.isReviewer$);
public readonly userProfile = toSyncSignal(this.profileService.userProfile$);

public readonly currentProposalId = routeParamSignal('id');

public readonly userReviewList = toSyncSignal(
this.reviewService.userReviewList$,
);
Expand All @@ -277,8 +284,11 @@ export class ProposalDetailsComponent {
private readonly profileService: ProfileService,
private readonly reviewService: ReviewService,
) {
routeParam('id').subscribe(proposalId => {
this.proposalService.setCurrentProposalId(proposalId);
effect(() => {
const proposalId = this.currentProposalId();
if (isNotNil(proposalId)) {
this.proposalService.setCurrentProposalId(proposalId);
}
});

this.proposalService.loadProposalList(ProposalState.Any);
Expand Down
Loading