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

fixed some small bugs #840

Merged
merged 5 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
101 changes: 82 additions & 19 deletions src/components/download/DataDownloadDisplayComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@
<v-col>
<v-autocomplete
density="compact"
:item-title="(item) => getParameterName(item)"
:item-title="(item) => getParameterQualifierName(item)"
return-object
v-model="selectedParameters"
v-model="selectedParameterQualifiers"
multiple
single-line
hide-details
rounded="0"
label="Parameters"
:items="parameters"
:items="parameterQualifiers"
>
<template v-slot:selection="{ item, index }">
<span v-if="index < 4">{{ item.title }}</span>
<span v-if="index == 4"
>... ({{ selectedParameters.length }} selected)</span
>... ({{ selectedParameterQualifiers.length }} selected)</span
>
</template>
</v-autocomplete>
Expand Down Expand Up @@ -156,7 +156,11 @@ import { createTransformRequestFn } from '@/lib/requests/transformRequest.ts'
import TimeSeriesFileDownloadComponent from '@/components/download/TimeSeriesFileDownloadComponent.vue'
import { UseDisplayConfigOptions } from '@/services/useDisplayConfig'
import { useUserSettingsStore } from '@/stores/userSettings.ts'
import { filterToParams } from '@deltares/fews-wms-requests'
import { TimeSeriesResult } from '@deltares/fews-pi-requests'
import { ParameterQualifiersHeader } from '@/lib/download/types'
import { isEqual } from 'lodash-es'
import { filterToParams } from '@/lib/download/downloadFiles.ts'
import { DataDownloadFilter } from '@/lib/download/types/DataDownloadFilter.ts'

