Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed errors and aesthetics in report-overriding message #1514

Closed
wants to merge 16 commits into from
Closed
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
21 changes: 15 additions & 6 deletions src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ interface ErrorData {
error: string
}

export class NetworkError extends Error {
name = 'NetworkError'
}

export const request = <Expected>(
method: HTTPMethod,
endpoint: string,
Expand All @@ -33,12 +37,17 @@ export const request = <Expected>(

onCancel(() => controller.abort())
const jwt = await getWorkingJWT()
const resp = await fetch(apiUrl + endpoint + qs(params), {
method,
body: JSON.stringify(body),
headers: jwt ? { Authorization: `Bearer ${jwt.raw}` } : {},
signal,
})
let resp: Response
try {
resp = await fetch(apiUrl + endpoint + qs(params), {
method,
body: JSON.stringify(body),
headers: jwt ? { Authorization: `Bearer ${jwt.raw}` } : {},
signal,
})
} catch (error_) {
throw new NetworkError(error_.message)
}

const text = await resp.text()

Expand Down
6 changes: 5 additions & 1 deletion src/components/analysis-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { usePromise } from '@/utils/use-promise'
import { getEventTeams } from '@/api/event-team-info/get-event-teams'
import { eventTeamUrl } from '@/utils/urls/event-team'
import { EventTeamInfo } from '@/api/event-team-info'
import { isData } from '@/utils/is-data'

interface Props {
eventKey: string
Expand Down Expand Up @@ -224,7 +225,10 @@ const AnalysisTable = ({
const rankColumn: Column<EventTeamInfo | undefined, RowType> = {
title: 'Rank',
key: 'Rank',
getCell: (row) => rankingInfo?.find((r) => r.team === 'frc' + row.team),
getCell: (row) =>
isData(rankingInfo)
? rankingInfo.find((r) => r.team === 'frc' + row.team)
: undefined,
getCellValue: (cell) => cell?.rank ?? Infinity,
renderCell: (cell) => (
<td class={rankCellStyle}>
Expand Down
9 changes: 7 additions & 2 deletions src/components/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { cleanFieldName } from '@/utils/clean-field-name'
import { getFieldKey } from '@/utils/get-field-key'
import { getReports } from '@/api/report/get-reports'
import { GetReport } from '@/api/report'
import { isData } from '@/utils/is-data'

const commentsDisplayStyle = css`
grid-column: 1 / -1;
Expand Down Expand Up @@ -106,7 +107,7 @@ export const ChartCard = ({
return matchesAutoFieldName || matchesTeleopFieldName
})?.name

const matchesWithSelectedStat = (matchesStats || [])
const matchesWithSelectedStat = (isData(matchesStats) ? matchesStats : [])
.map(({ matchKey, stats }) => {
const matchingStat = stats.find((f) => f.name === fullFieldName)
if (matchingStat) return { matchKey, matchingStat }
Expand Down Expand Up @@ -190,7 +191,11 @@ export const ChartCard = ({
</div>
{selectedMatchKey && (
<CommentsDisplay
reports={allReports.filter((r) => r.matchKey === selectedMatchKey)}
reports={
isData(allReports)
? allReports.filter((r) => r.matchKey === selectedMatchKey)
: []
}
/>
)}
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/components/comment-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Icon from './icon'
import { mdiMessageReplyText } from '@mdi/js'
import { formatUserName } from '@/utils/format-user-name'
import { getFastestUser } from '@/cache/users/get-fastest'
import { isData } from '@/utils/is-data'

const commentCardStyle = css`
display: grid;
Expand Down Expand Up @@ -52,7 +53,11 @@ export const CommentCard = ({
class={commentCardStyle}
>
<Icon icon={mdiMessageReplyText} />
{showReporter && <span>{formatUserName(reporter, reporterId)}</span>}
{showReporter && (
<span>
{formatUserName(isData(reporter) ? reporter : undefined, reporterId)}
</span>
)}
<p>{report.comment}</p>
</Card>
)
Expand Down
8 changes: 7 additions & 1 deletion src/components/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@ export const DialogDisplayer = () => {
<div tabIndex={0} class={dialogStyle} aria-modal="true" role="dialog">
{dialog.title && <h1>{dialog.title}</h1>}
{typeof dialog.description === 'string' ? (
<p>{dialog.description}</p>
<p
class={css`
font-weight: 500;
`}
>
{dialog.description}
</p>
) : (
dialog.description
)}
Expand Down
3 changes: 2 additions & 1 deletion src/components/profile-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { css } from 'linaria'
import { mdiAccountCircle } from '@mdi/js'
import { pigmicePurple } from '@/colors'
import { getFastestUser } from '@/cache/users/get-fastest'
import { isData } from '@/utils/is-data'

export const ProfileLink = ({
reporterId,
Expand All @@ -26,7 +27,7 @@ export const ProfileLink = ({
class={reporterStyle}
>
<Icon icon={mdiAccountCircle} />
{formatUserName(reporter, reporterId)}
{formatUserName(isData(reporter) ? reporter : undefined, reporterId)}
</El>
)
}
Expand Down
145 changes: 84 additions & 61 deletions src/components/report-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import { mdiAccountCircle } from '@mdi/js'
import { useEventMatches } from '@/cache/event-matches/use'
import { formatMatchKeyShort } from '@/utils/format-match-key-short'
import { matchHasTeam } from '@/utils/match-has-team'
import { request } from '@/api/base'
import { createDialog } from './dialog'
import { createAlert } from '@/router'
import { AlertType } from './alert'
import Loader from './loader'
import Card from './card'
import { isData } from '@/utils/is-data'
import { getReports } from '@/api/report/get-reports'

const reportEditorStyle = css`
padding: 1.5rem 2rem;
Expand Down Expand Up @@ -71,6 +70,7 @@ const userDropdownStyle = css`
align-items: center;
`

// eslint-disable-next-line complexity
export const ReportEditor = ({
initialReport,
onMatchSelect,
Expand All @@ -84,10 +84,16 @@ export const ReportEditor = ({
const [isDeleting, setIsDeleting] = useState<boolean>(false)
const [matchKey, setMatchKey] = useState(initialReport.matchKey)
const [comment, setComment] = useState(initialReport.comment || '')
const schemaId = useEventInfo(eventKey)?.schemaId
const schema = useSchema(schemaId)?.schema

const eventInfo = useEventInfo(eventKey)
const schemaId = isData(eventInfo) ? eventInfo.schemaId : undefined
const testSchema = useSchema(schemaId)
const schema = isData(testSchema) ? testSchema.schema : undefined

const eventMatches = useEventMatches(eventKey)
const match = eventMatches?.find((match) => matchKey === match.key)
const match = isData(eventMatches)
? eventMatches.find((match) => matchKey === match.key)
: undefined
const { jwt } = useJWT()
const isAdmin = jwt?.peregrineRoles.isAdmin
const users = usePromise(() => getUsers(), [])
Expand Down Expand Up @@ -161,81 +167,86 @@ export const ReportEditor = ({
}
const report = getReportIfValid()

const submit = (e: Event) => {
const submit = async (e: Event) => {
e.preventDefault()
if (!report) return
setIsSaving(true)
uploadReport(report)
.then((id) => {
onSaveSuccess({ ...report, id })
})
.catch(async (error: { error?: string; id?: number }) => {
if (error.error === 'conflicts' && error.id !== undefined) {
const conflictingId = error.id
const shouldOverride = await createDialog({
confirm: `Override Report ${conflictingId}`,
dismiss: 'Cancel',
description: `There is already a report with the same reporter, team, and match.`,
title: `This report conflicts with report ${conflictingId}`,
})
if (shouldOverride) {
await request<null>(
'PUT',
`reports/${report.id}`,
{ replace: true },
report,
)
createAlert({
type: AlertType.Success,
message: `Report ${conflictingId} was overridden`,
})
onSaveSuccess(report as GetReport)
}
if (report.key) {
deleteReportLocally(report.key)
onDelete?.()
}
} else {
const matchReports = await getReports({
event: report.eventKey,
match: report.matchKey,
})
let shouldOverride = true
for (const conflictReport of matchReports) {
if (
conflictReport.id !== report.id &&
conflictReport.teamKey === report.teamKey &&
conflictReport.reporterId === report.reporterId
) {
const conflictingId = conflictReport.id
shouldOverride = await createDialog({
confirm: `Override Report ${conflictingId}`,
dismiss: 'Cancel',
description: `There is already a report with the same reporter, team, and match.`,
title: `This report conflicts with report ${conflictingId}.`,
})
}
}
if (shouldOverride) {
uploadReport(report)
.then((id) => {
onSaveSuccess({ ...report, id })
})
.catch(() => {
const reportWithKey = {
...report,
key: initialReport.key || generateReportKey(),
}
saveReportLocally(reportWithKey)
if (onSaveLocally) onSaveLocally(reportWithKey)
}
})
.finally(() => {
setIsSaving(false)
})
})
}
setIsSaving(false)
}
const handleDelete = async (e: Event) => {
e.preventDefault()
if (initialReport.key) {
deleteReportLocally(initialReport.key)
} else {
const reportId = initialReport.id
if (reportId !== undefined) {
setIsDeleting(true)
await deleteReport(reportId)
setIsDeleting(false)
const confirmDelete = await createDialog({
confirm: 'Delete Report',
dismiss: 'Cancel',
description: '',
title: 'Confirm Delete',
})
if (confirmDelete) {
e.preventDefault()
if (initialReport.key) {
deleteReportLocally(initialReport.key)
} else {
const reportId = initialReport.id
if (reportId !== undefined) {
setIsDeleting(true)
await deleteReport(reportId)
setIsDeleting(false)
}
}
onDelete?.()
}
onDelete?.()
}
const reportAlreadyExists =
initialReport.key !== undefined || initialReport.id !== undefined

return eventMatches && match && visibleFields ? (
<Card as="form" class={reportEditorStyle} onSubmit={submit}>
<Dropdown
options={eventMatches}
options={isData(eventMatches) ? eventMatches : []}
onChange={(match) => {
onMatchSelect?.(match.key)
setMatchKey(match.key)
}}
getKey={(match) => match.key}
getText={(match) => formatMatchKeyShort(match.key)}
value={eventMatches.find((match) => match.key === matchKey)}
value={
isData(eventMatches)
? eventMatches.find((match) => match.key === matchKey)
: undefined
}
/>
<TeamPicker
onChange={setTeam}
Expand Down Expand Up @@ -271,20 +282,32 @@ export const ReportEditor = ({
<div class={userDropdownStyle}>
<Icon icon={mdiAccountCircle} />
<Dropdown
options={users.sort((a, b) =>
a.firstName.toLowerCase() > b.firstName.toLowerCase() ? 1 : -1,
)}
options={
isData(users)
? users.sort((a, b) =>
a.firstName.toLowerCase() > b.firstName.toLowerCase()
? 1
: -1,
)
: []
}
onChange={(user) => {
setReporterId(user.id)
setRealmId(user.realmId)
}}
getKey={(user) => user.id}
getText={(user) => `${user.firstName} ${user.lastName}`}
getGroup={(user) =>
realms?.find((realm) => realm.id === user.realmId)?.name ||
String(user.realmId)
isData(realms)
? realms.find((realm) => realm.id === user.realmId)?.name ||
String(user.realmId)
: null
}
value={
isData(users)
? users.find((user) => user.id === reporterId)
: undefined
}
value={users.find((user) => user.id === reporterId)}
/>
</div>
)}
Expand Down
13 changes: 7 additions & 6 deletions src/components/report-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CommentCard } from './comment-card'
import { css } from 'linaria'
import { cleanFieldName } from '@/utils/clean-field-name'
import { ProfileLink } from './profile-link'
import { isData } from '@/utils/is-data'

interface Props {
report: Report
Expand Down Expand Up @@ -66,10 +67,10 @@ export const ReportViewer = ({ report, onEditClick }: Props) => {
const reporterId = report.reporterId
const eventInfo = useEventInfo(report.eventKey)
const matchInfo = useMatchInfo(report.eventKey, report.matchKey)
const schema = useSchema(eventInfo?.schemaId)
const displayableFields = schema?.schema.filter(
(field) => field.reportReference !== undefined,
)
const schema = useSchema(isData(eventInfo) ? eventInfo.schemaId : undefined)
const displayableFields = isData(schema)
? schema.schema.filter((field) => field.reportReference !== undefined)
: undefined
const autoFields = displayableFields?.filter(
(field) => field.period === 'auto',
)
Expand Down Expand Up @@ -99,8 +100,8 @@ export const ReportViewer = ({ report, onEditClick }: Props) => {
<div>
{matchInfo && (
<TeamPicker
redAlliance={matchInfo.redAlliance}
blueAlliance={matchInfo.blueAlliance}
redAlliance={isData(matchInfo) ? matchInfo.redAlliance : []}
blueAlliance={isData(matchInfo) ? matchInfo.blueAlliance : []}
value={report.teamKey}
editable={false}
/>
Expand Down
Loading