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

Glacier support (partial) #1794

Merged
merged 6 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
129 changes: 129 additions & 0 deletions catalog/app/components/Preview/Display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import * as R from 'ramda'
import * as React from 'react'
import * as M from '@material-ui/core'

import * as AWS from 'utils/AWS'
import AsyncResult from 'utils/AsyncResult'
import * as Config from 'utils/Config'
import StyledLink from 'utils/StyledLink'
import pipeThru from 'utils/pipeThru'

import render from './render'
import { PreviewError } from './types'

const defaultProgress = () => <M.CircularProgress />

const defaultMessage = ({ heading, body, action }) => (
<>
{!!heading && (
<M.Typography variant="h6" gutterBottom>
{heading}
</M.Typography>
)}
{!!body && (
<M.Typography variant="body1" gutterBottom>
{body}
</M.Typography>
)}
{!!action && action}
</>
)

const defaultAction = ({ label, ...rest }) => (
<M.Button variant="outlined" {...rest}>
{label}
</M.Button>
)

export default function PreviewDisplay({
data,
noDownload,
renderContents = R.identity,
renderProgress = defaultProgress,
renderMessage = defaultMessage,
renderAction = defaultAction,
}) {
const cfg = Config.use()
const noDl = noDownload != null ? noDownload : cfg.noDownload
return pipeThru(data)(
AsyncResult.case({
_: renderProgress,
Ok: R.pipe(render, renderContents),
Err: PreviewError.case({
Deleted: () =>
renderMessage({
heading: 'Delete Marker',
body: (
<>
Selected version of the object is a{' '}
<StyledLink
href="https://docs.aws.amazon.com/AmazonS3/latest/dev/DeleteMarker.html"
target="_blank"
>
delete marker
</StyledLink>
</>
),
}),
Archived: () =>
renderMessage({
heading: 'Object Archived',
body: 'Preview not available',
}),
InvalidVersion: () =>
renderMessage({
heading: 'Invalid Version',
body: 'Invalid version id specified',
}),
Forbidden: () =>
renderMessage({
heading: 'Access Denied',
body: 'Preview not available',
}),
Gated: ({ load }) =>
renderMessage({
heading: 'Object is Too Large',
body: 'Large files are not previewed by default',
action: !!load && renderAction({ label: 'Load preview', onClick: load }),
}),
TooLarge: ({ handle }) =>
renderMessage({
heading: 'Object is Too Large',
body: 'Object is too large to preview',
action:
!noDl &&
AWS.Signer.withDownloadUrl(handle, (href) =>
renderAction({ label: 'Download and view in Browser', href }),
),
}),
Unsupported: ({ handle }) =>
renderMessage({
heading: 'Preview Not Available',
action:
!noDl &&
AWS.Signer.withDownloadUrl(handle, (href) =>
renderAction({ label: 'Download and view in Browser', href }),
),
}),
DoesNotExist: () =>
renderMessage({
heading: 'No Such Object',
body: 'Object does not exist',
}),
MalformedJson: ({ message }) =>
renderMessage({
heading: 'Malformed JSON',
body: message,
}),
Unexpected: ({ retry }) =>
renderMessage({
heading: 'Unexpected Error',
body: 'Something went wrong while loading preview',
action: !!retry && renderAction({ label: 'Retry', onClick: retry }),
}),
}),
}),
)
}

export const bind = (props) => (data) => <PreviewDisplay {...props} data={data} />
36 changes: 0 additions & 36 deletions catalog/app/components/Preview/Preview.js

This file was deleted.

5 changes: 4 additions & 1 deletion catalog/app/components/Preview/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './Preview'
export { default as Display, bind as display } from './Display'
export { default as render } from './render'
export { default as load } from './load'
export { PreviewData, PreviewError } from './types'
39 changes: 39 additions & 0 deletions catalog/app/components/Preview/load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react'

import * as Csv from './loaders/Csv'
import * as Excel from './loaders/Excel'
import * as Fcs from './loaders/Fcs'
import * as Html from './loaders/Html'
import * as Image from './loaders/Image'
import * as Json from './loaders/Json'
import * as Markdown from './loaders/Markdown'
import * as Notebook from './loaders/Notebook'
import * as Parquet from './loaders/Parquet'
import * as Pdf from './loaders/Pdf'
import * as Text from './loaders/Text'
import * as Vcf from './loaders/Vcf'
import * as fallback from './loaders/fallback'

const loaderChain = [
Csv,
Excel,
Fcs,
Json,
Markdown,
Notebook,
Parquet,
Pdf,
Vcf,
Html,
Text,
Image,
fallback,
]

export function Load({ handle, children }) {
const key = handle.logicalKey || handle.key
const { Loader } = React.useMemo(() => loaderChain.find((L) => L.detect(key)), [key])
return <Loader {...{ handle, children }} />
}

export default (handle, children) => <Load {...{ handle, children }} />
23 changes: 12 additions & 11 deletions catalog/app/components/Preview/loaders/Csv.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import * as R from 'ramda'

