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

Implement OneToManyDyadCensus and Anonymisation Interfaces #235

Draft
wants to merge 15 commits into
base: schema-8
Choose a base branch
from
Draft
1 change: 0 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const config = {
'*.test.*',
'public',
'.eslintrc.cjs',
'lib/protocol-validation', // TODO: remove this, and fix the errors
],
rules: {
'@next/next/no-img-element': 'off',
Expand Down
16 changes: 9 additions & 7 deletions actions/interviews.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use server';

import { createId } from '@paralleldrive/cuid2';
import { Prisma, type Interview, type Protocol } from '@prisma/client';
import { Prisma, type Interview } from '@prisma/client';
import { cookies } from 'next/headers';
import trackEvent from '~/lib/analytics';
import { safeRevalidateTag } from '~/lib/cache';
import type { InstalledProtocols } from '~/lib/interviewer/store';
import { type ProtocolWithAssets } from '~/lib/interviewer/ducks/modules/setServerSession';
import { type RootState } from '~/lib/interviewer/store';
import { formatExportableSessions } from '~/lib/network-exporters/formatters/formatExportableSessions';
import archive from '~/lib/network-exporters/formatters/session/archive';
import { generateOutputFiles } from '~/lib/network-exporters/formatters/session/generateOutputFiles';
Expand All @@ -17,14 +18,14 @@ import type {
ExportReturn,
FormattedSession,
} from '~/lib/network-exporters/utils/types';
import { type NcNetwork } from '~/lib/shared-consts';
import { getAppSetting } from '~/queries/appSettings';
import { getInterviewsForExport } from '~/queries/interviews';
import type {
CreateInterview,
DeleteInterviews,
SyncInterview,
} from '~/schemas/interviews';
import { type NcNetwork } from '~/schemas/network-canvas';
import { requireApiAuth } from '~/utils/auth';
import { prisma } from '~/utils/db';
import { ensureError } from '~/utils/ensureError';
Expand Down Expand Up @@ -91,21 +92,22 @@ export const prepareExportData = async (interviewIds: Interview['id'][]) => {

const interviewsSessions = await getInterviewsForExport(interviewIds);

const protocolsMap = new Map<string, Protocol>();
const protocolsMap = new Map<string, ProtocolWithAssets>();
interviewsSessions.forEach((session) => {
protocolsMap.set(session.protocol.hash, session.protocol);
});

const formattedProtocols: InstalledProtocols =
const formattedProtocols: RootState['installedProtocols'] =
Object.fromEntries(protocolsMap);

const formattedSessions = formatExportableSessions(interviewsSessions);

return { formattedSessions, formattedProtocols };
};

export const exportSessions = async (
formattedSessions: FormattedSession[],
formattedProtocols: InstalledProtocols,
formattedProtocols: RootState['installedProtocols'],
interviewIds: Interview['id'][],
exportOptions: ExportOptions,
): Promise<ExportReturn> => {
Expand Down Expand Up @@ -260,7 +262,7 @@ export async function syncInterview(data: SyncInterview) {
network,
currentStep,
stageMetadata,
lastUpdated: new Date(),
lastUpdated: new Date(), // TODO: this is present in the store - shouldn't we be using that value?
},
});

Expand Down
2 changes: 1 addition & 1 deletion actions/protocols.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use server';

