From 11cea42b275e761a7b1292abe5663bfec263afda Mon Sep 17 00:00:00 2001
From: Marek Vesely <xvesely4@fi.muni.cz>
Date: Tue, 21 May 2024 13:48:55 +0200
Subject: [PATCH] fix: role exercise team count

---
 codegen/gql/fragments/Definition.graphql      |  3 ++
 codegen/gql/fragments/Role.graphql            |  4 ++
 .../src/pages/instructor/+exerciseCreator.tsx | 43 ++++++++++++++++---
 graphql/fragments/Definition.generated.ts     |  4 +-
 graphql/fragments/Role.generated.ts           |  8 ++++
 graphql/queries/GetDefinition.generated.ts    |  4 +-
 graphql/queries/GetDefinitions.generated.ts   |  4 +-
 7 files changed, 57 insertions(+), 13 deletions(-)
 create mode 100644 codegen/gql/fragments/Role.graphql
 create mode 100644 graphql/fragments/Role.generated.ts

diff --git a/codegen/gql/fragments/Definition.graphql b/codegen/gql/fragments/Definition.graphql
index 6d935972c..fa99b9a8f 100644
--- a/codegen/gql/fragments/Definition.graphql
+++ b/codegen/gql/fragments/Definition.graphql
@@ -5,4 +5,7 @@ fragment Definition on DefinitionType {
   channels {
     ...Channel
   }
+  roles {
+    ...Role
+  }
 }
diff --git a/codegen/gql/fragments/Role.graphql b/codegen/gql/fragments/Role.graphql
new file mode 100644
index 000000000..a7e5e8c7b
--- /dev/null
+++ b/codegen/gql/fragments/Role.graphql
@@ -0,0 +1,4 @@
+fragment Role on DefinitionRoleType {
+  id
+  name
+}
diff --git a/frontend/src/pages/instructor/+exerciseCreator.tsx b/frontend/src/pages/instructor/+exerciseCreator.tsx
index be896a714..0f6d6380f 100644
--- a/frontend/src/pages/instructor/+exerciseCreator.tsx
+++ b/frontend/src/pages/instructor/+exerciseCreator.tsx
@@ -11,14 +11,14 @@ import {
   Label,
   NumericInput,
 } from '@blueprintjs/core'
+import type { Definition } from '@inject/graphql/fragments/Definition.generated'
 import { useCreateExercises } from '@inject/graphql/mutations/CreateExercise.generated'
 import { useGetDefinitions } from '@inject/graphql/queries/GetDefinitions.generated'
 import { GetExercisesDocument } from '@inject/graphql/queries/GetExercises.generated'
 import Box from '@inject/shared/components/Box'
 import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext'
