Skip to content

Commit

Permalink
JS-based validation with results #307
Browse files Browse the repository at this point in the history
  • Loading branch information
m-mohr committed Mar 15, 2024
1 parent 47dea4f commit eb509c4
Show file tree
Hide file tree
Showing 18 changed files with 424 additions and 216 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ You need to provide a field `stac_browser` and then you can set any of the follo
- `defaultThumbnailSize`
- `displayGeoTiffByDefault`
- `showThumbnailsAsAssets`
- `stacLint` (can only be disabled)

### Custom extensions

Expand Down
1 change: 0 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ module.exports = {
showKeywordsInItemCards: false,
showKeywordsInCatalogCards: false,
showThumbnailsAsAssets: false,
stacLint: true,
geoTiffResolution: 128,
redirectLegacyUrls: false,
itemsPerPage: 12,
Expand Down
5 changes: 0 additions & 5 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,6 @@
"boolean"
]
},
"stacLint": {
"type": [
"boolean"
]
},
"geoTiffResolution": {
"type": [
"integer"
Expand Down
13 changes: 0 additions & 13 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ The following ways to set config options are possible:
* [locale](#locale)
* [fallbackLocale](#fallbacklocale)
* [supportedLocales](#supportedlocales)
* [stacLint](#staclint)
* [historyMode](#historymode)
* [pathPrefix](#pathprefix)
* [stacProxyUrl](#stacproxyurl)
Expand Down Expand Up @@ -115,18 +114,6 @@ In CLI, please provide the languages separated by a space, e.g. `--supportedLoca
Please note that only left-to-right languages have been tested.
I'd need help to test support for right-to-left languages.

## stacLint

***experimental***

Enables or disables a feature that validates the STAC catalog when opening the "Source Data" popup.
Validation uses the external service [staclint.com](https://staclint.com).

Validation is automatically disabled in the following cases:
- the host of a catalog is `localhost`, `127.0.0.1` or `::1`
- [private query parameters](../README.md#private-query-parameters) have been set
- `stacProxyUrl` is set

## historyMode

***build-only option***
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"node-polyfill-webpack-plugin": "^2.0.0",
"remove-markdown": "^0.5.0",
"stac-layer": "^0.15.0",
"stac-node-validator": "^2.0.0-beta.5",
"urijs": "^1.19.11",
"v-clipboard": "^3.0.0-next.1",
"vue": "^2.6.12",
Expand Down
12 changes: 3 additions & 9 deletions src/StacBrowser.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<b-container id="stac-browser">
<Authentication v-if="doAuth.length > 0" />
<ErrorAlert class="global-error" v-if="globalError" v-bind="globalError" @close="hideError" />
<ErrorAlert v-if="globalError" dismissible class="global-error" v-bind="globalError" @close="hideError" />
<Sidebar v-if="sidebar" />
<!-- Header -->
<header>
Expand Down Expand Up @@ -227,8 +227,7 @@ export default {
'crossOriginMedia',
'defaultThumbnailSize',
'displayGeoTiffByDefault',
'showThumbnailsAsAssets',
'stacLint' // can only be disabled
'showThumbnailsAsAssets'
];
let doReset = !root || (oldRoot && Utils.isObject(oldRoot['stac_browser']));
Expand All @@ -242,11 +241,6 @@ export default {
if (doSet && typeof root['stac_browser'][key] !== 'undefined') {
value = root['stac_browser'][key]; // Custom value from root
}
// Don't enable stacLint if it has been disabled by default
if (key === 'stacLint' && !CONFIG.stacLint) {
continue;
}
// Commit config
if (typeof value !== 'undefined') {
Expand Down Expand Up @@ -392,4 +386,4 @@ export default {
@import '~bootstrap-vue/src/index.scss';
@import "./theme/page.scss";
@import "./theme/custom.scss";
</style>
</style>
4 changes: 2 additions & 2 deletions src/components/ErrorAlert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default {
},
dismissible: {
type: Boolean,
default: true
default: false
}
}
};
Expand All @@ -72,4 +72,4 @@ export default {
dl {
font-size: 0.9em;
}
</style>
</style>
4 changes: 2 additions & 2 deletions src/components/SearchFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ export default {
},
andOrOptions() {
return [
{ value: 'and', text: this.$i18n.t('search.logical.and') },
{ value: 'or', text: this.$i18n.t('search.logical.or') },
{ value: 'and', text: this.$t('search.logical.and') },
{ value: 'or', text: this.$t('search.logical.or') },
];
},
showAdditionalFilters() {
Expand Down
43 changes: 7 additions & 36 deletions src/components/Source.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@

<b-popover
v-if="stacUrl" id="popover-link" target="popover-link-btn" triggers="focus"
placement="bottom" container="stac-browser" :title="$t('source.title')"
@show="validate"
placement="bottom" container="stac-browser" :title="$t('source.title')"
>
<template v-if="stac">
<b-row v-if="stacId" class="stac-id">
Expand All @@ -53,13 +52,10 @@
<b-col cols="4">{{ $t('source.stacVersion') }}</b-col>
<b-col>{{ stacVersion }}</b-col>
</b-row>
<b-row v-if="canValidate" class="validation">
<b-row class="stac-valid">
<b-col cols="4">{{ $t('source.valid') }}</b-col>
<b-col>
<b-spinner v-if="valid === null" :label="$t('source.validating')" small />
<template v-else-if="valid === true">✔️</template>
<template v-else-if="valid === false">❌</template>
<template v-else>{{ $t('source.validationNA') }}</template>
<Validation :data="stac" />
</b-col>
</b-row>
<hr>
Expand All @@ -84,7 +80,6 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import Url from './Url.vue';
import URI from 'urijs';
import Utils from '../utils';
import { getBest, prepareSupported } from '../locale-id';
import CopyButton from './CopyButton.vue';
Expand All @@ -109,6 +104,7 @@ export default {
RootStats: () => import('./RootStats.vue'),
Url,
CopyButton,
Validation: () => import('./Validation.vue')
},
props: {
title: {
Expand All @@ -125,7 +121,7 @@ export default {
}
},
computed: {
...mapState(['conformsTo', 'dataLanguages', 'locale', 'privateQueryParameters', 'supportedLocales', 'stacLint', 'stacProxyUrl', 'uiLanguage', 'valid']),
...mapState(['conformsTo', 'dataLanguages', 'locale', 'supportedLocales', 'uiLanguage', 'valid']),
...mapGetters(['supportsExtension', 'root']),
stacVersion() {
return this.stac?.stac_version;
Expand Down Expand Up @@ -154,26 +150,6 @@ export default {
return '-';
}
},
canValidate() {
if (!this.stacLint || typeof this.stacUrl !== 'string') {
return false;
}
else if (Utils.size(this.privateQueryParameters) > 0) {
// Don't expose private query parameters to externals
return false;
}
else if (Array.isArray(this.stacProxyUrl)) {
// Don't validate if a proxy has been set
return false;
}
let uri = URI(this.stacUrl);
let host = uri.hostname().toLowerCase();
if (host === 'localhost' || host.startsWith('127.') || host === '::1') {
// Can't validate localhost
return false;
}
return true;
},
message() {
return this.$t('source.share.message', {title: this.title, url: this.browserUrl()});
},
Expand Down Expand Up @@ -235,12 +211,6 @@ export default {
},
methods: {
...mapActions(['switchLocale']),
async validate() {
if (!this.canValidate) {
return;
}
await this.$store.dispatch('validate', this.stacUrl);
},
browserUrl() {
return window.location.toString();
}
Expand All @@ -260,7 +230,8 @@ export default {
}
}
#popover-link .stac-id .copy-button {
#popover-link .stac-id .btn-sm,
#popover-link .stac-valid .btn-sm {
padding-top: 0.1rem;
padding-bottom: 0.1rem;
font-size: 0.7rem;
Expand Down
73 changes: 73 additions & 0 deletions src/components/Validation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div class="valid">
<b-spinner v-if="working" :label="$t('source.validating')" small />
<b-button v-else-if="valid === true" variant="success" :size="size" :to="validationLink"><b-icon-check /> {{ $t('checkbox.true') }}</b-button>
<b-button v-else-if="valid === false" variant="danger" :size="size" :to="validationLink"><b-icon-x /> {{ $t('checkbox.false') }}</b-button>
<template v-else>{{ $t('source.validationNA') }}</template>
</div>
</template>

<script>
import STAC from '../models/stac.js';
import validateSTAC from 'stac-node-validator';
import { BIconCheck, BIconX } from 'bootstrap-vue';
import { mapGetters } from 'vuex';
export default {
name: "Validation",
components: {
BIconCheck,
BIconX
},
props: {
data: {
type: Object,
default: null
},
size: {
type: String,
default: "sm"
}
},
data() {
return {
working: true,
valid: null
};
},
computed: {
...mapGetters(['toBrowserPath']),
validationLink() {
if (this.data instanceof STAC) {
return '/validation' + this.toBrowserPath(this.data.getAbsoluteUrl());
}
else {
return null;
}
}
},
async created() {
await this.validate();
},
methods: {
async validate() {
this.working = true;
this.valid = null;
try {
if (this.data instanceof STAC) {
const report = await validateSTAC(this.data);
this.valid = report.valid;
}
} catch (error) {
console.error(error);
} finally {
this.working = false;
}
}
}
};
</script>

<style>
</style>
84 changes: 84 additions & 0 deletions src/components/ValidationResult.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<template>
<b-card no-body :header="header">
<b-list-group flush>
<b-list-group-item v-if="errors.length === 0" variant="success">
{{ $t('source.valid') }}
</b-list-group-item>
<template v-else>
<b-list-group-item v-for="(error, i) in errors" :key="i" variant="danger">
{{ makeAjvErrorMessage(error) }}
</b-list-group-item>
</template>
</b-list-group>
</b-card>
</template>

<script>
import { BListGroup, BListGroupItem } from 'bootstrap-vue';
import URI from 'urijs';
import Utils from '../utils';
export default {
name: "ValidationResult",
components: {
BListGroup,
BListGroupItem
},
props: {
type: {
type: String,
required: true
},
title: {
type: String,
required: true
},
errors: {
type: Array,
required: true
}
},
computed: {
header() {
if (this.title.includes('://')) {
let uri = URI(this.title);
return uri.directory().replace(/\//g, ' ').trim();
}
return this.title;
}
},
methods: {
// ToDo: Import from stac-node-validator
makeAjvErrorMessage(error) {
let message = error.message;
if (Utils.isObject(error.params) && Object.keys(error.params).length > 0) {
let params = Object.entries(error.params)
.map(([key, value]) => {
let label = key.replace(/([^A-Z]+)([A-Z])/g, "$1 $2").toLowerCase();
return `${label}: ${value}`;
})
.join(', ');
message += ` (${params})`;
}
if (error.instancePath) {
return `${error.instancePath} ${message}`;
}
else if (error.schemaPath) {
return this.$t('messageForSchemaError', {message, schemaPath: error.schemaPath});
}
else if (message) {
return message;
}
else {
return String(error);
}
}
}
};
</script>

<style lang="scss">
#stac-browser .validation .results .card-header {
text-transform: capitalize;
}
</style>
Loading

0 comments on commit eb509c4

Please sign in to comment.