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

Data service: better detect file formats from WFS outputs #786

Merged
merged 2 commits into from
Feb 1, 2024
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
1 change: 1 addition & 0 deletions libs/common/domain/src/lib/model/record/metadata.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface DatasetDownloadDistribution {
// textEncoding?: string
name?: string
description?: string
accessServiceProtocol?: ServiceProtocol
}

export interface OnlineLinkResource {
Expand Down
32 changes: 16 additions & 16 deletions libs/feature/dataviz/src/lib/service/data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'
import { DataService } from './data.service'
import { openDataset } from '@geonetwork-ui/data-fetcher'
import { PROXY_PATH } from '@geonetwork-ui/util/shared'
import { firstValueFrom, lastValueFrom } from 'rxjs'
import { lastValueFrom } from 'rxjs'

const newEndpointCall = jest.fn()

Expand Down Expand Up @@ -160,7 +160,7 @@ describe('DataService', () => {
),
type: 'service',
accessServiceProtocol: 'wfs',
}
} as const
describe('WFS unreachable (CORS)', () => {
it('throws a relevant error', async () => {
try {
Expand Down Expand Up @@ -233,7 +233,7 @@ describe('DataService', () => {
url: new URL(
'http://local/wfs?GetFeature&FeatureType=surval_parametre_ligne&format=csv'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
Expand All @@ -243,7 +243,7 @@ describe('DataService', () => {
url: new URL(
'http://local/wfs?GetFeature&FeatureType=surval_parametre_ligne&format=xls'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
Expand All @@ -253,17 +253,17 @@ describe('DataService', () => {
url: new URL(
'http://local/wfs?GetFeature&FeatureType=surval_parametre_ligne&format=json'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
description: 'Lieu de surveillance (ligne)',
mimeType: 'gml',
mimeType: 'application/gml+xml',
name: 'surval_parametre_ligne',
url: new URL(
'http://local/wfs?GetFeature&FeatureType=surval_parametre_ligne&format=gml'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
])
Expand All @@ -286,7 +286,7 @@ describe('DataService', () => {
url: new URL(
'http://local/wfs?GetFeature&FeatureType=nojson_type&format=csv'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
Expand All @@ -296,17 +296,17 @@ describe('DataService', () => {
url: new URL(
'http://local/wfs?GetFeature&FeatureType=nojson_type&format=xls'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
description: 'Lieu de surveillance (ligne)',
mimeType: 'gml',
mimeType: 'application/gml+xml',
name: 'nojson_type',
url: new URL(
'http://local/wfs?GetFeature&FeatureType=nojson_type&format=gml'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
])
Expand All @@ -329,7 +329,7 @@ describe('DataService', () => {
url: new URL(
'http://unique-feature-type/wfs?GetFeature&FeatureType=myOnlyOne&format=csv'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
Expand All @@ -339,7 +339,7 @@ describe('DataService', () => {
url: new URL(
'http://unique-feature-type/wfs?GetFeature&FeatureType=myOnlyOne&format=xls'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
Expand All @@ -350,17 +350,17 @@ describe('DataService', () => {
'http://unique-feature-type/wfs?GetFeature&FeatureType=myOnlyOne&format=json'
),

type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
{
description: 'Lieu de surveillance (ligne)',
mimeType: 'gml',
mimeType: 'application/gml+xml',
name: '',
url: new URL(
'http://unique-feature-type/wfs?GetFeature&FeatureType=myOnlyOne&format=gml'
),
type: 'service',
type: 'download',
accessServiceProtocol: 'wfs',
},
])
Expand Down
13 changes: 8 additions & 5 deletions libs/feature/dataviz/src/lib/service/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
SupportedTypes,
} from '@geonetwork-ui/data-fetcher'
import {
extensionToFormat,
getFileFormat,
getFileFormatFromServiceOutput,
getMimeTypeForFormat,
ProxyService,
} from '@geonetwork-ui/util/shared'
Expand Down Expand Up @@ -45,7 +45,7 @@ interface WfsDownloadUrls {
export class DataService {
constructor(private proxy: ProxyService) {}

private getDownloadUrlsFromWfs(
getDownloadUrlsFromWfs(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These changes were made to avoid TS errors in the corresponding test file

wfsUrl: string,
featureTypeName: string
): Observable<WfsDownloadUrls> {
Expand Down Expand Up @@ -120,7 +120,7 @@ export class DataService {
)
}

private getDownloadUrlFromEsriRest(apiUrl: string, format: string): string {
getDownloadUrlFromEsriRest(apiUrl: string, format: string): string {
return this.proxy.getProxiedUrl(
`${apiUrl}/query?f=${format}&where=1=1&outFields=*`
)
Expand All @@ -138,8 +138,11 @@ export class DataService {
map((urls) =>
Object.keys(urls).map((format) => ({
...wfsLink,
type: 'download',
url: new URL(urls[format]),
mimeType: getMimeTypeForFormat(extensionToFormat(format)) || format,
mimeType: getMimeTypeForFormat(
getFileFormatFromServiceOutput(format)
),
}))
)
)
Expand All @@ -153,7 +156,7 @@ export class DataService {
url: new URL(
this.getDownloadUrlFromEsriRest(esriRestLink.url.toString(), format)
),
mimeType: getMimeTypeForFormat(extensionToFormat(format)) || format,
mimeType: getMimeTypeForFormat(getFileFormatFromServiceOutput(format)),
}))
}

Expand Down
45 changes: 36 additions & 9 deletions libs/util/shared/src/lib/links/link-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { LINK_FIXTURES } from '@geonetwork-ui/common/fixtures'
import {
checkFileFormat,
extensionToFormat,
FORMATS,
getBadgeColor,
getFileFormat,
getFileFormatFromServiceOutput,
getLinkLabel,
mimeTypeToFormat,
getLinkPriority,
mimeTypeToFormat,
} from './link-utils'
import { DatasetDownloadDistribution } from '@geonetwork-ui/common/domain/model/record'

Expand Down Expand Up @@ -50,7 +50,7 @@ describe('link utils', () => {
expect(getFileFormat(LINK_FIXTURES.geodataShp)).toEqual('shp')
})
})
describe('for a shapefile link withe MimeType', () => {
describe('for a shapefile link with MimeType', () => {
it('returns shp format', () => {
expect(getFileFormat(LINK_FIXTURES.geodataShpWithMimeType)).toEqual(
'shp'
Expand Down Expand Up @@ -165,11 +165,38 @@ describe('link utils', () => {
}
)
})
describe('#extensionToFormat for an XLS extension', () => {
it('returns excel format', () => {
expect(extensionToFormat('XLS')).toEqual('excel')
})

describe('#getFileFormatFromServiceOutput', () => {
// service output, recognized file format
const toTest = [
['SHAPE-ZIP', 'shp'],
['application/vnd.google-earth.kml xml', 'kml'],
['KML', 'kml'],
['excel2007', 'excel'],
['XLS', 'excel'],
['gml2', 'gml'],
['gml3', 'gml'],
['text/xml; subtype=gml/3.1.1', 'gml'],
['gml32', 'gml'],
['DXF', 'dxf'],
['DXF-ZIP', 'zip'],
['json', 'json'],
['geojson', 'geojson'],
['Acbd', null],
]

describe.each(toTest)(
'service output=%s, recognized file format=%s',
(serviceOutput, fileFormat) => {
it('returns the correct file format', () => {
expect(getFileFormatFromServiceOutput(serviceOutput)).toEqual(
fileFormat
)
})
}
)
})

describe('#getBadgeColor for format', () => {
it('returns #1e5180', () => {
expect(getBadgeColor('json')).toEqual('#1e5180')
Expand All @@ -190,15 +217,15 @@ describe('link utils', () => {
})
).toEqual(nFormats - 1)
})
it(`returns ${nFormats - 5}`, () => {
it(`returns ${nFormats - 6}`, () => {
expect(
getLinkPriority({
description: 'Data in KML format',
name: 'abc.kml',
url: new URL('https://my.server/files/abc.kml'),
type: 'download',
})
).toEqual(nFormats - 5)
).toEqual(nFormats - 6)
})
})
describe('#checkFileFormat', () => {
Expand Down
45 changes: 34 additions & 11 deletions libs/util/shared/src/lib/links/link-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ export const FORMATS = {
color: '#328556',
mimeTypes: ['x-gis/x-shapefile'],
},
gml: {
extensions: ['gml'],
priority: 5,
color: '#c92bce',
mimeTypes: ['application/gml+xml', 'text/xml; subtype=gml'],
},
kml: {
extensions: ['kml', 'kmz'],
priority: 5,
priority: 6,
color: '#348009',
mimeTypes: [
'application/vnd.google-earth.kml+xml',
Expand All @@ -54,34 +60,40 @@ export const FORMATS = {
},
gpkg: {
extensions: ['gpkg', 'geopackage'],
priority: 6,
priority: 7,
color: '#ea79ba',
mimeTypes: ['application/geopackage+sqlite3'],
},
zip: {
extensions: ['zip', 'tar.gz'],
priority: 7,
priority: 8,
color: '#f2bb3a',
mimeTypes: ['application/zip', 'application/x-zip'],
},
pdf: {
extensions: ['pdf'],
priority: 8,
priority: 9,
color: '#db544a',
mimeTypes: ['application/pdf'],
},
jpg: {
extensions: ['jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp'],
priority: 8,
priority: 9,
color: '#673ab7',
mimeTypes: ['image/jpg'],
},
svg: {
extensions: ['svg'],
priority: 9,
priority: 10,
color: '#d98294',
mimeTypes: ['image/svg+xml'],
},
dxf: {
extensions: ['dxf'],
priority: 11,
color: '#de630b',
mimeTypes: ['application/x-dxf', 'image/x-dxf'],
},
} as const

export type FileFormat = keyof typeof FORMATS
Expand All @@ -102,13 +114,24 @@ export function getLinkPriority(link: DatasetDistribution): number {
return getFormatPriority(getFileFormat(link))
}

export function extensionToFormat(extension: string): FileFormat {
for (const format in FORMATS) {
for (const alias of FORMATS[format].extensions) {
if (alias === extension.toLowerCase()) return format as FileFormat
export function getFileFormatFromServiceOutput(
serviceOutput: string
): FileFormat | null {
function formatMatcher(format: typeof FORMATS[FileFormat]): boolean {
const output = serviceOutput.toLowerCase()
return (
format.extensions.some((extension: string) =>
output.includes(extension)
) ||
format.mimeTypes.some((mimeType: string) => output.includes(mimeType))
)
}
for (const formatName in FORMATS) {
if (formatMatcher(FORMATS[formatName])) {
return formatName as FileFormat
}
}
return undefined
return null
}

export function getFileFormat(link: DatasetDistribution): FileFormat {
Expand Down
Loading