import AsyncResult from 'utils/AsyncResult'

import { PreviewData } from '../types'
import * as utils from './utils'

export const detect = R.pipe(utils.stripCompression, utils.extIn(['.csv', '.tsv']))

const fetcher = utils.previewFetcher('csv', (json) =>
AsyncResult.Ok(
const isTsv = R.pipe(utils.stripCompression, utils.extIs('.tsv'))

export const Loader = function CsvLoader({ handle, children }) {
const data = utils.usePreview({
type: 'csv',
handle,
query: isTsv(handle.key) ? { sep: '\t' } : undefined,
})
const processed = utils.useProcessing(data.result, (json) =>
PreviewData.DataFrame({
preview: json.html,
note: json.info.note,
warnings: json.info.warnings,
}),
),
)

const isTsv = R.pipe(utils.stripCompression, utils.extIs('.tsv'))

export const load = (handle, callback) =>
fetcher(handle, callback, isTsv(handle.key) ? { query: { sep: '\t' } } : undefined)
)
return children(utils.useErrorHandling(processed, { handle, retry: data.fetch }))
}
12 changes: 6 additions & 6 deletions catalog/app/components/Preview/loaders/Excel.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import * as R from 'ramda'

import AsyncResult from 'utils/AsyncResult'

import { PreviewData } from '../types'
import * as utils from './utils'

export const detect = R.pipe(utils.stripCompression, utils.extIn(['.xls', '.xlsx']))

export const load = utils.previewFetcher('excel', (json) =>
AsyncResult.Ok(
export const Loader = function ExcelLoader({ handle, children }) {
const data = utils.usePreview({ type: 'excel', handle })
const processed = utils.useProcessing(data.result, (json) =>
PreviewData.DataFrame({
preview: json.html,
note: json.info.note,
warnings: json.info.warnings,
}),
),
)
)
return children(utils.useErrorHandling(processed, { handle, retry: data.fetch }))
}
17 changes: 7 additions & 10 deletions catalog/app/components/Preview/loaders/Fcs.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import * as R from 'ramda'

import AsyncResult from 'utils/AsyncResult'

import { PreviewData } from '../types'
import * as utils from './utils'

export const detect = R.pipe(utils.stripCompression, utils.extIs('.fcs'))

export const load = utils.previewFetcher(
'fcs',
R.pipe(
({ html, info }) => ({
export const Loader = function FcsLoader({ handle, children }) {
const data = utils.usePreview({ type: 'fcs', handle })
const processed = utils.useProcessing(data.result, ({ html, info }) =>
PreviewData.Fcs({
preview: html,
metadata: info.metadata,
note: info.note,
warnings: info.warnings,
}),
PreviewData.Fcs,
AsyncResult.Ok,
),
)
)
return children(utils.useErrorHandling(processed, { handle, retry: data.fetch }))
}
25 changes: 10 additions & 15 deletions catalog/app/components/Preview/loaders/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import * as AWS from 'utils/AWS'
import AsyncResult from 'utils/AsyncResult'
import { useIsInStack } from 'utils/BucketConfig'
import useMemoEq from 'utils/useMemoEq'

import { PreviewData } from '../types'

Expand All @@ -10,26 +11,20 @@ import * as utils from './utils'

export const detect = utils.extIn(['.htm', '.html'])

const IFrameLoader = ({ handle, children }) => {
function IFrameLoader({ handle, children }) {
const sign = AWS.Signer.useS3Signer()
const src = React.useMemo(() => sign(handle, { ResponseContentType: 'text/html' }), [
handle.bucket,
handle.key,
handle.version,
sign,
])
return children(AsyncResult.Ok(AsyncResult.Ok(PreviewData.IFrame({ src }))))
const src = useMemoEq([handle, sign], () =>
sign(handle, { ResponseContentType: 'text/html' }),
)
// TODO: issue a head request to ensure existence and get storage class
return children(AsyncResult.Ok(PreviewData.IFrame({ src })))
}

const HtmlLoader = ({ handle, children }) => {
export const Loader = function HtmlLoader({ handle, children }) {
const isInStack = useIsInStack()
return isInStack(handle.bucket) ? (
<IFrameLoader handle={handle}>{children}</IFrameLoader>
<IFrameLoader {...{ handle, children }} />
) : (
Text.load(handle, children)
<Text.Loader {...{ handle, children }} />
)
}

export const load = (handle, callback) => (
<HtmlLoader handle={handle}>{callback}</HtmlLoader>
)
6 changes: 4 additions & 2 deletions catalog/app/components/Preview/loaders/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ import * as utils from './utils'

export const detect = utils.extIn(SUPPORTED_EXTENSIONS)

export const load = (handle, callback) =>
callback(AsyncResult.Ok(AsyncResult.Ok(PreviewData.Image({ handle }))))
// TODO: issue a head request to ensure existance and get storage class
export const Loader = function ImageLoader({ handle, children }) {
return children(AsyncResult.Ok(PreviewData.Image({ handle })))
}
Loading