diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json
index a2e6ef80..404144b9 100644
--- a/packages/client/public/locales/en/translation.json
+++ b/packages/client/public/locales/en/translation.json
@@ -85,7 +85,9 @@
"userPermissions": {
"studyAdmin": "Study Admin",
"contributor": "Contributor",
- "trained": "Trained"
+ "trained": "Trained",
+ "trainingView": "View Training Results",
+ "noTrainingTags": "No Tags to Show"
},
"projectUserPermissions": {
"projectAdmin": "Project Admin",
diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx
index dd1c9730..74234797 100644
--- a/packages/client/src/App.tsx
+++ b/packages/client/src/App.tsx
@@ -27,6 +27,8 @@ import { StudyProvider } from './context/Study.context';
import { ConfirmationProvider } from './context/Confirmation.context';
import { DatasetProvider } from './context/Dataset.context';
import { EntryControls } from './pages/studies/EntryControls';
+import { PermissionProvider } from './context/Permission.context';
+import { TagTrainingView } from './pages/studies/TagTrainingView';
const drawerWidth = 256;
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
@@ -90,17 +92,19 @@ const AppInternal: FC = () => {
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
@@ -126,6 +130,7 @@ const MyRoutes: FC = () => {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/packages/client/src/components/SideBar.component.tsx b/packages/client/src/components/SideBar.component.tsx
index 6b466b46..f50c2467 100644
--- a/packages/client/src/components/SideBar.component.tsx
+++ b/packages/client/src/components/SideBar.component.tsx
@@ -1,15 +1,13 @@
-import { FC, ReactNode, useEffect, useState } from 'react';
+import { FC, ReactNode, useState } from 'react';
import { Collapse, Divider, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import { ExpandMore, ExpandLess, School, Dataset, Work, Logout, GroupWork } from '@mui/icons-material';
import { useAuth } from '../context/Auth.context';
import { useNavigate } from 'react-router-dom';
import { Environment } from './Environment.component';
import { Permission } from '../graphql/graphql';
-import { useGetRolesQuery } from '../graphql/permission/permission';
-import { useProject } from '../context/Project.context';
-import { useStudy } from '../context/Study.context';
import { useTranslation } from 'react-i18next';
import { LanguageSelector } from './LanguageSelector';
+import { usePermission } from '../context/Permission.context';
interface SideBarProps {
open: boolean;
@@ -19,17 +17,8 @@ interface SideBarProps {
export const SideBar: FC = ({ open, drawerWidth }) => {
const { logout } = useAuth();
const navigate = useNavigate();
- const [permission, setPermission] = useState(null);
- const { project } = useProject();
- const { study } = useStudy();
- const rolesQueryResults = useGetRolesQuery({ variables: { project: project?._id, study: study?._id } });
const { t } = useTranslation();
-
- useEffect(() => {
- if (rolesQueryResults.data) {
- setPermission(rolesQueryResults.data.getRoles);
- }
- }, [rolesQueryResults.data]);
+ const { permission } = usePermission();
const navItems: NavItemProps[] = [
{
diff --git a/packages/client/src/components/TagTraining.component.tsx b/packages/client/src/components/TagTraining.component.tsx
index a8f0487e..21500ab1 100644
--- a/packages/client/src/components/TagTraining.component.tsx
+++ b/packages/client/src/components/TagTraining.component.tsx
@@ -73,8 +73,6 @@ export const TagTrainingComponent: React.FC = (props)
props.setTrainingSet(entries);
}, [trainingSet]);
- // TODO: In the future, the datasets retrieved should only be datasets
- // accessible by the current project
useEffect(() => {
if (getDatasetsResults.data) {
setDatasets(getDatasetsResults.data.getDatasetsByProject);
diff --git a/packages/client/src/components/tag/view/TagGridView.component.tsx b/packages/client/src/components/tag/view/TagGridView.component.tsx
index 426f356b..67d6e641 100644
--- a/packages/client/src/components/tag/view/TagGridView.component.tsx
+++ b/packages/client/src/components/tag/view/TagGridView.component.tsx
@@ -1,10 +1,9 @@
import { useTranslation } from 'react-i18next';
import { GetGridColDefs, TagViewTest } from '../../../types/TagColumnView';
-import { Study, Entry } from '../../../graphql/graphql';
+import { Entry, Study } from '../../../graphql/graphql';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { DataGrid } from '@mui/x-data-grid';
-import { GetTagsQuery, useGetTagsQuery } from '../../../graphql/tag/tag';
-import { useEffect, useState } from 'react';
+import { GetTagsQuery } from '../../../graphql/tag/tag';
import { freeTextTest, getTextCols } from './FreeTextGridView.component';
import { EntryView } from '../../EntryView.component';
import { Checkbox } from '@mui/material';
@@ -16,11 +15,11 @@ import { getVideoCols, videoViewTest } from './VideoGridView.component';
export interface TagGridViewProps {
study: Study;
+ tags: GetTagsQuery['getTags'];
}
-export const TagGridView: React.FC = ({ study }) => {
+export const TagGridView: React.FC = ({ tags, study }) => {
const { t } = useTranslation();
- const [tags, setTags] = useState([]);
const tagColumnViews: { tester: TagViewTest; getGridColDefs: GetGridColDefs }[] = [
{ tester: freeTextTest, getGridColDefs: getTextCols },
@@ -31,14 +30,6 @@ export const TagGridView: React.FC = ({ study }) => {
{ tester: videoViewTest, getGridColDefs: getVideoCols }
];
- const getTagsResults = useGetTagsQuery({ variables: { study: study._id } });
-
- useEffect(() => {
- if (getTagsResults.data) {
- setTags(getTagsResults.data.getTags);
- }
- }, [getTagsResults.data]);
-
const entryColumns: GridColDef[] = [
{
field: 'entryView',
diff --git a/packages/client/src/context/Permission.context.tsx b/packages/client/src/context/Permission.context.tsx
new file mode 100644
index 00000000..daf5e68b
--- /dev/null
+++ b/packages/client/src/context/Permission.context.tsx
@@ -0,0 +1,33 @@
+import { ReactNode, createContext, useContext, useEffect, useState } from 'react';
+import { Permission } from '../graphql/graphql';
+import { useProject } from './Project.context';
+import { useStudy } from './Study.context';
+import { useGetRolesQuery } from '../graphql/permission/permission';
+
+interface PermissionContextProps {
+ permission: Permission | null;
+}
+
+const PermissionContext = createContext({} as PermissionContextProps);
+
+export interface PermissionProviderProps {
+ children: ReactNode;
+}
+
+export const PermissionProvider: React.FC = ({ children }) => {
+ const [permission, setPermission] = useState(null);
+ const { project } = useProject();
+ const { study } = useStudy();
+
+ const rolesQueryResult = useGetRolesQuery({ variables: { project: project?._id, study: study?._id } });
+
+ useEffect(() => {
+ if (rolesQueryResult.data) {
+ setPermission(rolesQueryResult.data.getRoles);
+ }
+ }, [rolesQueryResult]);
+
+ return {children};
+};
+
+export const usePermission = () => useContext(PermissionContext);
diff --git a/packages/client/src/context/Tag.context.tsx b/packages/client/src/context/Tag.context.tsx
index 0e3f0b30..958a2fd2 100644
--- a/packages/client/src/context/Tag.context.tsx
+++ b/packages/client/src/context/Tag.context.tsx
@@ -1,9 +1,11 @@
import { ReactNode, FC, createContext, useContext, useEffect, useState } from 'react';
import { useStudy } from './Study.context';
import { AssignTagMutation, useAssignTagMutation } from '../graphql/tag/tag';
+import { usePermission } from './Permission.context';
export interface TagContextProps {
tag: AssignTagMutation['assignTag'] | null;
+ training: boolean;
requestTag: () => void;
}
@@ -17,10 +19,14 @@ export const TagProvider: FC = ({ children }) => {
const { study } = useStudy();
const [tag, setTag] = useState(null);
const [assignTag, assignTagResult] = useAssignTagMutation();
+ const { permission } = usePermission();
+ const [training, setTraining] = useState(false);
useEffect(() => {
requestTag();
- }, [study]);
+
+ setTraining(permission ? !permission.trainedContributor : false);
+ }, [permission]);
useEffect(() => {
setTag(assignTagResult.data?.assignTag);
@@ -35,7 +41,7 @@ export const TagProvider: FC = ({ children }) => {
assignTag({ variables: { study: study._id }, fetchPolicy: 'network-only' });
};
- return {children};
+ return {children};
};
export const useTag = () => useContext(TagContext);
diff --git a/packages/client/src/graphql/graphql.ts b/packages/client/src/graphql/graphql.ts
index 486a8f49..e6c9a6bf 100644
--- a/packages/client/src/graphql/graphql.ts
+++ b/packages/client/src/graphql/graphql.ts
@@ -360,6 +360,7 @@ export type Query = {
getRoles: Permission;
getStudyPermissions: Array;
getTags: Array;
+ getTrainingTags: Array;
isEntryEnabled: Scalars['Boolean']['output'];
lexFindAll: Array;
lexiconByKey: LexiconEntry;
@@ -438,6 +439,12 @@ export type QueryGetTagsArgs = {
};
+export type QueryGetTrainingTagsArgs = {
+ study: Scalars['ID']['input'];
+ user: Scalars['String']['input'];
+};
+
+
export type QueryIsEntryEnabledArgs = {
entry: Scalars['ID']['input'];
study: Scalars['ID']['input'];
diff --git a/packages/client/src/graphql/tag/tag.graphql b/packages/client/src/graphql/tag/tag.graphql
index 2efa1006..af863dca 100644
--- a/packages/client/src/graphql/tag/tag.graphql
+++ b/packages/client/src/graphql/tag/tag.graphql
@@ -65,3 +65,22 @@ query getTags($study: ID!) {
}
}
+query getTrainingTags($study: ID!, $user: String!) {
+ getTrainingTags(study: $study, user: $user) {
+ _id
+ entry {
+ _id
+ organization
+ entryID
+ contentType
+ dataset
+ creator
+ dateCreated
+ meta
+ signedUrl
+ signedUrlExpiration
+ }
+ data
+ complete
+ }
+}
diff --git a/packages/client/src/graphql/tag/tag.ts b/packages/client/src/graphql/tag/tag.ts
index 6417d986..e36489ed 100644
--- a/packages/client/src/graphql/tag/tag.ts
+++ b/packages/client/src/graphql/tag/tag.ts
@@ -69,6 +69,14 @@ export type GetTagsQueryVariables = Types.Exact<{
export type GetTagsQuery = { __typename?: 'Query', getTags: Array<{ __typename?: 'Tag', _id: string, data?: any | null, complete: boolean, entry: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number } }> };
+export type GetTrainingTagsQueryVariables = Types.Exact<{
+ study: Types.Scalars['ID']['input'];
+ user: Types.Scalars['String']['input'];
+}>;
+
+
+export type GetTrainingTagsQuery = { __typename?: 'Query', getTrainingTags: Array<{ __typename?: 'Tag', _id: string, data?: any | null, complete: boolean, entry: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number } }> };
+
export const CreateTagsDocument = gql`
mutation createTags($study: ID!, $entries: [ID!]!) {
@@ -364,4 +372,54 @@ export function useGetTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions;
export type GetTagsLazyQueryHookResult = ReturnType;
-export type GetTagsQueryResult = Apollo.QueryResult;
\ No newline at end of file
+export type GetTagsQueryResult = Apollo.QueryResult;
+export const GetTrainingTagsDocument = gql`
+ query getTrainingTags($study: ID!, $user: String!) {
+ getTrainingTags(study: $study, user: $user) {
+ _id
+ entry {
+ _id
+ organization
+ entryID
+ contentType
+ dataset
+ creator
+ dateCreated
+ meta
+ signedUrl
+ signedUrlExpiration
+ }
+ data
+ complete
+ }
+}
+ `;
+
+/**
+ * __useGetTrainingTagsQuery__
+ *
+ * To run a query within a React component, call `useGetTrainingTagsQuery` and pass it any options that fit your needs.
+ * When your component renders, `useGetTrainingTagsQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * you can use to render your UI.
+ *
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
+ *
+ * @example
+ * const { data, loading, error } = useGetTrainingTagsQuery({
+ * variables: {
+ * study: // value for 'study'
+ * user: // value for 'user'
+ * },
+ * });
+ */
+export function useGetTrainingTagsQuery(baseOptions: Apollo.QueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useQuery(GetTrainingTagsDocument, options);
+ }
+export function useGetTrainingTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useLazyQuery(GetTrainingTagsDocument, options);
+ }
+export type GetTrainingTagsQueryHookResult = ReturnType;
+export type GetTrainingTagsLazyQueryHookResult = ReturnType;
+export type GetTrainingTagsQueryResult = Apollo.QueryResult;
\ No newline at end of file
diff --git a/packages/client/src/pages/contribute/ContributeLanding.tsx b/packages/client/src/pages/contribute/ContributeLanding.tsx
index d57b7241..5d130ef8 100644
--- a/packages/client/src/pages/contribute/ContributeLanding.tsx
+++ b/packages/client/src/pages/contribute/ContributeLanding.tsx
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
const ContributeLandingInternal: React.FC = () => {
const navigate = useNavigate();
const { study } = useStudy();
- const { tag } = useTag();
+ const { tag, training } = useTag();
const { t } = useTranslation();
const enterTagging = () => {
@@ -26,7 +26,7 @@ const ContributeLandingInternal: React.FC = () => {
- {false ? t('components.contribute.studyTraining') : t('components.contribute.studyTagging')}
+ {training ? t('components.contribute.studyTraining') : t('components.contribute.studyTagging')}
{t('common.study')}: {study.name}
diff --git a/packages/client/src/pages/studies/TagTrainingView.tsx b/packages/client/src/pages/studies/TagTrainingView.tsx
new file mode 100644
index 00000000..3e0d920e
--- /dev/null
+++ b/packages/client/src/pages/studies/TagTrainingView.tsx
@@ -0,0 +1,33 @@
+import { useLocation } from 'react-router-dom';
+import { User, Study } from '../../graphql/graphql';
+import { GetTagsQuery, useGetTrainingTagsQuery } from '../../graphql/tag/tag';
+import { useEffect, useState } from 'react';
+import { TagGridView } from '../../components/tag/view/TagGridView.component';
+import { Typography } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+export const TagTrainingView: React.FC = () => {
+ const state = useLocation().state;
+ const user: User = state.user;
+ const study: Study = state.study;
+ const [tags, setTags] = useState([]);
+ const { t } = useTranslation();
+
+ const trainingTags = useGetTrainingTagsQuery({ variables: { study: study._id, user: user.uid } });
+
+ useEffect(() => {
+ if (trainingTags.data) {
+ setTags(trainingTags.data.getTrainingTags);
+ }
+ }, [trainingTags]);
+
+ return (
+ <>
+ {!tags || tags.length === 0 ? (
+ {t('components.userPermissions.noTrainingTags')}
+ ) : (
+
+ )}
+ >
+ );
+};
diff --git a/packages/client/src/pages/studies/TagView.tsx b/packages/client/src/pages/studies/TagView.tsx
index 09879e1e..36ba2ba5 100644
--- a/packages/client/src/pages/studies/TagView.tsx
+++ b/packages/client/src/pages/studies/TagView.tsx
@@ -2,15 +2,34 @@ import { Container, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useStudy } from '../../context/Study.context';
import { TagGridView } from '../../components/tag/view/TagGridView.component';
+import { useEffect, useState } from 'react';
+import { GetTagsQuery, useGetTagsLazyQuery } from '../../graphql/tag/tag';
export const TagView: React.FC = () => {
const { t } = useTranslation();
const { study } = useStudy();
+ const [tags, setTags] = useState([]);
+ const [getTagQuery, getTagResult] = useGetTagsLazyQuery();
+
+ useEffect(() => {
+ if (!study) {
+ return;
+ }
+
+ getTagQuery({ variables: { study: study._id } });
+ }, [study]);
+
+ useEffect(() => {
+ if (!getTagResult.data) {
+ return;
+ }
+ setTags(getTagResult.data.getTags);
+ }, [getTagResult]);
return (
{t('menu.viewTags')}
- {study && }
+ {study && }
);
};
diff --git a/packages/client/src/pages/studies/UserPermissions.tsx b/packages/client/src/pages/studies/UserPermissions.tsx
index 8df0f6a6..df7fd8f7 100644
--- a/packages/client/src/pages/studies/UserPermissions.tsx
+++ b/packages/client/src/pages/studies/UserPermissions.tsx
@@ -1,4 +1,4 @@
-import { Switch, Typography } from '@mui/material';
+import { Switch, Typography, Button } from '@mui/material';
import { DataGrid, GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { useStudy } from '../../context/Study.context';
import { Study, StudyPermissionModel } from '../../graphql/graphql';
@@ -11,6 +11,7 @@ import {
} from '../../graphql/permission/permission';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
export const StudyUserPermissions: React.FC = () => {
const { study } = useStudy();
@@ -115,6 +116,31 @@ const EditTrainedSwitch: React.FC = (props) => {
);
};
+interface TagViewButtonProps {
+ permission: StudyPermissionModel;
+ study: Study;
+}
+
+const TagViewButton: React.FC = (props) => {
+ const { t } = useTranslation();
+ const navigation = useNavigate();
+
+ const onClick = () => {
+ navigation('/study/training', {
+ state: {
+ user: props.permission.user,
+ study: props.study
+ }
+ });
+ };
+
+ return (
+
+ );
+};
+
const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => {
const { decodedToken } = useAuth();
const { data, refetch } = useGetStudyPermissionsQuery({
@@ -176,6 +202,14 @@ const UserPermissionTable: React.FC<{ study: Study }> = ({ study }) => {
},
editable: false,
flex: 1
+ },
+ {
+ field: 'traingData',
+ headerName: t('components.userPermissions.trainingView'),
+ renderCell: (params: GridRenderCellParams) => {
+ return ;
+ },
+ flex: 1
}
];
return (
diff --git a/packages/server/src/tag/resolvers/tag.resolver.ts b/packages/server/src/tag/resolvers/tag.resolver.ts
index 07a892e9..257c3f4b 100644
--- a/packages/server/src/tag/resolvers/tag.resolver.ts
+++ b/packages/server/src/tag/resolvers/tag.resolver.ts
@@ -15,6 +15,7 @@ import { TokenContext } from '../../jwt/token.context';
import { TokenPayload } from '../../jwt/token.dto';
import { StudyPermissions } from '../../permission/permissions/study';
import { TagPermissions } from 'src/permission/permissions/tag';
+import { Roles } from 'src/permission/permissions/roles';
// TODO: Add permissioning
@UseGuards(JwtAuthGuard)
@@ -44,7 +45,14 @@ export class TagResolver {
@Args('study', { type: () => ID }, StudyPipe) study: Study,
@TokenContext() user: TokenPayload
): Promise {
- return this.tagService.assignTag(study, user.user_id);
+ // Determine if the user is considered "trained"
+ const isTrained: boolean = await this.enforcer.enforce(
+ user.user_id,
+ Roles.TRAINED_CONTRIBUTOR,
+ study._id.toString()
+ );
+
+ return this.tagService.assignTag(study, user.user_id, isTrained);
}
@Mutation(() => Boolean)
@@ -96,6 +104,19 @@ export class TagResolver {
return this.tagService.getTags(study);
}
+ @Query(() => [Tag])
+ async getTrainingTags(
+ @Args('study', { type: () => ID }, StudyPipe) study: Study,
+ @Args('user') user: string,
+ @TokenContext() requestingUser: TokenPayload
+ ): Promise {
+ if (!(await this.enforcer.enforce(requestingUser.user_id, TagPermissions.READ, study._id.toString()))) {
+ throw new UnauthorizedException('User cannot read tags in this study');
+ }
+
+ return this.tagService.getTrainingTags(study, user);
+ }
+
@ResolveField(() => Entry)
async entry(@Parent() tag: Tag): Promise {
return this.entryPipe.transform(tag.entry);
diff --git a/packages/server/src/tag/services/tag.service.ts b/packages/server/src/tag/services/tag.service.ts
index 7f1e2a89..274ffc77 100644
--- a/packages/server/src/tag/services/tag.service.ts
+++ b/packages/server/src/tag/services/tag.service.ts
@@ -8,6 +8,8 @@ import { StudyService } from '../../study/study.service';
import { MongooseMiddlewareService } from '../../shared/service/mongoose-callback.service';
import { TagTransformer } from './tag-transformer.service';
import { TokenPayload } from '../../jwt/token.dto';
+import { TrainingSetService } from './training-set.service';
+import { TrainingSet } from '../models/training-set';
@Injectable()
export class TagService {
@@ -15,7 +17,8 @@ export class TagService {
@InjectModel(Tag.name) private readonly tagModel: Model,
private readonly studyService: StudyService,
middlewareService: MongooseMiddlewareService,
- private readonly tagTransformService: TagTransformer
+ private readonly tagTransformService: TagTransformer,
+ private readonly trainingSetService: TrainingSetService
) {
// Subscribe to study delete events
middlewareService.register(Study.name, 'deleteOne', async (study: Study) => {
@@ -48,7 +51,8 @@ export class TagService {
study: study._id,
complete: false,
order,
- enabled: true
+ enabled: true,
+ training: false
});
tags.push(newTag);
}
@@ -56,12 +60,81 @@ export class TagService {
return tags;
}
+ async assignTag(study: Study, user: string, isTrained: boolean): Promise {
+ return isTrained ? this.assignTagFull(study, user) : this.assignTrainingTag(study, user);
+ }
+
+ async getTrainingTags(study: Study, user: string): Promise {
+ return this.tagModel.find({
+ user,
+ study: study._id,
+ training: true
+ });
+ }
+
+ /**
+ * Assign the user a tag as part of the training set.
+ */
+ private async assignTrainingTag(study: Study, user: string): Promise {
+ // First get the training set associated with the study
+ const trainingSet = await this.trainingSetService.findByStudy(study);
+
+ // If the training set is null or the length of entries is 0, then no tag to assign
+ if (!trainingSet || trainingSet.entries.length == 0) {
+ return null;
+ }
+
+ // See if the user has any training tags. If we have reached this point in the code,
+ // and no training tags exist, then they haven't been generated for this user yet.
+ const existingTrainingTag = await this.tagModel.findOne({
+ user,
+ study: study._id,
+ training: true
+ });
+
+ // If there is no existing training tag, generate the training set for the user
+ if (!existingTrainingTag) {
+ await this.createTrainingTags(study, user, trainingSet);
+ }
+
+ // At this point, the next incomplete training tag can be returned
+ const tags = await this.tagModel
+ .find({
+ study: study._id,
+ user,
+ training: true,
+ complete: false
+ })
+ .sort({ order: 1 })
+ .limit(1);
+
+ return tags[0];
+ }
+
+ private async createTrainingTags(study: Study, user: string, trainingSet: TrainingSet): Promise {
+ return await Promise.all(
+ trainingSet.entries.map(async (entry, index) => {
+ return this.tagModel.create({
+ entry,
+ study: study._id,
+ complete: false,
+ order: index,
+ enabled: true,
+ training: true,
+ user
+ });
+ })
+ );
+ }
+
/**
+ * Assign tags based on the full (not training) data set.
+ *
* Assign the tag to the given user. If the user already has an incomplete
* tag, return that tag to the user. If there are no more remaining tags,
* null is returned.
*/
- async assignTag(study: Study, user: string): Promise {
+ private async assignTagFull(study: Study, user: string): Promise {
// Check for incomplete tags
const incomplete = await this.getIncomplete(study, user);
if (incomplete) {
diff --git a/packages/server/src/tag/services/training-set.service.ts b/packages/server/src/tag/services/training-set.service.ts
index 774a0531..ea576006 100644
--- a/packages/server/src/tag/services/training-set.service.ts
+++ b/packages/server/src/tag/services/training-set.service.ts
@@ -15,4 +15,8 @@ export class TrainingSetService {
entries: entries.map((entry) => entry._id)
});
}
+
+ async findByStudy(study: Study): Promise {
+ return this.trainingSetModel.findOne({ study: study._id });
+ }
}