interface Props {
nodeId?: string | string[]
Expand All @@ -174,7 +178,7 @@ const options = computed<UseDisplayConfigOptions>(() => {
})
const downloadStartTime = ref<Date>()
const downloadEndTime = ref<Date>()
const timeSeriesFilter = ref<TimeSeriesFilter>()
const timeSeriesFilter = ref<DataDownloadFilter>()

const baseUrl = configManager.get('VITE_FEWS_WEBSERVICES_URL')
const piProvider = new PiWebserviceProvider(baseUrl, {
Expand All @@ -185,18 +189,35 @@ const filterId = props.topologyNode.filterIds
? props.topologyNode.filterIds[0]
: undefined
const allLocations = ref<Location[]>([])
const allParameters = ref<TimeSeriesParameter[]>([])
const locations = ref<Location[]>([])
const selectedLocations = ref<Location[]>([])

const parameters = ref<TimeSeriesParameter[]>([])
const selectedParameters = ref<TimeSeriesParameter[]>([])
const parameterQualifiers = ref<ParameterQualifiersHeader[]>([])
const selectedParameterQualifiers = ref<ParameterQualifiersHeader[]>([])

allLocations.value = await getLocations()
locations.value = allLocations.value
selectedLocations.value = allLocations.value

parameters.value = await getParameters()
selectedParameters.value = parameters.value
const timeSeriesResults = await getTimeSeriesHeaders()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-level await blocks setting up the component until the request finishes, is this what we want?

In any case, since the fetches are independent, they can be executed concurrently with Promise.all or Promise.allSettled.

allParameters.value = await getParameters()
const parameterQualifiersHeaders: ParameterQualifiersHeader[] = []
timeSeriesResults?.forEach((timeSeriesResult) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be simplified with a .map and a lodash uniqWith maybe

const parameterQualifiersHeader: ParameterQualifiersHeader = {
parameterId: timeSeriesResult.header?.parameterId,
qualifiers: timeSeriesResult.header?.qualifierId,
}
const existing = parameterQualifiersHeaders.find(
(item) =>
item.parameterId === timeSeriesResult.header?.parameterId &&
isEqual(item.qualifiers, timeSeriesResult.header?.qualifierId),
)
if (existing) return
parameterQualifiersHeaders.push(parameterQualifiersHeader)
})
parameterQualifiers.value = parameterQualifiersHeaders
selectedParameterQualifiers.value = parameterQualifiersHeaders

const selectableAttributes = ref<string[][]>([])
selectableAttributes.value = getAttributeValues(allLocations.value)
Expand Down Expand Up @@ -338,10 +359,10 @@ function validateUserInput(
)
newErrors.push('Select one or more locations')
if (
selectedParameters.value === undefined ||
selectedParameters.value.length == 0
selectedParameterQualifiers.value === undefined ||
selectedParameterQualifiers.value.length == 0
)
newErrors.push('Select one or more parameters')
newErrors.push('Select one or more parameters/qualifiers')
if (endDate.value < startDate.value) {
newErrors.push('The end date should be greater than the start date')
}
Expand All @@ -360,10 +381,21 @@ function downloadData() {
errors.value = validateUserInput(startTimeRequest, endTimeRequest)
if (errors.value.length !== 0) return

const parameterIds = selectedParameterQualifiers.value.map(
(parameterQualifier) => parameterQualifier.parameterId,
)
const qualifiersIds = selectedParameterQualifiers.value
.filter((parameterQualifier) => parameterQualifier.qualifiers !== undefined)
.map((parameterQualifier) => parameterQualifier.qualifiers)
.flatMap((item) => item)

timeSeriesFilter.value = {
filterId: filterId,
locationIds: selectedLocations.value.map((location) => location.locationId),
parameterIds: selectedParameters.value.map((parameter) => parameter.id),
locationIds: selectedLocations.value
.map((location) => location.locationId)
.join(','),
parameterIds: [...new Set(parameterIds)].join(','),
qualifierIds: [...new Set(qualifiersIds)].join(','),
}
const queryParameters = filterToParams(timeSeriesFilter.value)
if (queryParameters.length > 7500) {
Expand All @@ -387,15 +419,46 @@ function getLocationName(location: Location): string {
return location.locationId
}

function getParameterName(parameter: TimeSeriesParameter): string {
const showParameterName =
props.topologyNode.dataDownloadDisplay?.showParameterName
function getParameterName(
parameters: TimeSeriesParameter[],
parameterQualifiersHeader: ParameterQualifiersHeader,
showParameterName: string | undefined,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe showParameterName?: 'name' | 'short name'

) {
const parameter = parameters.find(
(item) => item.id === parameterQualifiersHeader.parameterId,
)
if (parameter === undefined) return ''
if (showParameterName === 'name') return parameter.name ?? parameter.id
if (showParameterName === 'short name')
return parameter.shortName ?? parameter.id
return parameter.id
}

function getParameterQualifierName(
parameterQualifiersHeader: ParameterQualifiersHeader,
): string {
const showParameterName =
props.topologyNode.dataDownloadDisplay?.showParameterName
const parameterName = getParameterName(
allParameters.value,
parameterQualifiersHeader,
showParameterName,
)
if (parameterQualifiersHeader.qualifiers === undefined) return parameterName
const qualifiers = ' (' + parameterQualifiersHeader.qualifiers.join(',') + ')'
return parameterName + qualifiers
}

async function getTimeSeriesHeaders(): Promise<TimeSeriesResult[] | undefined> {
const filter: TimeSeriesFilter = {
onlyHeaders: true,
filterId: filterId,
documentFormat: DocumentFormat.PI_JSON,
}
const timeSeriesResponse = await piProvider.getTimeSeries(filter)
return timeSeriesResponse.timeSeries
}

async function getLocations(): Promise<Location[]> {
const filter: LocationsFilter = {
showAttributes: true,
Expand Down Expand Up @@ -434,7 +497,7 @@ function getAttributeValues(locations: Location[]): string[][] {
return attributeValuesMap
}

async function getParameters() {
async function getParameters(): Promise<TimeSeriesParameter[]> {
if (props.topologyNode?.filterIds === undefined) return []
const filter: ParametersFilter = {
filterId: filterId,
Expand Down
25 changes: 17 additions & 8 deletions src/components/download/TimeSeriesFileDownloadComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ import type { UseDisplayConfigOptions } from '@/services/useDisplayConfig'
import { authenticationManager } from '@/services/authentication/AuthenticationManager.ts'
import { filterToParams } from '@deltares/fews-wms-requests'
import { downloadFileAttachment } from '@/lib/download/downloadFiles.ts'
import { ref, computed, toValue } from 'vue'
import { computed, onUpdated, ref, toValue } from 'vue'
import { useSystemTimeStore } from '@/stores/systemTime.ts'
import { UseTimeSeriesOptions } from '@/services/useTimeSeries'
import { DateTime } from 'luxon'
import { DataDownloadFilter } from '@/lib/download/types/DataDownloadFilter.ts'

const store = useSystemTimeStore()
const viewPeriodFromStore = computed<UseTimeSeriesOptions>(() => {
Expand All @@ -74,7 +75,7 @@ interface Props {
filter?:
| filterActionsFilter
| timeSeriesGridActionsFilter
| TimeSeriesFilter
| DataDownloadFilter
| undefined
startTime?: Date | undefined
endTime?: Date | undefined
Expand All @@ -91,26 +92,34 @@ const props = defineProps<Props>()
const model = defineModel<boolean>()

const fileType = ref<keyof typeof fileTypes>('csv')
const fileName = ref('timeseries')

const cancelDialog = () => {
model.value = false
}
const fileName = ref('timeseries')
onUpdated(() => {
if (!model.value) return
let dateValue = new Date()
const FILE_FORMAT_DATE_FMT = 'yyyyMMddHHmmss'
const defaultDateTimeString =
DateTime.fromJSDate(dateValue).toFormat(FILE_FORMAT_DATE_FMT)
fileName.value = `timeseries_${defaultDateTimeString}`
})

function isTimeSeriesGridActionsFilter(
filter: filterActionsFilter | timeSeriesGridActionsFilter | undefined,
): filter is timeSeriesGridActionsFilter {
return (filter as timeSeriesGridActionsFilter).x !== undefined
}

function isTimeSeriesFilter(
function isDataDownloadFilter(
filter:
| filterActionsFilter
| timeSeriesGridActionsFilter
| TimeSeriesFilter
| undefined,
): filter is TimeSeriesFilter {
return (filter as TimeSeriesFilter).filterId !== undefined
): filter is DataDownloadFilter {
return (filter as DataDownloadFilter).filterId !== undefined
}

function isFilterActionsFilter(
Expand Down Expand Up @@ -155,10 +164,10 @@ function determineViewPeriod(): string {
const downloadFile = (downloadFormat: string) => {
let viewPeriod = determineViewPeriod()
if (props.filter) {
if (isTimeSeriesFilter(props.filter)) {
if (isDataDownloadFilter(props.filter)) {
const queryParameters = filterToParams(props.filter)
const url = new URL(
`${baseUrl}rest/fewspiservice/v1/timeseries/filters/actions${queryParameters}&documentFormat=${downloadFormat}${viewPeriod}`,
`${baseUrl}rest/fewspiservice/v1/timeseries${queryParameters}&documentFormat=${downloadFormat}${viewPeriod}`,
)
return downloadFileAttachment(
url.href,
Expand Down
10 changes: 10 additions & 0 deletions src/lib/download/downloadFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ async function downloadFileWithFetch(
}
}

export function filterToParams(filter: Record<string, any>): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're making a new version of filterToParams, we could also consider using the URL API for query parameters, which might be a bit nicer to work with than strings?

const filterArgs = Object.entries(filter).flatMap(([key, value]) => {
if (value === undefined) return []

const encodedValue = encodeURIComponent(value)
return [`${key}=${encodedValue}`]
})

return filterArgs.length ? '?' + filterArgs.join('&') : ''
}
export async function downloadFileAttachment(
url: string,
fileName: string,
Expand Down
6 changes: 6 additions & 0 deletions src/lib/download/types/DataDownloadFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface DataDownloadFilter {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this extend a TimeSeriesFilter (or something), since we are using it on the FEWS PI time series end point?

filterId: string | undefined
parameterIds: string | undefined
locationIds: string | undefined
qualifierIds: string | undefined
}
4 changes: 4 additions & 0 deletions src/lib/download/types/ParameterQualifiersHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ParameterQualifiersHeader {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does @deltares/fews-pi-requests not have an equivalent type? Maybe you can use Pick to extract a subset of this type, to guarantee that the types remain consistent.

parameterId: string | undefined
qualifiers: string[] | undefined
}
2 changes: 2 additions & 0 deletions src/lib/download/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ParameterQualifiersHeader.ts'
export * from './DataDownloadFilter.ts'
Loading