Skip to content

Commit

Permalink
Catalog: Replace S3 Select with GQL (#4218)
Browse files Browse the repository at this point in the history
  • Loading branch information
nl0 authored Nov 19, 2024
1 parent c65fc4c commit 8dd6815
Show file tree
Hide file tree
Showing 28 changed files with 2,114 additions and 1,385 deletions.
1 change: 1 addition & 0 deletions catalog/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ where verb is one of

## Changes

- [Changed] S3 Select -> GQL API calls for getting access counts ([#4218](https://github.com/quiltdata/quilt/pull/4218))
- [Changed] Athena: improve loading state and errors visuals; fix minor bugs; alphabetize and persist selection in workgroups, catalog names and databases ([#4208](https://github.com/quiltdata/quilt/pull/4208))
- [Changed] Show stack release version in footer ([#4200](https://github.com/quiltdata/quilt/pull/4200))
- [Added] Selective package downloading ([#4173](https://github.com/quiltdata/quilt/pull/4173))
Expand Down
92 changes: 92 additions & 0 deletions catalog/app/containers/Bucket/File/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as dateFns from 'date-fns'
import * as Eff from 'effect'
import * as React from 'react'
import * as M from '@material-ui/core'

import Sparkline from 'components/Sparkline'
import * as GQL from 'utils/GraphQL'
import log from 'utils/Logging'
import * as SVG from 'utils/SVG'
import { readableQuantity } from 'utils/string'

import Section from '../Section'

import ACCESS_COUNTS_QUERY from './gql/ObjectAccessCounts.generated'

const currentYear = new Date().getFullYear()

const formatDate = (date: Date) =>
dateFns.format(date, currentYear === date.getFullYear() ? 'd MMM' : 'd MMM yyyy')

interface AnalyticsProps {
bucket: string
path: string
}

export default function Analytics({ bucket, path }: AnalyticsProps) {
const [cursor, setCursor] = React.useState<number | null>(null)

const result = GQL.useQuery(ACCESS_COUNTS_QUERY, { bucket, key: path })

const data = React.useMemo(() => {
if (result.fetching) return Eff.Option.none()
if (result.error) log.error('Error fetching object access counts:', result.error)
return Eff.Option.some(Eff.Option.fromNullable(result.data?.objectAccessCounts))
}, [result.fetching, result.error, result.data])

const defaultExpanded = Eff.Option.match(data, {
onNone: () => false,
onSome: Eff.Option.match({
onNone: () => false,
onSome: ({ total }) => !!total,
}),
})

return (
<Section icon="bar_charts" heading="Analytics" defaultExpanded={defaultExpanded}>
{Eff.Option.match(data, {
onNone: () => <M.CircularProgress />,
onSome: Eff.Option.match({
onNone: () => <M.Typography>No analytics available</M.Typography>,
onSome: ({ counts, total }) =>
total ? (
<M.Box
display="flex"
width="100%"
justifyContent="space-between"
alignItems="center"
>
<M.Box>
<M.Typography variant="h5">Downloads</M.Typography>
<M.Typography variant="h4" component="div">
{readableQuantity(cursor === null ? total : counts[cursor].value)}
</M.Typography>
<M.Typography variant="overline" component="span">
{cursor === null
? `${counts.length} days`
: formatDate(counts[cursor].date)}
</M.Typography>
</M.Box>
<M.Box width="calc(100% - 7rem)">
<Sparkline
data={counts.map((c) => c.value)}
onCursor={setCursor}
width={1000}
height={60}
stroke={SVG.Paint.Server(
<linearGradient x2="0" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0" stopColor={M.colors.blueGrey[800]} />
<stop offset="100%" stopColor={M.colors.blueGrey[100]} />
</linearGradient>,
)}
/>
</M.Box>
</M.Box>
) : (
<M.Typography>No analytics available</M.Typography>
),
}),
})}
</Section>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react'
import * as Assistant from 'components/Assistant'
import * as XML from 'utils/XML'

import { ObjectExistence } from './requests'
import { ObjectExistence } from '../requests'

interface VersionsContextProps {
data: $TSFixMe
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { basename } from 'path'

import * as dateFns from 'date-fns'
import * as R from 'ramda'
import * as React from 'react'
import { Link, useHistory, useLocation, useParams } from 'react-router-dom'
Expand All @@ -11,7 +10,6 @@ import * as Buttons from 'components/Buttons'
import * as FileEditor from 'components/FileEditor'
import Message from 'components/Message'
import * as Preview from 'components/Preview'
import Sparkline from 'components/Sparkline'
import cfg from 'constants/config'
import * as Bookmarks from 'containers/Bookmarks'
import * as Notifications from 'containers/Notifications'
Expand All @@ -21,23 +19,24 @@ import * as BucketPreferences from 'utils/BucketPreferences'
import { useData } from 'utils/Data'
import MetaTitle from 'utils/MetaTitle'
import * as NamedRoutes from 'utils/NamedRoutes'
import * as SVG from 'utils/SVG'
import { linkStyle } from 'utils/StyledLink'
import copyToClipboard from 'utils/clipboard'
import * as Format from 'utils/format'
import parseSearch from 'utils/parseSearch'
import { up, decode, handleToHttpsUri } from 'utils/s3paths'
import { readableBytes, readableQuantity } from 'utils/string'

import AssistButton from './AssistButton'
import FileCodeSamples from './CodeSamples/File'
import * as AssistantContext from './FileAssistantContext'
import FileProperties from './FileProperties'
import * as FileView from './FileView'
import Section from './Section'
import renderPreview from './renderPreview'
import * as requests from './requests'
import { useViewModes, viewModeToSelectOption } from './viewModes'
import { readableBytes } from 'utils/string'

import AssistButton from '../AssistButton'
import FileCodeSamples from '../CodeSamples/File'
import FileProperties from '../FileProperties'
import * as FileView from '../FileView'
import Section from '../Section'
import renderPreview from '../renderPreview'
import * as requests from '../requests'
import { useViewModes, viewModeToSelectOption } from '../viewModes'

import Analytics from './Analytics'
import * as AssistantContext from './AssistantContext'

const useVersionInfoStyles = M.makeStyles(({ typography }) => ({
version: {
Expand Down Expand Up @@ -203,69 +202,6 @@ function VersionInfo({ bucket, path, version }) {
)
}

function Analytics({ bucket, path }) {
const [cursor, setCursor] = React.useState(null)
const s3 = AWS.S3.use()
const today = React.useMemo(() => new Date(), [])
const formatDate = (date) =>
dateFns.format(
date,
today.getFullYear() === date.getFullYear() ? 'd MMM' : 'd MMM yyyy',
)
const data = useData(requests.objectAccessCounts, { s3, bucket, path, today })

const defaultExpanded = data.case({
Ok: ({ total }) => !!total,
_: () => false,
})

return (
<Section icon="bar_charts" heading="Analytics" defaultExpanded={defaultExpanded}>
{data.case({
Ok: ({ counts, total }) =>
total ? (
<M.Box
display="flex"
width="100%"
justifyContent="space-between"
alignItems="center"
>
<M.Box>
<M.Typography variant="h5">Downloads</M.Typography>
<M.Typography variant="h4" component="div">
{readableQuantity(cursor === null ? total : counts[cursor].value)}
</M.Typography>
<M.Typography variant="overline" component="span">
{cursor === null
? `${counts.length} days`
: formatDate(counts[cursor].date)}
</M.Typography>
</M.Box>
<M.Box width="calc(100% - 7rem)">
<Sparkline
data={R.pluck('value', counts)}
onCursor={setCursor}
width={1000}
height={60}
stroke={SVG.Paint.Server(
<linearGradient x2="0" y2="100%" gradientUnits="userSpaceOnUse">
<stop offset="0" stopColor={M.colors.blueGrey[800]} />
<stop offset="100%" stopColor={M.colors.blueGrey[100]} />
</linearGradient>,
)}
/>
</M.Box>
</M.Box>
) : (
<M.Typography>No analytics available</M.Typography>
),
Err: () => <M.Typography>No analytics available</M.Typography>,
_: () => <M.CircularProgress />,
})}
</Section>
)
}

function CenteredProgress() {
return (
<M.Box textAlign="center" width="100%">
Expand Down
100 changes: 100 additions & 0 deletions catalog/app/containers/Bucket/File/gql/ObjectAccessCounts.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
import * as Types from '../../../../model/graphql/types.generated'

export type containers_Bucket_File_gql_ObjectAccessCountsQueryVariables = Types.Exact<{
bucket: Types.Scalars['String']
key: Types.Scalars['String']
}>

export type containers_Bucket_File_gql_ObjectAccessCountsQuery = {
readonly __typename: 'Query'
} & {
readonly objectAccessCounts: Types.Maybe<
{ readonly __typename: 'AccessCounts' } & Pick<Types.AccessCounts, 'total'> & {
readonly counts: ReadonlyArray<
{ readonly __typename: 'AccessCountForDate' } & Pick<
Types.AccessCountForDate,
'date' | 'value'
>
>
}
>
}

export const containers_Bucket_File_gql_ObjectAccessCountsDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'containers_Bucket_File_gql_ObjectAccessCounts' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'bucket' } },
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
},
},
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'key' } },
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'objectAccessCounts' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'bucket' },
value: { kind: 'Variable', name: { kind: 'Name', value: 'bucket' } },
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'key' },
value: { kind: 'Variable', name: { kind: 'Name', value: 'key' } },
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'window' },
value: { kind: 'IntValue', value: '365' },
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'total' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'counts' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'date' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
],
},
},
],
},
},
],
} as unknown as DocumentNode<
containers_Bucket_File_gql_ObjectAccessCountsQuery,
containers_Bucket_File_gql_ObjectAccessCountsQueryVariables
>

export { containers_Bucket_File_gql_ObjectAccessCountsDocument as default }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
query ($bucket: String!, $key: String!) {
objectAccessCounts(bucket: $bucket, key: $key, window: 365) {
total
counts {
date
value
}
}
}
1 change: 1 addition & 0 deletions catalog/app/containers/Bucket/File/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './File'
Loading

0 comments on commit 8dd6815

Please sign in to comment.