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

Otter 51 researcher step 2 #41

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b0424fc
do not nest `a` tags, use Link as component prop
nathanstitt Nov 21, 2024
bce3b12
increase size limits
nathanstitt Nov 21, 2024
921852e
name file based on study title
nathanstitt Nov 21, 2024
d7f23fa
Fix AWS tagging
nathanstitt Nov 21, 2024
4f72196
open instructions after adding new run
nathanstitt Nov 21, 2024
35680bf
reverse logic, do not use raw uuid in URLs
nathanstitt Nov 21, 2024
f0e0a10
lint long line
nathanstitt Nov 21, 2024
e29daf6
match app name from IaC
nathanstitt Nov 21, 2024
f5dbae8
remove logic around "View Instructions" button
jbwilson8 Nov 20, 2024
fc4a783
rebase
jbwilson8 Dec 2, 2024
63a9676
linting
jbwilson8 Dec 2, 2024
f3e3448
remove typo
jbwilson8 Dec 2, 2024
4b74016
revert change
jbwilson8 Dec 3, 2024
60646d0
re-add code that was removed as a result of the previous revert change
jbwilson8 Dec 4, 2024
21ca403
Merge pull request #39 from safeinsights/fixes-for-demo
nathanstitt Dec 9, 2024
6ac8b16
remove logic around "View Instructions" button
jbwilson8 Nov 20, 2024
bc28e5c
linting
jbwilson8 Dec 2, 2024
c269170
remove typo
jbwilson8 Dec 2, 2024
c7856aa
re-add code that was removed as a result of the previous revert change
jbwilson8 Dec 4, 2024
73f1969
Merge branch 'OTTER-51-Researcher-Step-2' of https://github.com/safei…
jbwilson8 Dec 9, 2024
7e8d6f0
Revert "Merge branch 'OTTER-51-Researcher-Step-2' of https://github.c…
jbwilson8 Dec 9, 2024
f955c62
remove logic around "View Instructions" button
jbwilson8 Nov 20, 2024
d2351b8
rebase
jbwilson8 Dec 2, 2024
eb6bed3
linting
jbwilson8 Dec 2, 2024
8126b55
remove typo
jbwilson8 Dec 2, 2024
23b6e75
revert change
jbwilson8 Dec 3, 2024
f256718
Merge branch 'OTTER-51-Researcher-Step-2' of https://github.com/safei…
jbwilson8 Dec 10, 2024
354e55e
remove duplicate code after rebase
jbwilson8 Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/app/researcher/studies/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '@/database'
import { Container, Flex, Button, Paper, Title, Group, Alert, Anchor } from '@mantine/core'
import Link from '../../../../node_modules/next/link'
import { b64toUUID } from '@/lib/uuid'
import { uuidToB64 } from '@/lib/uuid'
import { studyRowStyle, studyStatusStyle, studyTitleStyle } from './styles.css'
import { humanizeStatus } from '@/lib/status'