import { type Protocol } from '@codaco/shared-consts';
import { Prisma } from '@prisma/client';
import { safeRevalidateTag } from 'lib/cache';
import { hash } from 'ohash';
import { type z } from 'zod';
import { type Protocol } from '~/lib/shared-consts';
import { getUTApi } from '~/lib/uploadthing-server-helpers';
import { protocolInsertSchema } from '~/schemas/protocol';
import { requireApiAuth } from '~/utils/auth';
Expand Down
2 changes: 1 addition & 1 deletion app/(interview)/interview/[interviewId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default async function Page({
return (
<>
{session && <FeedbackBanner />}
<InterviewShell interview={interview} syncInterview={syncInterview} />
<InterviewShell serverPayload={interview} syncInterview={syncInterview} />
</>
);
}
39 changes: 20 additions & 19 deletions app/(interview)/interview/_components/InterviewShell.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,70 @@
'use client';

import { parseAsInteger, useQueryState } from 'nuqs';
import { useEffect, useState } from 'react';
import { Provider } from 'react-redux';
import type { SyncInterviewType } from '~/actions/interviews';
import DialogManager from '~/lib/interviewer/components/DialogManager';
import ProtocolScreen from '~/lib/interviewer/containers/ProtocolScreen';
import {
SET_SERVER_SESSION,
type SetServerSessionAction,
} from '~/lib/interviewer/ducks/modules/setServerSession';
import { store } from '~/lib/interviewer/store';
import ServerSync from './ServerSync';
import { useEffect, useState } from 'react';
import { parseAsInteger, useQueryState } from 'nuqs';
import type { SyncInterviewType } from '~/actions/interviews';
import type { getInterviewById } from '~/queries/interviews';
import ServerSync from './ServerSync';

// The job of interview shell is to receive the server-side session and protocol
// and create a redux store with that data.
// Eventually it will handle syncing this data back.
const InterviewShell = ({
interview,
serverPayload,
syncInterview,
}: {
interview: Awaited<ReturnType<typeof getInterviewById>>;
serverPayload: Awaited<ReturnType<typeof getInterviewById>>;
syncInterview: SyncInterviewType;
}) => {
const [initialized, setInitialized] = useState(false);
const [currentStage, setCurrentStage] = useQueryState('step', parseAsInteger);

useEffect(() => {
if (initialized || !interview) {
if (initialized || !serverPayload) {
return;
}

const { protocol, ...serverSession } = interview;

// If we have a current stage in the URL bar, and it is different from the
// server session, set the server session to the current stage.
//
// If we don't have a current stage in the URL bar, set it to the server
// session, and set the URL bar to the server session.
if (currentStage === null) {
void setCurrentStage(serverSession.currentStep);
} else if (currentStage !== serverSession.currentStep) {
serverSession.currentStep = currentStage;
void setCurrentStage(serverPayload.currentStep);
} else if (currentStage !== serverPayload.currentStep) {
serverPayload.currentStep = currentStage;
}

// If there's no current stage in the URL bar, set it.
store.dispatch<SetServerSessionAction>({
type: SET_SERVER_SESSION,
payload: {
protocol,
session: serverSession,
},
payload: serverPayload,
});

setInitialized(true);
}, [initialized, setInitialized, currentStage, setCurrentStage, interview]);
}, [
initialized,
setInitialized,
currentStage,
setCurrentStage,
serverPayload,
]);

if (!initialized || !interview) {
if (!initialized || !serverPayload) {
return null;
}

return (
<Provider store={store}>
<ServerSync interviewId={interview.id} serverSync={syncInterview}>
<ServerSync interviewId={serverPayload.id} serverSync={syncInterview}>
<ProtocolScreen />
</ServerSync>
<DialogManager />
Expand Down
8 changes: 4 additions & 4 deletions app/dashboard/_components/InterviewsTable/Columns.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';

import { type ColumnDef } from '@tanstack/react-table';
import { Checkbox } from '~/components/ui/checkbox';
import Image from 'next/image';
import { DataTableColumnHeader } from '~/components/DataTable/ColumnHeader';
import { Progress } from '~/components/ui/progress';
import type { Stage } from '@codaco/shared-consts';
import { Badge } from '~/components/ui/badge';
import { Checkbox } from '~/components/ui/checkbox';
import { Progress } from '~/components/ui/progress';
import TimeAgo from '~/components/ui/TimeAgo';
import Image from 'next/image';
import type { Stage } from '~/lib/shared-consts';
import type { GetInterviewsReturnType } from '~/queries/interviews';

export const InterviewColumns = (): ColumnDef<
Expand Down
4 changes: 2 additions & 2 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import Paragraph from '~/components/ui/typography/Paragraph';

export default function NotFound() {
return (
<div className="bg-gray-100 flex h-screen flex-col items-center justify-center">
<FileWarning className="text-violet-700 mb-4 h-12 w-12" />
<div className="flex h-screen flex-col items-center justify-center bg-gray-100">
<FileWarning className="mb-4 h-12 w-12 text-violet-700" />
<Heading variant="h1">404</Heading>
<Paragraph variant="lead">Page not found.</Paragraph>
</div>
Expand Down
7 changes: 3 additions & 4 deletions components/BackgroundBlobs/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";
'use client';

import React from "react";
import useCanvas from "~/hooks/useCanvas";
import useCanvas from '~/hooks/useCanvas';

type CanvasProps = {
draw: (ctx: CanvasRenderingContext2D, time: number) => void;
Expand All @@ -13,7 +12,7 @@ const Canvas = (props: CanvasProps) => {
const { draw, predraw, postdraw } = props;
const canvasRef = useCanvas(draw, predraw, postdraw);

return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
return <canvas ref={canvasRef} className="h-full w-full" />;
};

export default Canvas;
2 changes: 1 addition & 1 deletion hooks/useCanvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useEffect } from 'react';
import { useEffect, useRef } from 'react';

const resizeCanvas = (
context: CanvasRenderingContext2D,
Expand Down
2 changes: 1 addition & 1 deletion lib/interviewer/components/Canvas/ConvexHull.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { entityAttributesProperty } from '@codaco/shared-consts';
import ConcaveMan from 'concaveman';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { entityAttributesProperty } from '~/lib/shared-consts';

const ConvexHull = ({
color = 'cat-color-seq-1',
Expand Down
2 changes: 1 addition & 1 deletion lib/interviewer/components/Canvas/ConvexHulls.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { entityAttributesProperty } from '@codaco/shared-consts';
import { findIndex } from 'es-toolkit/compat';
import PropTypes from 'prop-types';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import useResizeObserver from '~/hooks/useResizeObserver';
import { getCurrentStage } from '~/lib/interviewer/selectors/session';
import { entityAttributesProperty } from '~/lib/shared-consts';
import { getCategoricalOptions } from '../../selectors/network';
import ConvexHull from './ConvexHull';

Expand Down
6 changes: 3 additions & 3 deletions lib/interviewer/components/Canvas/LayoutNode.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useRef, useEffect } from 'react';
import { useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import { entityPrimaryKeyProperty } from '@codaco/shared-consts';
import UINode from '../Node';
import { entityPrimaryKeyProperty } from '~/lib/shared-consts';
import DragManager from '../../behaviours/DragAndDrop/DragManager';
import UINode from '../Node';

const LayoutNode = ({
node,
Expand Down
8 changes: 4 additions & 4 deletions lib/interviewer/components/Canvas/NodeLayout.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* eslint-disable no-param-reassign */
import {
entityAttributesProperty,
entityPrimaryKeyProperty,
} from '@codaco/shared-consts';
import { find, get, isEmpty } from 'es-toolkit/compat';
import PropTypes from 'prop-types';
import React from 'react';
import {
entityAttributesProperty,
entityPrimaryKeyProperty,
} from '~/lib/shared-consts';
import LayoutContext from '../../contexts/LayoutContext';
import LayoutNode from './LayoutNode';
import { getTwoModeLayoutVariable } from './utils';
Expand Down
8 changes: 3 additions & 5 deletions lib/interviewer/components/DialogManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { bindActionCreators, compose } from '@reduxjs/toolkit';
import { connect } from 'react-redux';
import { compose, bindActionCreators } from '@reduxjs/toolkit';
import Dialogs from '~/lib/ui/components/Dialogs';
import { actionCreators as dialogsActions } from '../ducks/modules/dialogs';
import { actionCreators as dialogsActions } from '../ducks/modules/dialogs.ts';

const mapStateToProps = (state) => ({
dialogs: state.dialogs,
Expand All @@ -11,6 +11,4 @@ const mapDispatchToProps = (dispatch) => ({
closeDialog: bindActionCreators(dialogsActions.closeDialog, dispatch),
});

export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(Dialogs);
export default compose(connect(mapStateToProps, mapDispatchToProps))(Dialogs);
Loading
Loading