Skip to content

Commit ebb56dc

Browse files
authored
Merge pull request #10906 from neinteractiveliterature/nbudin/issue10160
Selectable signup round automation actions
2 parents 4c3519f + 2c7d2fb commit ebb56dc

21 files changed

Lines changed: 271 additions & 31 deletions

app/graphql/graphql_operations_generated.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Types::SignupRoundAutomationAction < Types::BaseEnum
2+
description "An action to take when a signup round opens."
3+
4+
value "EXECUTE_RANKED_CHOICE",
5+
"Execute any pending ranked choices as allowed by this signup round",
6+
value: "execute_ranked_choice"
7+
end

app/graphql/types/signup_round_input_type.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
class Types::SignupRoundInputType < Types::BaseInputObject
22
description "An input for creating or modifying SignupRounds."
33

4+
argument :automation_action, Types::SignupRoundAutomationAction, required: false, camelize: false do
5+
description "The action to take when this signup round opens."
6+
end
47
argument :maximum_event_signups,
58
String,
69
required: false,

app/graphql/types/signup_round_type.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ class SignupRoundType < Types::BaseObject
1010
signup automation.
1111
MARKDOWN
1212

13+
field :automation_action, Types::SignupRoundAutomationAction, null: true do
14+
description "The action to take when this signup round opens."
15+
end
1316
field :convention, Types::ConventionType, null: false, description: "The convention this SignupRound is in."
1417
field :created_at, Types::DateType, null: false, description: "When this SignupRound was first created."
1518
field :executed_at, Types::DateType do

app/javascript/SignupRoundsAdmin/CreateNewSignupRoundForm.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ErrorDisplay, FormGroupWithLabel } from '@neinteractiveliterature/litfo
88
import DateTimeInput from '../BuiltInFormControls/DateTimeInput';
99
import MaximumEventSignupsInput from './MaximumEventSignupsInput';
1010
import { ApolloError } from '@apollo/client';
11+
import { SignupAutomationMode, SignupRoundAutomationAction } from 'graphqlTypes.generated';
1112

