Skip to content

Commit

Permalink
Correctly implement a subscription for pin updates, and removed the m…
Browse files Browse the repository at this point in the history
…anual refresh button
  • Loading branch information
Ben Lerner committed Nov 18, 2023
1 parent 05d0694 commit 637046b
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 22 deletions.
22 changes: 22 additions & 0 deletions app/graphql/subscriptions/pin_was_updated.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Subscriptions
class PinWasUpdated < Subscriptions::BaseSubscription
argument :exam_id, ID, required: true, loads: Types::ExamType

field :registration, Types::RegistrationType, null: false

def authorized?(exam:)
return true if exam.proctors.exists? context[:current_user].id
return true if exam.professors.exists? context[:current_user].id

raise GraphQL::ExecutionError, 'You do not have permission.'
end

def update(exam:)
{
registration: object,
}
end
end
end
2 changes: 2 additions & 0 deletions app/graphql/types/subscription_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ class SubscriptionType < GraphQL::Schema::Object

field :room_announcement_received, subscription: Subscriptions::RoomAnnouncementReceived
field :room_announcement_was_sent, subscription: Subscriptions::RoomAnnouncementWasSent

field :pin_was_updated, subscription: Subscriptions::PinWasUpdated
end
end
5 changes: 5 additions & 0 deletions app/models/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ def validate_pin!(pin)
self.login_attempt_count += 1
end
save!
HourglassSchema.subscriptions.trigger(
:pin_was_updated,
{ exam_id: HourglassSchema.id_from_object(exam, Types::ExamType, nil) },
self,
)

pin_validated
end
Expand Down
50 changes: 30 additions & 20 deletions app/packs/components/workflows/proctor/exams/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, {
useState,
useContext,
useMemo,
useEffect,
useCallback,
useRef,
Suspense,
Expand Down Expand Up @@ -35,7 +34,6 @@ import {
MdMessage,
MdSend,
MdPeople,
MdRefresh,
} from 'react-icons/md';
import Loading from '@hourglass/common/loading';
import { AlertContext } from '@hourglass/common/alerts';
Expand All @@ -45,7 +43,6 @@ import {
SelectOption,
SelectOptions,
useMutationWithDefaults,
useRefresher,
} from '@hourglass/common/helpers';
import { GiBugleCall } from 'react-icons/gi';
import { DateTime } from 'luxon';
Expand All @@ -60,7 +57,6 @@ import {
useSubscription,
useLazyLoadQuery,
usePaginationFragment,
useRefetchableFragment,
} from 'react-relay';
import ErrorBoundary from '@hourglass/common/boundary';

Expand Down Expand Up @@ -1797,19 +1793,37 @@ const makeRegistrationFilter = (
})
);

const pinWasUpdatedSubscriptionSpec = graphql`
subscription examsPinWasUpdatedSubscription($examId: ID!) {
pinWasUpdated(examId: $examId) {
registration {
id
currentPin
pinValidated
}
}
}
`;