-import { useCallback, useMemo, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
 
-const TEAMS_MIN = 1
 const TEAMS_MAX = 20
 
 const ExerciseCreator = () => {
@@ -32,7 +32,18 @@ const ExerciseCreator = () => {
     })
   const { notify } = useNotifyContext()
   const [definitionId, setDefinitionId] = useState<string>()
-  const [count, setCount] = useState<undefined | number>(undefined)
+  const [definition, setDefinition] = useState<Definition>()
+  const [count, setCount] = useState<undefined | number>()
+
+  useEffect(() => {
+    const definition = definitionData?.definitions?.find(
+      definition => definition?.id === definitionId
+    )
+    setCount(definition?.roles.length || 1)
+    if (definition) {
+      setDefinition(definition)
+    }
+  }, [definitionData?.definitions, definitionId])
 
   const handleSubmit = useCallback(() => {
     addExercise({
@@ -104,17 +115,35 @@ const ExerciseCreator = () => {
           </Label>
 
           <Label style={{ width: '100%' }}>
-            Team count
+            Number of teams
+            {definition?.roles.length ? (
+              <>
+                {' '}
+                <span
+                  className={Classes.TEXT_MUTED}
+                >{`(multiple of the number of roles - ${definition.roles.length})`}</span>
+              </>
+            ) : (
+              ''
+            )}
             <NumericInput
               fill
               value={count}
+              disabled={!definition}
+              stepSize={definition?.roles.length || 1}
+              minorStepSize={null}
+              majorStepSize={null}
               onValueChange={value => {
-                if (value >= TEAMS_MIN && value <= TEAMS_MAX) {
+                if (
+                  value >= (definition?.roles.length || 1) &&
+                  value <= Math.max(TEAMS_MAX, definition?.roles.length || 1) &&
+                  value % (definition?.roles.length || 1) === 0
+                ) {
                   setCount(value)
                 }
               }}
-              min={TEAMS_MIN}
-              max={TEAMS_MAX}
+              min={definition?.roles.length || 1}
+              max={Math.max(TEAMS_MAX, definition?.roles.length || 1)}
               title='Number of teams'
               placeholder='Number of teams'
               style={{ margin: 0 }}
diff --git a/graphql/fragments/Definition.generated.ts b/graphql/fragments/Definition.generated.ts
index 1d4614871..102d2fac2 100644
--- a/graphql/fragments/Definition.generated.ts
+++ b/graphql/fragments/Definition.generated.ts
@@ -3,6 +3,6 @@
 import type * as _Types from '../types';
 
 import type { DocumentNode } from 'graphql';
-export type Definition = { id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }> };
+export type Definition = { id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }>, roles: Array<{ id: string, name: string }> };
 
-export const Definition = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}}]} as unknown as DocumentNode;
\ No newline at end of file
+export const Definition = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}},{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Role"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Role"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionRoleType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode;
\ No newline at end of file
diff --git a/graphql/fragments/Role.generated.ts b/graphql/fragments/Role.generated.ts
new file mode 100644
index 000000000..3d937e977
--- /dev/null
+++ b/graphql/fragments/Role.generated.ts
@@ -0,0 +1,8 @@
+/* eslint-disable */
+//@ts-nocheck
+import type * as _Types from '../types';
+
+import type { DocumentNode } from 'graphql';
+export type Role = { id: string, name: string };
+
+export const Role = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Role"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionRoleType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode;
\ No newline at end of file
diff --git a/graphql/queries/GetDefinition.generated.ts b/graphql/queries/GetDefinition.generated.ts
index 5c08ebdd7..9e6034bc2 100644
--- a/graphql/queries/GetDefinition.generated.ts
+++ b/graphql/queries/GetDefinition.generated.ts
@@ -10,10 +10,10 @@ export type GetDefinitionVariables = _Types.Exact<{
 }>;
 
 
-export type GetDefinition = { definition: { id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }> } | null };
+export type GetDefinition = { definition: { id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }>, roles: Array<{ id: string, name: string }> } | null };
 
 
-export const GetDefinitionDocument = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDefinition"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"definitionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"definition"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"definitionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"definitionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Definition"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}}]} as unknown as DocumentNode;
+export const GetDefinitionDocument = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDefinition"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"definitionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"definition"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"definitionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"definitionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Definition"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}},{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Role"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Role"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionRoleType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode;
 
 /**
  * __useGetDefinition__
diff --git a/graphql/queries/GetDefinitions.generated.ts b/graphql/queries/GetDefinitions.generated.ts
index 2a6921a60..6a82dd44f 100644
--- a/graphql/queries/GetDefinitions.generated.ts
+++ b/graphql/queries/GetDefinitions.generated.ts
@@ -8,10 +8,10 @@ const defaultOptions = {} as const;
 export type GetDefinitionsVariables = _Types.Exact<{ [key: string]: never; }>;
 
 
-export type GetDefinitions = { definitions: Array<{ id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }> } | null> | null };
+export type GetDefinitions = { definitions: Array<{ id: string, name: string, version: string, channels: Array<{ id: string, name: string, type: _Types.ChannelType, readReceipt: Array<{ readReceipt: string | null, teamId: string }> }>, roles: Array<{ id: string, name: string }> } | null> | null };
 
 
-export const GetDefinitionsDocument = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"definitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Definition"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}}]} as unknown as DocumentNode;
+export const GetDefinitionsDocument = /*#__PURE__*/ {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"definitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Definition"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Definition"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"channels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Channel"}}]}},{"kind":"Field","name":{"kind":"Name","value":"roles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"Role"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Channel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionChannelType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"readReceipt"},"directives":[{"kind":"Directive","name":{"kind":"Name","value":"client"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readReceipt"}},{"kind":"Field","name":{"kind":"Name","value":"teamId"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"Role"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"DefinitionRoleType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode;
 
 /**
  * __useGetDefinitions__
-- 
GitLab