1213
type CreateNewSignupRoundFormProps = {
1314
onCancel: () => void;
@@ -33,6 +34,15 @@ export default function CreateNewSignupRoundForm({ onCancel }: CreateNewSignupRo
3334
<fetcher.Form action="/signup_rounds" method="POST">
3435
<h6>{t('signups.signupRounds.addNewSignupRound')}</h6>
3536
<input type="hidden" name="convention_id" value={convention?.id} />
37+
<input
38+
type="hidden"
39+
name="automation_action"
40+
value={
41+
convention?.signup_automation_mode === SignupAutomationMode.RankedChoice
42+
? SignupRoundAutomationAction.ExecuteRankedChoice
43+
: ''
44+
}
45+
/>
3646

3747
<FormGroupWithLabel label={t('signups.signupRounds.start')}>
3848
{(id) => (

app/javascript/SignupRoundsAdmin/SignupRoundCard.tsx

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useContext, useState, useMemo } from 'react';
2-
import { ParsedSignupRound } from '../SignupRoundUtils';
3-
import { RankedChoiceOrder, SignupAutomationMode } from '../graphqlTypes.generated';
2+
import { MaximumEventSignupsValue, ParsedSignupRound } from '../SignupRoundUtils';
3+
import { RankedChoiceOrder, SignupAutomationMode, SignupRoundAutomationAction } from '../graphqlTypes.generated';
44
import { useTranslation } from 'react-i18next';
55
import MaximumEventSignupsInput from './MaximumEventSignupsInput';
66
import {
@@ -17,6 +17,18 @@ import { DateTime } from 'luxon';
1717
import { Link, useFetcher } from 'react-router';
1818
import { describeSignupRound } from './describeSignupRound';
1919

20+
function maximumEventSignupsAsNumber(value: MaximumEventSignupsValue): number {
21+
switch (value) {
22+
case 'not_now':
23+
case 'not_yet':
24+
return 0;
25+
case 'unlimited':
26+
return Infinity;
27+
default:
28+
return value;
29+
}
30+
}
31+
2032
type SignupRoundCardProps = {
2133
rounds: ParsedSignupRound<SignupRoundsAdminQueryData['convention']['signup_rounds'][number]>[];
2234
roundIndex: number;
@@ -36,24 +48,38 @@ function SignupRoundCard({ rounds, roundIndex }: SignupRoundCardProps) {
3648
const unsavedChanges = useMemo(
3749
() =>
3850
editingRound.maximum_event_signups !== round.maximum_event_signups ||
51+
editingRound.automation_action != round.automation_action ||
3952
editingRound.ranked_choice_order !== round.ranked_choice_order,
4053
[editingRound, round],
4154
);
4255

43-
const prevRound = roundIndex > 0 ? rounds[roundIndex - 1] : undefined;
56+
const rankedChoiceSignupsAsNumbers = useMemo(() => {
57+
return rounds.map((round) => {
58+
if (round.automation_action === SignupRoundAutomationAction.ExecuteRankedChoice) {
59+
return maximumEventSignupsAsNumber(round.maximum_event_signups ?? 'not_yet');
60+
} else {
61+
return undefined;
62+
}
63+
});
64+
}, [rounds]);
4465
const roundDescription = useMemo(() => describeSignupRound(rounds, roundIndex, t), [rounds, roundIndex, t]);
4566
const increasedMaximumSignups = useMemo(() => {
46-
return (
47-
prevRound &&
48-
((round.maximum_event_signups === 'unlimited' && prevRound.maximum_event_signups !== 'unlimited') ||
49-
(typeof round.maximum_event_signups === 'number' &&
50-
typeof prevRound.maximum_event_signups === 'number' &&
51-
round.maximum_event_signups > prevRound.maximum_event_signups) ||
52-
(round.maximum_event_signups !== 'not_now' &&
53-
round.maximum_event_signups !== 'not_yet' &&
54-
(prevRound.maximum_event_signups === 'not_now' || prevRound.maximum_event_signups === 'not_yet')))
55-
);
56-
}, [prevRound, round.maximum_event_signups]);
67+
if (round.maximum_event_signups == null) {
68+
return false;
69+
}
70+
71+
for (let i = roundIndex - 1; i > 0; i--) {
72+
const roundSignupsAsNumber = rankedChoiceSignupsAsNumbers[i];
73+
if (
74+
roundSignupsAsNumber != null &&
75+
roundSignupsAsNumber >= maximumEventSignupsAsNumber(round.maximum_event_signups)
76+
) {
77+
return false;
78+
}
79+
}
80+
81+
return true;
82+
}, [rankedChoiceSignupsAsNumbers, roundIndex, round.maximum_event_signups]);
5783

5884
return (
5985
<fetcher.Form action={`./${round.id}`} method="PATCH" preventScrollReset>
@@ -82,6 +108,23 @@ function SignupRoundCard({ rounds, roundIndex }: SignupRoundCardProps) {
82108
</div>
83109
{signupAutomationMode === SignupAutomationMode.RankedChoice && (
84110
<>
111+
<BootstrapFormSelect
112+
label={t('signups.signupRounds.automationActionLabel')}
113+
name="automation_action"
114+
value={editingRound.automation_action ?? undefined}
115+
onValueChange={(newValue) =>
116+
setEditingRound((prevEditingRound) => {
117+
const newAction =
118+
newValue === SignupRoundAutomationAction.ExecuteRankedChoice ? newValue : undefined;
119+
return { ...prevEditingRound, automation_action: newAction };
120+
})
121+
}
122+
>
123+
<option value="">{t('signups.signupRounds.automationActions.none')}</option>
124+
<option value={SignupRoundAutomationAction.ExecuteRankedChoice}>
125+
{t('signups.signupRounds.automationActions.executeRankedChoice')}
126+
</option>
127+
</BootstrapFormSelect>
85128
{increasedMaximumSignups && (
86129
<BootstrapFormSelect
87130
label={t('signups.rankedChoiceOrderLabel')}

app/javascript/SignupRoundsAdmin/buildSignupRoundInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ParsedSignupRound } from '../SignupRoundUtils';
2-
import { RankedChoiceOrder, SignupRoundInput } from '../graphqlTypes.generated';
2+
import { RankedChoiceOrder, SignupRoundAutomationAction, SignupRoundInput } from '../graphqlTypes.generated';
33
import { SignupRoundsAdminQueryData } from './queries.generated';
44

55
export function buildSignupRoundInput(
@@ -16,8 +16,10 @@ export function buildSignupRoundInput(
1616
}
1717

1818
export function buildSignupRoundInputFromFormData(formData: FormData): SignupRoundInput {
19+
const automationActionValue = formData.get('automation_action')?.toString();
1920
return {
2021
maximum_event_signups: formData.get('maximum_event_signups')?.toString(),
22+
automation_action: (automationActionValue || null) as SignupRoundAutomationAction | null,
2123
ranked_choice_order: formData.get('ranked_choice_order')?.toString() as RankedChoiceOrder | undefined,
2224
start: formData.get('start')?.toString(),
2325
};

app/javascript/SignupRoundsAdmin/mutations.generated.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ export type CreateSignupRoundMutationVariables = Types.Exact<{
88
}>;
99

1010

11-
export type CreateSignupRoundMutationData = { __typename: 'Mutation', createSignupRound: { __typename: 'CreateSignupRoundPayload', signup_round: { __typename: 'SignupRound', id: string, maximum_event_signups: string, ranked_choice_order?: Types.RankedChoiceOrder | null, start?: string | null, executed_at?: string | null } } };
11+
export type CreateSignupRoundMutationData = { __typename: 'Mutation', createSignupRound: { __typename: 'CreateSignupRoundPayload', signup_round: { __typename: 'SignupRound', id: string, maximum_event_signups: string, automation_action?: Types.SignupRoundAutomationAction | null, ranked_choice_order?: Types.RankedChoiceOrder | null, start?: string | null, executed_at?: string | null } } };
1212

1313
export type UpdateSignupRoundMutationVariables = Types.Exact<{
1414
id: Types.Scalars['ID']['input'];
1515
signupRound: Types.SignupRoundInput;
1616
}>;
1717

1818

19-
export type UpdateSignupRoundMutationData = { __typename: 'Mutation', updateSignupRound: { __typename: 'UpdateSignupRoundPayload', signup_round: { __typename: 'SignupRound', id: string, maximum_event_signups: string, ranked_choice_order?: Types.RankedChoiceOrder | null, start?: string | null, executed_at?: string | null } } };
19+
export type UpdateSignupRoundMutationData = { __typename: 'Mutation', updateSignupRound: { __typename: 'UpdateSignupRoundPayload', signup_round: { __typename: 'SignupRound', id: string, maximum_event_signups: string, automation_action?: Types.SignupRoundAutomationAction | null, ranked_choice_order?: Types.RankedChoiceOrder | null, start?: string | null, executed_at?: string | null } } };
2020

2121
export type DeleteSignupRoundMutationVariables = Types.Exact<{
2222
id: Types.Scalars['ID']['input'];
@@ -26,6 +26,6 @@ export type DeleteSignupRoundMutationVariables = Types.Exact<{
2626
export type DeleteSignupRoundMutationData = { __typename: 'Mutation', deleteSignupRound: { __typename: 'DeleteSignupRoundPayload', clientMutationId?: string | null } };
2727

2828

29-
export const CreateSignupRoundDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSignupRound"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"conventionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRoundInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createSignupRound"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"conventionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"conventionId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"signupRound"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"signup_round"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SignupRoundFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SignupRoundFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRound"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"maximum_event_signups"}},{"kind":"Field","name":{"kind":"Name","value":"ranked_choice_order"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"executed_at"}}]}}]} as unknown as DocumentNode<CreateSignupRoundMutationData, CreateSignupRoundMutationVariables>;
30-
export const UpdateSignupRoundDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSignupRound"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRoundInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSignupRound"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"signupRound"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"signup_round"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SignupRoundFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SignupRoundFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRound"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"maximum_event_signups"}},{"kind":"Field","name":{"kind":"Name","value":"ranked_choice_order"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"executed_at"}}]}}]} as unknown as DocumentNode<UpdateSignupRoundMutationData, UpdateSignupRoundMutationVariables>;
29+
export const CreateSignupRoundDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSignupRound"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"conventionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRoundInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createSignupRound"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"conventionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"conventionId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"signupRound"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"signup_round"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SignupRoundFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SignupRoundFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRound"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"maximum_event_signups"}},{"kind":"Field","name":{"kind":"Name","value":"automation_action"}},{"kind":"Field","name":{"kind":"Name","value":"ranked_choice_order"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"executed_at"}}]}}]} as unknown as DocumentNode<CreateSignupRoundMutationData, CreateSignupRoundMutationVariables>;
30+
export const UpdateSignupRoundDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSignupRound"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRoundInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSignupRound"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"signupRound"},"value":{"kind":"Variable","name":{"kind":"Name","value":"signupRound"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"signup_round"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SignupRoundFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SignupRoundFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SignupRound"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"maximum_event_signups"}},{"kind":"Field","name":{"kind":"Name","value":"automation_action"}},{"kind":"Field","name":{"kind":"Name","value":"ranked_choice_order"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"executed_at"}}]}}]} as unknown as DocumentNode<UpdateSignupRoundMutationData, UpdateSignupRoundMutationVariables>;
3131
export const DeleteSignupRoundDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteSignupRound"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteSignupRound"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clientMutationId"}}]}}]}}]} as unknown as DocumentNode<DeleteSignupRoundMutationData, DeleteSignupRoundMutationVariables>;

0 commit comments

Comments
 (0)