const ShowCurrentPins: React.FC<{
enabled: boolean;
recipients: SplitRecipients;
recipientOptions: RecipientOptions;
exam: exams_pins$key;
}> = (props) => {
const { recipients, recipientOptions, exam } = props;
const {
recipients,
recipientOptions,
exam,
enabled,
} = props;
const [showing, setShowing] = useState(false);
const [refresh, forceRefresh] = useRefresher();
return (
<>
<Button
variant="info"
className="float-right"
disabled={!enabled}
onClick={() => setShowing(true)}
>
Show current PINs
Expand All @@ -1823,20 +1837,12 @@ const ShowCurrentPins: React.FC<{
>
<Modal.Header closeButton>
<Modal.Title>
<Button
variant="info"
className="mr-4"
onClick={forceRefresh}
>
<Icon I={MdRefresh} />
</Button>
Current PINs for students
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Suspense fallback={<p>Loading...</p>}>
<ShowCurrentPinsTable
refresh={refresh}
recipients={recipients}
recipientOptions={recipientOptions}
exam={exam}
Expand All @@ -1857,20 +1863,18 @@ const ShowCurrentPins: React.FC<{
};

const ShowCurrentPinsTable: React.FC<{
refresh,
recipients: SplitRecipients,
recipientOptions: RecipientOptions,
exam: exams_pins$key,
}> = (props) => {
const {
refresh,
recipients,
recipientOptions,
exam,
} = props;
const [filter, setFilter] = useState<MessageFilterOption[]>(undefined);
const filterBy = useMemo(() => makeRegistrationFilter(recipients, filter), [filter]);
const [res, refetch] = useRefetchableFragment(
const res = useFragment<exams_pins$key>(
graphql`
fragment exams_pins on Exam
@refetchable(queryName: "RegistrationPinRefetchQuery") {
Expand All @@ -1883,9 +1887,6 @@ const ShowCurrentPinsTable: React.FC<{
`,
exam,
);
useEffect(() => {
refetch({}, { fetchPolicy: 'network-only' });
}, [refresh]);
const { registrations } = res;
const regsById = new Map(registrations.map((r) => [r.id, r]));
let all: Array<Recipient> = [];
Expand Down Expand Up @@ -1989,6 +1990,7 @@ const ProctoringRecipients: React.FC<{
id
displayName
}
currentPin
}
rooms {
id
Expand All @@ -1998,6 +2000,12 @@ const ProctoringRecipients: React.FC<{
`,
exam,
);
useSubscription(useMemo(() => ({
subscription: pinWasUpdatedSubscriptionSpec,
variables: {
examId: res.id,
},
}), [res.id]));
const students: Recipient[] = [];
const studentsByRoom: Record<RoomAnnouncement['id'], Record<DirectMessage['registration']['id'], boolean>> = {};
const versionsByRoom: Record<RoomAnnouncement['id'], Record<VersionAnnouncement['id'], boolean>> = {};
Expand All @@ -2019,6 +2027,7 @@ const ProctoringRecipients: React.FC<{
roomsByVersion[reg.examVersion.id] = roomsByVersion[reg.examVersion.id] ?? {};
roomsByVersion[reg.examVersion.id][reg.room?.id] = true;
});
const anyPins = res.registrations.some((r) => (r.currentPin ?? '') !== '');
const sortByName = (a, b) => a.name.localeCompare(b.name);
const recipients: SplitRecipients = useMemo(() => ({
versions: res.examVersions.edges.map(({ node: ev }) => {
Expand Down Expand Up @@ -2098,6 +2107,7 @@ const ProctoringRecipients: React.FC<{
<h1>
{res.name}
<ShowCurrentPins
enabled={anyPins}
recipients={recipients}
recipientOptions={recipientOptions}
exam={res}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {
useContext,
Suspense,
useEffect,
useMemo,
} from 'react';
import {
useParams,
Expand All @@ -16,7 +17,12 @@ import {
import { DateTime, LocaleOptions } from 'luxon';
import { Container, Button, Table } from 'react-bootstrap';
import { BsListCheck } from 'react-icons/bs';
import { graphql, useLazyLoadQuery, useMutation } from 'react-relay';
import {
graphql,
useLazyLoadQuery,
useMutation,
useSubscription,
} from 'react-relay';
import {
AnswersState,
} from '@student/exams/show/types';
Expand Down Expand Up @@ -55,6 +61,18 @@ const ExamSubmissions: React.FC = () => (
</ErrorBoundary>
);

const pinWasUpdatedSubscriptionSpec = graphql`
subscription submissionsPinWasUpdatedSubscription($examId: ID!) {
pinWasUpdated(examId: $examId) {
registration {
id
currentPin
pinValidated
}
}
}
`;

const ExamSubmissionsQuery: React.FC = () => {
const { examId } = useParams<{ examId: string }>();
const queryData = useLazyLoadQuery<submissionsAllQuery>(
Expand All @@ -74,12 +92,19 @@ const ExamSubmissionsQuery: React.FC = () => {
endTime
effectiveEndTime
currentPin
pinValidated
}
}
}
`,
{ examId },
);
useSubscription(useMemo(() => ({
subscription: pinWasUpdatedSubscriptionSpec,
variables: {
examId,
},
}), [examId]));
const [showModal, setShowModal] = useState(false);
const openModal = useCallback(() => setShowModal(true), []);
const closeModal = useCallback(() => setShowModal(false), []);
Expand Down Expand Up @@ -296,7 +321,13 @@ const ExamSubmissionsQuery: React.FC = () => {
{reg.user.displayName}
</Link>
</td>
{anyPins && <td>{reg.currentPin ?? 'none required'}</td>}
{anyPins && (
<td>
{reg.pinValidated
? 'Already validated'
: (reg.currentPin ?? 'none required')}
</td>
)}
</tr>
))}
</tbody>
Expand Down

0 comments on commit 637046b

Please sign in to comment.