Hide Irrelevant Review Buttons ()

Closes .

Reviewed-on: 
Co-authored-by: Quaternions <krakow20@gmail.com>
Co-committed-by: Quaternions <krakow20@gmail.com>
This commit is contained in:
Quaternions 2025-04-04 22:10:31 +00:00 committed by Quaternions
parent 8be9475ee5
commit ec15c1f2e5
6 changed files with 209 additions and 31 deletions
web/src/app
mapfixes/[mapfixId]
submissions/[submissionId]
ts

@ -1,8 +1,11 @@
import { Roles, RolesConstants } from "@/app/ts/Roles";
import { MapfixStatus } from "@/app/ts/Mapfix";
import { Button, ButtonOwnProps } from "@mui/material";
import { useState, useEffect } from "react";
type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
type ApiActions = Lowercase<Actions> | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
type ApiActions = Lowercase<Actions> | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
interface ReviewButton {
name: Review,
@ -12,7 +15,9 @@ interface ReviewButton {
}
interface ReviewId {
mapfixId: string
mapfixId: string,
mapfixStatus: number,
mapfixSubmitter: number,
}
async function ReviewButtonClicked(action: ApiActions, mapfixId: string) {
@ -45,7 +50,6 @@ function ReviewButton(props: ReviewButton) {
}
export default function ReviewButtons(props: ReviewId) {
const mapfixId = props.mapfixId
// When is each button visible?
// Multiple buttons can be visible at once.
// Action | Role | When Current Status is One of:
@ -59,16 +63,86 @@ export default function ReviewButtons(props: ReviewId) {
// RequestChanges | Reviewer | Validated, Accepted, Submitted
// Upload | MapAdmin | Validated
// ResetUploading | MapAdmin | Uploading
const { mapfixId, mapfixStatus } = props;
const [user, setUser] = useState<number|null>(null);
const [roles, setRoles] = useState<Roles>(RolesConstants.Empty);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const [rolesData, userData] = await Promise.all([
fetch("/api/session/roles").then(rolesResponse => rolesResponse.json()),
fetch("/api/session/user").then(userResponse => userResponse.json())
]);
setRoles(rolesData.Roles);
setUser(userData.UserID);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, [mapfixId]);
if (loading) return <p>Loading...</p>;
const visibleButtons: ReviewButton[] = [];
const is_submitter = user === props.mapfixSubmitter;
if (is_submitter) {
if ([MapfixStatus.UnderConstruction, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) {
visibleButtons.push({ name: "Submit", action: "submit", color: "info", mapfixId });
}
if ([MapfixStatus.Submitted, MapfixStatus.ChangesRequested].includes(mapfixStatus!)) {
visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", mapfixId });
}
}
if (roles&RolesConstants.MapfixReview) {
// you can't review your own mapfix!
// note that this means there needs to be more than one person with MapfixReview
if (!is_submitter && mapfixStatus === MapfixStatus.Submitted) {
visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", mapfixId });
visibleButtons.push({ name: "Reject", action: "reject", color: "error", mapfixId });
}
if (mapfixStatus === MapfixStatus.Accepted) {
visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", mapfixId });
}
if (mapfixStatus === MapfixStatus.Validating) {
visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", mapfixId });
}
// this button serves the same purpose as Revoke if you are both
// the map submitter and have MapfixReview when status is Submitted
if (
[MapfixStatus.Validated, MapfixStatus.Accepted].includes(mapfixStatus!)
|| !is_submitter && mapfixStatus == MapfixStatus.Submitted
) {
visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", mapfixId });
}
}
if (roles&RolesConstants.MapfixUpload) {
if (mapfixStatus === MapfixStatus.Validated) {
visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", mapfixId });
}
if (mapfixStatus === MapfixStatus.Uploading) {
visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", mapfixId });
}
}
return (
<section className="review-set">
<ReviewButton color="info" name="Submit" action="submit" mapfixId={mapfixId}/>
<ReviewButton color="info" name="Revoke" action="revoke" mapfixId={mapfixId}/>
<ReviewButton color="info" name="Accept" action="trigger-validate" mapfixId={mapfixId}/>
<ReviewButton color="info" name="Validate" action="retry-validate" mapfixId={mapfixId}/>
<ReviewButton color="error" name="Reject" action="reject" mapfixId={mapfixId}/>
<ReviewButton color="info" name="Upload" action="trigger-upload" mapfixId={mapfixId}/>
<ReviewButton color="error" name="Reset Uploading (fix softlocked status)" action="reset-uploading" mapfixId={mapfixId}/>
<ReviewButton color="error" name="Reset Validating (fix softlocked status)" action="reset-validating" mapfixId={mapfixId}/>
{visibleButtons.length === 0 ? (
<p>No available actions</p>
) : (
visibleButtons.map((btn) => (
<ReviewButton key={btn.action} {...btn} />
))
)}
</section>
)
);
}

@ -15,7 +15,9 @@ import { useState, useEffect } from "react";
import "./(styles)/page.scss";
interface ReviewId {
mapfixId: string
mapfixId: string,
mapfixStatus: number;
mapfixSubmitter: number,
}
function Ratings() {
@ -46,7 +48,7 @@ function RatingArea(mapfix: ReviewId) {
<MapImage/>
</section>
<Ratings/>
<ReviewButtons mapfixId={mapfix.mapfixId}/>
<ReviewButtons mapfixId={mapfix.mapfixId} mapfixStatus={mapfix.mapfixStatus} mapfixSubmitter={mapfix.mapfixSubmitter}/>
</aside>
)
}
@ -96,7 +98,7 @@ export default function MapfixInfoPage() {
<Webpage>
<main className="map-page-main">
<section className="review-section">
<RatingArea mapfixId={dynamicId.mapfixId}/>
<RatingArea mapfixId={dynamicId.mapfixId} mapfixStatus={mapfix.StatusID} mapfixSubmitter={mapfix.Submitter}/>
<TitleAndComments name={mapfix.DisplayName} creator={mapfix.Creator} review={mapfix.StatusID} status_message={mapfix.StatusMessage} asset_id={mapfix.AssetID} comments={[]}/>
</section>
</main>

@ -1,8 +1,11 @@
import { Roles, RolesConstants } from "@/app/ts/Roles";
import { SubmissionStatus } from "@/app/ts/Submission";
import { Button, ButtonOwnProps } from "@mui/material";
import { useState, useEffect } from "react";
type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
type ApiActions = Lowercase<Actions> | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
type ApiActions = Lowercase<Actions> | "request-changes" | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
type Review = Actions | "Request Changes" | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
interface ReviewButton {
name: Review,
@ -12,7 +15,9 @@ interface ReviewButton {
}
interface ReviewId {
submissionId: string
submissionId: string,
submissionStatus: number,
submissionSubmitter: number,
}
async function ReviewButtonClicked(action: ApiActions, submissionId: string) {
@ -45,7 +50,6 @@ function ReviewButton(props: ReviewButton) {
}
export default function ReviewButtons(props: ReviewId) {
const submissionId = props.submissionId
// When is each button visible?
// Multiple buttons can be visible at once.
// Action | Role | When Current Status is One of:
@ -59,16 +63,86 @@ export default function ReviewButtons(props: ReviewId) {
// RequestChanges | Reviewer | Validated, Accepted, Submitted
// Upload | MapAdmin | Validated
// ResetUploading | MapAdmin | Uploading
const { submissionId, submissionStatus } = props;
const [user, setUser] = useState<number|null>(null);
const [roles, setRoles] = useState<Roles>(RolesConstants.Empty);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const [rolesData, userData] = await Promise.all([
fetch("/api/session/roles").then(rolesResponse => rolesResponse.json()),
fetch("/api/session/user").then(userResponse => userResponse.json())
]);
setRoles(rolesData.Roles);
setUser(userData.UserID);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, [submissionId]);
if (loading) return <p>Loading...</p>;
const visibleButtons: ReviewButton[] = [];
const is_submitter = user === props.submissionSubmitter;
if (is_submitter) {
if ([SubmissionStatus.UnderConstruction, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) {
visibleButtons.push({ name: "Submit", action: "submit", color: "info", submissionId });
}
if ([SubmissionStatus.Submitted, SubmissionStatus.ChangesRequested].includes(submissionStatus!)) {
visibleButtons.push({ name: "Revoke", action: "revoke", color: "info", submissionId });
}
}
if (roles&RolesConstants.SubmissionReview) {
// you can't review your own submission!
// note that this means there needs to be more than one person with SubmissionReview
if (!is_submitter && submissionStatus === SubmissionStatus.Submitted) {
visibleButtons.push({ name: "Accept", action: "trigger-validate", color: "info", submissionId });
visibleButtons.push({ name: "Reject", action: "reject", color: "error", submissionId });
}
if (submissionStatus === SubmissionStatus.Accepted) {
visibleButtons.push({ name: "Validate", action: "retry-validate", color: "info", submissionId });
}
if (submissionStatus === SubmissionStatus.Validating) {
visibleButtons.push({ name: "Reset Validating (fix softlocked status)", action: "reset-validating", color: "error", submissionId });
}
// this button serves the same purpose as Revoke if you are both
// the map submitter and have SubmissionReview when status is Submitted
if (
[SubmissionStatus.Validated, SubmissionStatus.Accepted].includes(submissionStatus!)
|| !is_submitter && submissionStatus == SubmissionStatus.Submitted
) {
visibleButtons.push({ name: "Request Changes", action: "request-changes", color: "error", submissionId });
}
}
if (roles&RolesConstants.SubmissionUpload) {
if (submissionStatus === SubmissionStatus.Validated) {
visibleButtons.push({ name: "Upload", action: "trigger-upload", color: "info", submissionId });
}
if (submissionStatus === SubmissionStatus.Uploading) {
visibleButtons.push({ name: "Reset Uploading (fix softlocked status)", action: "reset-uploading", color: "error", submissionId });
}
}
return (
<section className="review-set">
<ReviewButton color="info" name="Submit" action="submit" submissionId={submissionId}/>
<ReviewButton color="info" name="Revoke" action="revoke" submissionId={submissionId}/>
<ReviewButton color="info" name="Accept" action="trigger-validate" submissionId={submissionId}/>
<ReviewButton color="info" name="Validate" action="retry-validate" submissionId={submissionId}/>
<ReviewButton color="error" name="Reject" action="reject" submissionId={submissionId}/>
<ReviewButton color="info" name="Upload" action="trigger-upload" submissionId={submissionId}/>
<ReviewButton color="error" name="Reset Uploading (fix softlocked status)" action="reset-uploading" submissionId={submissionId}/>
<ReviewButton color="error" name="Reset Validating (fix softlocked status)" action="reset-validating" submissionId={submissionId}/>
{visibleButtons.length === 0 ? (
<p>No available actions</p>
) : (
visibleButtons.map((btn) => (
<ReviewButton key={btn.action} {...btn} />
))
)}
</section>
)
);
}

@ -17,6 +17,8 @@ import "./(styles)/page.scss";
interface ReviewId {
submissionId: string;
assetId: number;
submissionStatus: number;
submissionSubmitter: number,
}
function Ratings() {
@ -47,7 +49,7 @@ function RatingArea(submission: ReviewId) {
<MapImage id={submission.assetId}/>
</section>
<Ratings/>
<ReviewButtons submissionId={submission.submissionId}/>
<ReviewButtons submissionId={submission.submissionId} submissionStatus={submission.submissionStatus} submissionSubmitter={submission.submissionSubmitter}/>
</aside>
)
}
@ -97,7 +99,7 @@ export default function SubmissionInfoPage() {
<Webpage>
<main className="map-page-main">
<section className="review-section">
<RatingArea assetId={submission.AssetID} submissionId={dynamicId.submissionId}/>
<RatingArea assetId={submission.AssetID} submissionId={dynamicId.submissionId} submissionStatus={submission.StatusID} submissionSubmitter={submission.Submitter}/>
<TitleAndComments name={submission.DisplayName} creator={submission.Creator} review={submission.StatusID} status_message={submission.StatusMessage} asset_id={submission.AssetID} comments={[]}/>
</section>
</main>

@ -8,6 +8,7 @@ const enum MapfixStatus {
Uploading = 6,
Uploaded = 7,
Rejected = 8,
// MapfixStatus does not have a Released state
}
interface MapfixInfo {

25
web/src/app/ts/Roles.ts Normal file

@ -0,0 +1,25 @@
type Roles = number;
// Constants
const RolesConstants = {
All: -1 as Roles,
SubmissionUpload: 1 << 6 as Roles,
SubmissionReview: 1 << 5 as Roles,
SubmissionRelease: 1 << 4 as Roles,
ScriptWrite: 1 << 3 as Roles,
MapfixUpload: 1 << 2 as Roles,
MapfixReview: 1 << 1 as Roles,
MapDownload: 1 << 0 as Roles,
Empty: 0 as Roles,
};
// Operations
function hasRole(flags: Roles, role: Roles): boolean {
return (flags & role) === role;
}
export {
type Roles,
RolesConstants,
hasRole,
};