Skip to content

Commit

Permalink
Proposals/selective publishing (#4885)
Browse files Browse the repository at this point in the history
* wip

* Finish up UI

* Cleanup based on PR feedback

* Add test for concealing proposal workflow

* Add test coverage

* Add highlighting on the contract tab

* Bump core
  • Loading branch information
motechFR authored Oct 25, 2024
1 parent 5a1a578 commit ee0b949
Show file tree
Hide file tree
Showing 21 changed files with 505 additions and 303 deletions.
2 changes: 2 additions & 0 deletions apps/scoutgameadmin/components/common/SiteNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ function getActiveButton(pathname: string) {
return 'repos';
} else if (pathname.startsWith('/transactions')) {
return 'transactions';
} else if (pathname.startsWith('/contract')) {
return 'contract';
} else if (pathname.startsWith('/users')) {
return 'users';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { ProposalEvaluationType } from '@charmverse/core/prisma-client';
import { Alert } from '@mui/material';
import { useMemo, useState } from 'react';

import MultiTabs from 'components/common/MultiTabs';
import { useIsAdmin } from 'hooks/useIsAdmin';
import { useUser } from 'hooks/useUser';
import type { ProposalWithUsersAndRubric, PopulatedEvaluation } from 'lib/proposals/interfaces';
import type { PopulatedEvaluation, ProposalWithUsersAndRubric } from 'lib/proposals/interfaces';
import { showRubricAnswersToAuthor } from 'lib/proposals/showRubricAnswersToAuthor';

import { RubricAnswersForm } from './components/RubricAnswersForm';
import { RubricDecision } from './components/RubricDecision';
Expand Down Expand Up @@ -33,8 +35,18 @@ export function RubricEvaluation({ proposal, isCurrent, evaluation, refreshPropo
[user?.id, !!evaluation?.draftRubricAnswers?.length]

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test app

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test app

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test apps

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Validate code

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test

React Hook useMemo has a missing dependency: 'evaluation?.draftRubricAnswers'. Either include it or remove the dependency array

Check warning on line 35 in components/proposals/ProposalPage/components/ProposalEvaluations/components/Review/components/RubricEvaluation/RubricEvaluation.tsx

View workflow job for this annotation

GitHub Actions / Test

React Hook useMemo has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked
);

const canViewRubricAnswers = isAdmin || canAnswerRubric || evaluation.shareReviews;
const isAuthor = proposal.authors.some((a) => a.userId === user?.id);

const authorCanViewFailedEvaluationResults = showRubricAnswersToAuthor({
isAuthor,
evaluationType: evaluation.type as ProposalEvaluationType,
isCurrentEvaluationStep: isCurrent,
proposalFailed: isCurrent && evaluation.result === 'fail',
showAuthorResultsOnRubricFail: !!evaluation.showAuthorResultsOnRubricFail
});

const canViewRubricAnswers =
isAdmin || canAnswerRubric || evaluation.shareReviews || authorCanViewFailedEvaluationResults;
async function onSubmitEvaluation({ isDraft }: { isDraft: boolean }) {
if (proposal) {
await refreshProposal();
Expand All @@ -58,7 +70,7 @@ export function RubricEvaluation({ proposal, isCurrent, evaluation, refreshPropo
return (
<>
<Alert severity='info'>
{evaluation.shareReviews
{evaluation.shareReviews || authorCanViewFailedEvaluationResults
? 'Evaluation results are anonymous'
: evaluation.isReviewer
? 'Evaluation results are only visible to Reviewers'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { WorkflowEvaluationJson } from '@charmverse/core/proposals';
import { ExpandMore } from '@mui/icons-material';
import { Accordion, AccordionSummary, Typography, AccordionDetails, Box } from '@mui/material';
import { Stack } from '@mui/system';
import { useState } from 'react';
import type { UseFormSetValue } from 'react-hook-form';

import type { EvaluationStepFormValues } from '../form';

import { EvaluationAppealSettings } from './EvaluationAppealSettings';
import { EvaluationRequiredReviews } from './EvaluationRequiredReviews';
import { ShowRubricResults } from './ShowRubricResults';
import { StepActionButtonLabel } from './StepActionButtonLabel';
import { StepFailReasonSelect } from './StepFailReasonSelect';

export function EvaluationAdvancedSettingsAccordion({
formValues,
setValue
}: {
formValues: EvaluationStepFormValues;
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false);
const actionLabels = formValues?.actionLabels as WorkflowEvaluationJson['actionLabels'];
const declineReasons = (formValues?.declineReasons as WorkflowEvaluationJson['declineReasons']) ?? [];
return (
<Box>
<Accordion
style={{ marginBottom: '20px' }}
expanded={isAdvancedSettingsOpen}
onChange={() => setIsAdvancedSettingsOpen(!isAdvancedSettingsOpen)}
>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography>Advanced settings</Typography>
</AccordionSummary>
<AccordionDetails>
<Stack gap={2}>
<StepActionButtonLabel type={formValues.type} setValue={setValue} actionLabels={actionLabels} />
{formValues.type === 'rubric' && <ShowRubricResults formValues={formValues} setValue={setValue} />}
{formValues.type === 'pass_fail' && (
<>
<EvaluationRequiredReviews requiredReviews={formValues.requiredReviews} setValue={setValue} />
<StepFailReasonSelect declineReasons={declineReasons} setValue={setValue} />
<EvaluationAppealSettings formValues={formValues} setValue={setValue} />
</>
)}
</Stack>
</AccordionDetails>
</Accordion>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Box, Switch, TextField, Typography } from '@mui/material';
import { Stack } from '@mui/system';
import type { UseFormSetValue } from 'react-hook-form';

import FieldLabel from 'components/common/form/FieldLabel';

import type { EvaluationStepFormValues } from '../form';

export function EvaluationAppealSettings({
setValue,
formValues
}: {
formValues: EvaluationStepFormValues;
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
const { appealable, appealRequiredReviews, finalStep } = formValues;
return (
<Stack gap={1}>
<Box>
<FieldLabel>Priority Step</FieldLabel>
<Stack flexDirection='row' justifyContent='space-between' alignItems='center'>
<Typography color='textSecondary' variant='body2'>
If this Step passes, the entire proposal passes
</Typography>
<Switch
checked={!!finalStep}
onChange={(e) => {
const checked = e.target.checked;
setValue('finalStep', checked);
setValue('appealRequiredReviews', null);
setValue('appealable', false);
}}
/>
</Stack>
</Box>
<Box>
<FieldLabel>Appeal</FieldLabel>
<Stack flexDirection='row' justifyContent='space-between' alignItems='center'>
<Typography color='textSecondary' variant='body2'>
Authors can appeal the decision. The results of the appeal are final.
</Typography>
<Switch
checked={!!appealable}
onChange={(e) => {
const checked = e.target.checked;
setValue('appealRequiredReviews', !checked ? null : 1);
setValue('finalStep', null);
setValue('appealable', checked);
}}
/>
</Stack>
</Box>
{appealable && (
<Box>
<FieldLabel>Appeal required reviews</FieldLabel>
<TextField
disabled={!appealable}
type='number'
onChange={(e) => {
setValue('appealRequiredReviews', Math.max(1, Number(e.target.value)));
}}
fullWidth
value={appealRequiredReviews ?? ''}
/>
</Box>
)}
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { WorkflowEvaluationJson } from '@charmverse/core/proposals';
import { TextField, Box } from '@mui/material';
import type { UseFormSetValue } from 'react-hook-form';

import FieldLabel from 'components/common/form/FieldLabel';

import type { EvaluationStepFormValues } from '../form';

export function EvaluationRequiredReviews({
setValue,
requiredReviews
}: {
requiredReviews: WorkflowEvaluationJson['requiredReviews'];
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
return (
<Box className='octo-propertyrow'>
<FieldLabel>Required reviews</FieldLabel>
<TextField
type='number'
onChange={(e) => {
setValue('requiredReviews', Math.max(1, Number(e.target.value)));
}}
fullWidth
value={requiredReviews}
/>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Box, Stack, Switch, Typography } from '@mui/material';
import type { UseFormSetValue } from 'react-hook-form';

import FieldLabel from 'components/common/form/FieldLabel';

import type { EvaluationStepFormValues } from '../form';

export function ShowRubricResults({
setValue,
formValues
}: {
formValues: EvaluationStepFormValues;
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
return (
<Box className='octo-propertyrow'>
<FieldLabel>Show Author Results on Rubric Fail</FieldLabel>
<Box display='flex' alignItems='center'>
<Switch
checked={!!formValues.showAuthorResultsOnRubricFail}
onChange={(ev) => setValue('showAuthorResultsOnRubricFail', ev.target.checked)}
/>
<Typography variant='body2' color='textSecondary'>
If enabled, authors can see their evaluation results when the evaluation fails
</Typography>
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ProposalEvaluationType } from '@charmverse/core/prisma-client';
import type { WorkflowEvaluationJson } from '@charmverse/core/proposals';
import { TextField, Box } from '@mui/material';
import { customLabelEvaluationTypes } from '@root/lib/proposals/getActionButtonLabels';
import type { UseFormSetValue } from 'react-hook-form';

import FieldLabel from 'components/common/form/FieldLabel';

import type { EvaluationStepFormValues } from '../form';

export function StepActionButtonLabel({
type,
setValue,
actionLabels
}: {
type: ProposalEvaluationType;
actionLabels: WorkflowEvaluationJson['actionLabels'];
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
return customLabelEvaluationTypes.includes(type) ? (
<Box className='octo-propertyrow'>
<FieldLabel>Decision Labels</FieldLabel>
<TextField
placeholder='Pass'
onChange={(e) => {
setValue('actionLabels', {
...actionLabels,
approve: e.target.value
});
}}
fullWidth
value={actionLabels?.approve}
sx={{
mb: 1
}}
/>
<TextField
placeholder='Decline'
onChange={(e) => {
setValue('actionLabels', {
...actionLabels,
reject: e.target.value
});
}}
fullWidth
value={actionLabels?.reject}
/>
</Box>
) : null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import DeleteIcon from '@mui/icons-material/DeleteOutlineOutlined';
import { TextField, Typography, IconButton, Box } from '@mui/material';
import { Stack } from '@mui/system';
import { useState } from 'react';
import type { UseFormSetValue } from 'react-hook-form';

import { Button } from 'components/common/Button';
import FieldLabel from 'components/common/form/FieldLabel';

import type { EvaluationStepFormValues } from '../form';

export function StepFailReasonSelect({
setValue,
declineReasons
}: {
declineReasons: string[];
setValue: UseFormSetValue<EvaluationStepFormValues>;
}) {
const [declineReason, setDeclineReason] = useState('');
const isDuplicate = declineReasons.includes(declineReason);

function addDeclineReason() {
setValue('declineReasons', [...declineReasons, declineReason.trim()]);
setDeclineReason('');
}

return (
<Box className='octo-propertyrow'>
<FieldLabel>Decline reasons</FieldLabel>
<Stack direction='row' gap={1} mb={1.5} alignItems='center'>
<TextField
value={declineReason}
placeholder='Add a decline reason'
variant='outlined'
sx={{ flexGrow: 1 }}
onChange={(e) => {
setDeclineReason(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
addDeclineReason();
}
}}
/>
<Button
variant='outlined'
disabledTooltip={isDuplicate ? 'This decline reason already exists' : ''}
disabled={isDuplicate || declineReason.length === 0}
onClick={addDeclineReason}
>
Add
</Button>
</Stack>
<Stack gap={0.5}>
{declineReasons.length === 0 && (
<Typography variant='body2' color='textSecondary'>
No decline reasons added
</Typography>
)}
{declineReasons.map((reason) => (
<Stack key={reason} direction='row' gap={1} justifyContent='space-between' alignItems='center'>
<Typography variant='body2'>{reason}</Typography>
<IconButton
size='small'
onClick={() => {
setValue(
'declineReasons',
declineReasons.filter((_reason) => reason !== _reason)
);
}}
>
<DeleteIcon color='error' fontSize='small' />
</IconButton>
</Stack>
))}
</Stack>
</Box>
);
}
Loading

0 comments on commit ee0b949

Please sign in to comment.