Expand Down Expand Up @@ -41,9 +41,9 @@ export default async function StudyReviewPage() {
<p className={studyTitleStyle}>{study.title}</p>
<p>{study.piName}</p>
<p className={studyStatusStyle}>{humanizeStatus(study.status)}</p>
<Link href={`/researcher/study/${b64toUUID(study.id)}/review`}>
<Anchor>Proceed to review ≫</Anchor>
</Link>
<Anchor component={Link} href={`/researcher/study/${uuidToB64(study.id)}/review`}>
Proceed to review ≫
</Anchor>
</li>
))}
</ul>
Expand Down
4 changes: 2 additions & 2 deletions src/app/researcher/study/[encodedStudyId]/edit/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { zodResolver } from 'mantine-form-zod-resolver'
const schema = z
.object({
title: z.string().min(3).max(100),
description: z.string().min(3).max(100),
piName: z.string().min(3).max(100),
description: z.string().min(3).max(500),
piName: z.string().min(3).max(1500),
highlights: z.boolean().nullish(), // .preprocess((value) => value === 'on', z.boolean()).nullish(),
eventCapture: z.boolean().nullish(),
outputMimeType: z.string().nullish(),
Expand Down
4 changes: 2 additions & 2 deletions src/app/researcher/study/[encodedStudyId]/review/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Paper, Center, Title, Stack, Group } from '@mantine/core'
import { db } from '@/database'
import { uuidToB64 } from '@/lib/uuid'
import { b64toUUID } from '@/lib/uuid'
import { StudyPanel } from './panel'
import { AlertNotFound } from '@/components/errors'
import { ResearcherBreadcrumbs } from '@/components/page-breadcrumbs'
Expand All @@ -15,7 +15,7 @@ export default async function StudyReviewPage({
const study = await db
.selectFrom('study')
.selectAll()
.where('id', '=', uuidToB64(encodedStudyId))
.where('id', '=', b64toUUID(encodedStudyId))
.executeTakeFirst()

if (!study) {
Expand Down
10 changes: 6 additions & 4 deletions src/app/researcher/study/[encodedStudyId]/review/results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { DataTable } from 'mantine-datatable'
import { fetchRunResultsAction } from './actions'
import { ErrorAlert } from '@/components/errors'
import { IconDownload } from '@tabler/icons-react'
import { slugify } from '@/lib/string'

type RunResultsProps = {
run: { id: string }
study: { title: string }
}

const ViewCSV: FC<RunResultsProps> = ({ run }) => {
Expand Down Expand Up @@ -48,7 +50,7 @@ const ViewCSV: FC<RunResultsProps> = ({ run }) => {

return (
<DataTable
height={'calc(100vh - 200px)'}
height={'calc(100vh - 250px)'}
idAccessor={'INDEX_KEY_FOR_RENDERING'}
withTableBorder={false}
withColumnBorders={false}
Expand All @@ -58,7 +60,7 @@ const ViewCSV: FC<RunResultsProps> = ({ run }) => {
)
}

export const PreviewCSVResultsBtn: FC<RunResultsProps> = ({ run }) => {
export const PreviewCSVResultsBtn: FC<RunResultsProps> = ({ run, study }) => {
const [opened, { open, close }] = useDisclosure(false)

return (
Expand All @@ -68,7 +70,7 @@ export const PreviewCSVResultsBtn: FC<RunResultsProps> = ({ run }) => {
onClose={close}
size="100%"
title={
<Link href={`/dl/results/${uuidToB64(run.id)}`}>
<Link href={`/dl/results/${uuidToB64(run.id)}/${slugify(study.title)}.csv`}>
<Button rightSection={<IconDownload size={14} />}>Download Results</Button>
</Link>
}
Expand All @@ -79,7 +81,7 @@ export const PreviewCSVResultsBtn: FC<RunResultsProps> = ({ run }) => {
}}
centered
>
<ViewCSV run={run} />
<ViewCSV run={run} study={study} />
</Modal>

<Button variant="outline" onClick={open}>
Expand Down
34 changes: 26 additions & 8 deletions src/app/researcher/study/[encodedStudyId]/review/runs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Group,
Center,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'

import { PushInstructions } from '@/components/push-instructions'
import { IconPlus } from '@tabler/icons-react'
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'
Expand All @@ -38,12 +38,12 @@ type RunsTableProps = {

const RunsTable: React.FC<RunsTableProps> = ({ isActive, study }) => {
const queryClient = useQueryClient()
const [__, setViewingRunId] = useState<string | null>(null)
const [openedRunId, setOpened] = useState<string | false>(false)

const { mutate: insertRun } = useMutation({
mutationFn: () => onStudyRunCreateAction(study.id),
onSuccess: async (runId) => {
setViewingRunId(runId)
setOpened(runId)
await queryClient.invalidateQueries({ queryKey: ['runsForStudy', study.id] })
},
})
Expand All @@ -54,8 +54,6 @@ const RunsTable: React.FC<RunsTableProps> = ({ isActive, study }) => {
queryFn: () => onFetchStudyRunsAction(study.id),
})

const [opened, { open, close }] = useDisclosure(false)

if (isPending) return <p>Loading...</p>

return (
Expand All @@ -79,18 +77,38 @@ const RunsTable: React.FC<RunsTableProps> = ({ isActive, study }) => {
<Table.Td>{run.startedAt?.toISOString() || ''}</Table.Td>
<Table.Td align="right">
<Group>
<>
<Modal
size={800}
opened={opened}
onClose={close}
title="AWS Instructions"
centered
>
<PushInstructions
containerLocation={study.containerLocation}
runId={run.id}
/>
</Modal>
<Button onClick={open}>View Instructions</Button>
</>
{run.status == 'INITIATED' && (
<>
<Modal opened={opened} onClose={close} title="AWS Instructions" centered>
<Modal
opened={!!openedRunId}
onClose={() => setOpened(false)}
title="AWS Instructions"
centered
>
<PushInstructions
containerLocation={study.containerLocation}
runId={run.id}
/>
</Modal>
<Button onClick={open}>View Instructions</Button>
<Button onClick={() => setOpened(run.id)}>View Instructions</Button>
</>
)}
{run.status == 'COMPLETED' && <PreviewCSVResultsBtn run={run} />}
{run.status == 'COMPLETED' && <PreviewCSVResultsBtn run={run} study={study} />}
</Group>
</Table.Td>
</Table.Tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { db } from '@/database'
import { uuidToB64 } from '@/lib/uuid'
import { v7 as uuidv7 } from 'uuid'
import { onStudyRunCreateAction } from '@/app/researcher/studies/actions'
import { strToAscii } from '@/lib/string'

export const onCreateStudyAction = async (memberId: string, study: FormValues) => {
schema.parse(study) // throws when malformed
Expand All @@ -25,7 +26,7 @@ export const onCreateStudyAction = async (memberId: string, study: FormValues) =

if (USING_CONTAINER_REGISTRY) {
repoUrl = await createAnalysisRepository(repoPath, {
title: study.title,
title: strToAscii(study.title),
studyId,
})
} else {
Expand Down
19 changes: 0 additions & 19 deletions src/lib/paths.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
import type { MinimalRunInfo, MinimalRunResultsInfo } from '@/lib/types'
import { uuidToB64 } from './uuid'
// // https://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
export function slugify(str: string) {
str = str.replace(/^\s+|\s+$/g, '') // trim
str = str.toLowerCase()

// remove accents, swap ñ for n, etc
var from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;'
var to = 'aaaaeeeeiiiioooouuuunc------'
for (var i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i))
}

str = str
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-') // collapse dashes

return str.slice(0, 50)
}

export const pathForStudyRun = (parts: MinimalRunInfo) =>
`analysis/${parts.memberIdentifier}/${parts.studyId}/${parts.studyRunId}`
Expand Down
23 changes: 23 additions & 0 deletions src/lib/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function strToAscii(str: string) {
return str.replace(/[^\x00-\x7F]/g, '')
}

// https://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
export function slugify(str: string) {
str = str.replace(/^\s+|\s+$/g, '') // trim
str = str.toLowerCase()

// remove accents, swap ñ for n, etc
var from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;'
var to = 'aaaaeeeeiiiioooouuuunc------'
for (var i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i))
}

str = str
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-') // collapse dashes

return str.slice(0, 50)
}
21 changes: 11 additions & 10 deletions src/server/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { Upload } from '@aws-sdk/lib-storage'
import { ECRClient, CreateRepositoryCommand, SetRepositoryPolicyCommand } from '@aws-sdk/client-ecr'
import { TEST_ENV } from './config'
import { AWS_ACCOUNT_ENVIRONMENT, TEST_ENV } from './config'
import { fromIni } from '@aws-sdk/credential-providers'
import { slugify, pathForStudyRun, pathForStudyRunResults } from '@/lib/paths'
import { strToAscii, slugify } from '@/lib/string'
import { pathForStudyRun, pathForStudyRunResults } from '@/lib/paths'
import { uuidToB64 } from '@/lib/uuid'
import { Readable } from 'stream'
import { createHash } from 'crypto'
import { CodeManifest, MinimalRunInfo, MinimalRunResultsInfo } from '@/lib/types'
import { getECRPolicy } from './aws-ecr-policy'

const DEFAULT_TAGS: Record<string, string> = {
Environment: 'sandbox',
Target: 'si:analysis',
export function objectToAWSTags(tags: Record<string, string>) {
const Environment = AWS_ACCOUNT_ENVIRONMENT[process.env.AWS_ACCOUNT_ID || ''] || 'Unknown'
return Object.entries({ ...tags, Environment, Application: 'Mangement App' }).map(([Key, Value]) => ({
Key,
Value: strToAscii(Value).slice(0, 256),
}))
}

let _ecrClient: ECRClient | null = null
Expand Down Expand Up @@ -70,7 +74,7 @@ export async function createAnalysisRepository(repositoryName: string, tags: Rec
const resp = await ecrClient.send(
new CreateRepositoryCommand({
repositoryName,
tags: Object.entries(Object.assign(tags, DEFAULT_TAGS)).map(([Key, Value]) => ({ Key, Value })),
tags: objectToAWSTags({ ...tags, Target: 'si:analysis' }),
}),
)
if (!resp?.repository?.repositoryUri) {
Expand Down Expand Up @@ -105,10 +109,7 @@ export const storeResultsFile = async (info: MinimalRunResultsInfo, body: Readab
const hash = await calculateChecksum(csStream)
const uploader = new Upload({
client: getS3Client(),
tags: [
{ Key: 'studyId', Value: info.studyId },
{ Key: 'runId', Value: info.studyRunId },
],
tags: objectToAWSTags({ studyId: info.studyId, runId: info.studyRunId }),
params: {
Bucket: s3BucketName(),
ChecksumSHA256: hash,
Expand Down
7 changes: 7 additions & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ export const ENCLAVE_AWS_ACCOUNT_NUMBERS = [
'084375557107', // dev
'354918363956', // sandbox
]

export const AWS_ACCOUNT_ENVIRONMENT: Record<string, string> = {
'533267019973': 'Production',
'867344442985': 'Staging',
'905418271997': 'Sandbox',
'872515273917': 'Development',
}
Loading