diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6a4d539..a78d077 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,7 @@ env: VITE_APP_GOOGLE_MAPS_API_KEY: "" VITE_APP_SUPPORT_EMAIL: help@example.com VITE_APP_CLIENT_ID: APP-4ZA8C8BYAH3QHNE9 + SEARCH_RELEVANCE_SCORE_THRESHOLD: 1.4 jobs: @@ -36,7 +37,7 @@ jobs: DB_USERNAME: ${{ secrets.DB_USERNAME }} DB_PASSWORD: ${{ secrets.DB_PASSWORD }} run: | - variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL") + variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL" "SEARCH_RELEVANCE_SCORE_THRESHOLD") # Empty the .env file > .env diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 3a3cce5..bc4ec88 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -6,6 +6,6 @@ version: 2 updates: - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests + directory: "/frontend" # Location of package manifests schedule: interval: "weekly" diff --git a/.github/workflows/deploy-dev.yaml b/.github/workflows/deploy-dev.yaml index 7cb8c34..c764159 100644 --- a/.github/workflows/deploy-dev.yaml +++ b/.github/workflows/deploy-dev.yaml @@ -21,6 +21,7 @@ env: VITE_APP_LOGIN_URL: https://orcid.org/oauth/authorize VITE_APP_SUPPORT_EMAIL: help@example.com VITE_APP_CLIENT_ID: APP-4ZA8C8BYAH3QHNE9 + SEARCH_RELEVANCE_SCORE_THRESHOLD: 1.4 jobs: @@ -44,7 +45,7 @@ jobs: DB_USERNAME: ${{ secrets.DB_USERNAME_BETA }} DB_PASSWORD: ${{ secrets.DB_PASSWORD_BETA }} run: | - variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL") + variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL" "SEARCH_RELEVANCE_SCORE_THRESHOLD") # Empty the .env file > .env diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 8c6291e..63ca374 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -22,6 +22,7 @@ env: VITE_APP_LOGIN_URL: https://orcid.org/oauth/authorize VITE_APP_SUPPORT_EMAIL: help@example.com VITE_APP_CLIENT_ID: APP-4ZA8C8BYAH3QHNE9 + SEARCH_RELEVANCE_SCORE_THRESHOLD: 1.4 jobs: @@ -45,7 +46,7 @@ jobs: DB_USERNAME: ${{ secrets.DB_USERNAME }} DB_PASSWORD: ${{ secrets.DB_PASSWORD }} run: | - variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL") + variables=("OIDC_ISSUER" "DB_USERNAME" "DB_PASSWORD" "DB_HOST" "DATABASE_NAME" "DB_PROTOCOL" "TESTING" "VITE_APP_LOGIN_URL" "HYDROSHARE_META_READ_URL" "HYDROSHARE_FILE_READ_URL" "SEARCH_RELEVANCE_SCORE_THRESHOLD") # Empty the .env file > .env diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0bad23b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.detectIndentation": true +} \ No newline at end of file diff --git a/README.md b/README.md index d6cce32..1f4a0d2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,30 @@ -# I-GUIDE catalog API -I-GUIDE Catalog API +# I-GUIDE Catalog + +The I-GUIDE Catalog is part of of the [I-GUIDE Cyberinfrastructure Platform](https://i-guide.io/platform/). The Platform supports collaborative research along with computation and data-intensive geospatial problem solving. Within the context of the I-GUIDE Platform, the goal of the Catalog is to allow users to find, explore, and share data, models, code, software, hosted services, computational resources, and learning materials. A major goal of the catalog is to make these resources "actionable" - e.g., once a user finds a resource, they should be able to interact with the content of the resource and/or launch it into an appropriate analysis or computational environment for execution and exploration. + +## Deployment + +The I-GUIDE Catalog is currently deployed at [https://iguide.cuahsi.io/](https://iguide.cuahsi.io/). + +## Issue Tracker + +Please report any bugs or ideas for enhancements to the I-GUIDE Catalog issue tracker: + +[https://github.com/I-GUIDE/catalog/issues](https://github.com/I-GUIDE/catalog/issues) + +## License + +The I-GUIDE Catalog is released under the BSD 3-Clause License. This means that you can do what you want with the code [provided that you inlude the BSD copyright and license notice in it](https://www.tldrlegal.com/license/bsd-3-clause-license-revised). + +©2024 I-GUIDE Developers. + +## Sponsors and Credits + +[![NSF-2118329](https://img.shields.io/badge/NSF-2118329-blue.svg)](https://nsf.gov/awardsearch/showAward?AWD_ID=2118329) + +This material is based upon work supported by the National Science Foundation (NSF) under award [2118329](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2118329). Any opinions, findings, conclusions, or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the NSF. + +## Developer Information ### Getting Started ```console @@ -43,7 +68,7 @@ TESTING=True ``` 2. Login and submit a record to create all the collections -3. Run `triggers/management/change_streams_pre_and_post.py` +3. Run `api/models/management/change_streams_pre_and_post.py` 4. Create the catalog and typeahead indexes from `atlas/` (TODO detailed instructions) ### Triggers diff --git a/api/config/__init__.py b/api/config/__init__.py index cad45f3..adb3ba3 100644 --- a/api/config/__init__.py +++ b/api/config/__init__.py @@ -19,6 +19,7 @@ class Settings(BaseSettings): oidc_issuer: str hydroshare_meta_read_url: HttpUrl hydroshare_file_read_url: HttpUrl + search_relevance_score_threshold: float = 1.4 def __init__(self, **data: Any) -> None: super().__init__(**data) diff --git a/api/models/management/change_streams_pre_and_post.py b/api/models/management/change_streams_pre_and_post.py index aec4505..49daecd 100644 --- a/api/models/management/change_streams_pre_and_post.py +++ b/api/models/management/change_streams_pre_and_post.py @@ -1,10 +1,11 @@ import asyncio -from api.config import get_settings -from api.models.catalog import DatasetMetadataDOC from beanie import init_beanie from motor.motor_asyncio import AsyncIOMotorClient +from api.config import get_settings +from api.models.catalog import DatasetMetadataDOC + async def main(): db = AsyncIOMotorClient(get_settings().db_connection_string) diff --git a/api/models/schema.py b/api/models/schema.py index 8305b9c..0fdb84d 100644 --- a/api/models/schema.py +++ b/api/models/schema.py @@ -236,17 +236,16 @@ class TemporalCoverage(SchemaBaseModel): title="Start date", description="A date/time object containing the instant corresponding to the commencement of the time " "interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", - # TODO: these are failing due to a problem with transpiled dependencies inside cznet-vue-core - # formatMaximum={"$data": "1/endDate"}, - # errorMessage= { "formatMaximum": "must be lesser than or equal to End date" } + formatMaximum={"$data": "1/endDate"}, + errorMessage= { "formatMaximum": "must be lesser than or equal to End date" } ) endDate: Optional[datetime] = Field( title="End date", description="A date/time object containing the instant corresponding to the termination of the time " "interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, " "that means the temporal coverage is ongoing.", - # formatMinimum={"$data": "1/startDate"}, - # errorMessage= { "formatMinimum": "must be greater than or equal to Start date" } + formatMinimum={"$data": "1/startDate"}, + errorMessage= { "formatMinimum": "must be greater than or equal to Start date" } ) diff --git a/api/models/schemas/schema.json b/api/models/schemas/schema.json index 779792b..06550c8 100644 --- a/api/models/schemas/schema.json +++ b/api/models/schemas/schema.json @@ -639,12 +639,24 @@ "startDate": { "title": "Start date", "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "formatMaximum": { + "$data": "1/endDate" + }, + "errorMessage": { + "formatMaximum": "must be lesser than or equal to End date" + }, "type": "string", "format": "date-time" }, "endDate": { "title": "End date", "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "formatMinimum": { + "$data": "1/startDate" + }, + "errorMessage": { + "formatMinimum": "must be greater than or equal to Start date" + }, "type": "string", "format": "date-time" } @@ -2021,12 +2033,24 @@ "startDate": { "title": "Start date", "description": "A date/time object containing the instant corresponding to the commencement of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM).", + "formatMaximum": { + "$data": "1/endDate" + }, + "errorMessage": { + "formatMaximum": "must be lesser than or equal to End date" + }, "type": "string", "format": "date-time" }, "endDate": { "title": "End date", "description": "A date/time object containing the instant corresponding to the termination of the time interval (ISO8601 formatted date - YYYY-MM-DDTHH:MM). If the ending date is left off, that means the temporal coverage is ongoing.", + "formatMinimum": { + "$data": "1/startDate" + }, + "errorMessage": { + "formatMinimum": "must be greater than or equal to Start date" + }, "type": "string", "format": "date-time" } diff --git a/api/routes/discovery.py b/api/routes/discovery.py index e7c0ae8..6a95617 100644 --- a/api/routes/discovery.py +++ b/api/routes/discovery.py @@ -3,6 +3,8 @@ from fastapi import APIRouter, Request, Depends from pydantic import BaseModel, validator +from api.config import get_settings + router = APIRouter() @@ -91,7 +93,7 @@ def _filters(self): @property def _should(self): - search_paths = ['name', 'description', 'keywords', 'keywords.name'] + search_paths = ['name', 'description', 'keywords', 'keywords.name', 'creator.name'] should = [ {'autocomplete': {'query': self.term, 'path': key, 'fuzzy': {'maxEdits': 1}}} for key in search_paths ] @@ -125,7 +127,7 @@ def _must(self): @property def stages(self): - highlightPaths = ['name', 'description', 'keywords', 'keywords.name'] + highlightPaths = ['name', 'description', 'keywords', 'keywords.name', 'creator.name'] stages = [] compound = {'filter': self._filters, 'must': self._must} if self.term: @@ -153,6 +155,10 @@ def stages(self): stages.append( {'$set': {'score': {'$meta': 'searchScore'}, 'highlights': {'$meta': 'searchHighlights'}}}, ) + if self.term: + # get only results which meet minimum relevance score threshold + score_threshold = get_settings().search_relevance_score_threshold + stages.append({'$match': {'score': {'$gt': score_threshold}}}) return stages diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7ab3c51..7ac5560 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { - "name": "cznet-discovery", - "version": "2.0.0", + "name": "i-guide-catalog", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "cznet-discovery", - "version": "2.0.0", + "name": "i-guide-catalog", + "version": "1.1.1", "dependencies": { "@cznethub/cznet-vue-core": "^0.2.6", "@unhead/vue": "^1.9.9", @@ -17,6 +17,7 @@ "@vuex-orm/core": "^0.36.4", "deepmerge": "^4.3.1", "dompurify": "^3.1.2", + "github-markdown-css": "^5.5.1", "google-maps": "^4.3.3", "lodash.isequal": "^4.5.0", "markdown-it": "^14.1.0", @@ -30,6 +31,7 @@ "vue-facing-decorator": "^3.0.4", "vue-i18n": "^9.13.1", "vue-router": "^4.3.2", + "vue-timeago3": "^2.3.2", "vuetify": "^3.6.3", "vuex": "^4.1.0", "vuex-persistedstate": "^4.1.0" @@ -2001,7 +2003,6 @@ "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -6297,6 +6298,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dayjs": { "version": "1.11.11", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", @@ -8210,6 +8226,17 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-markdown-css": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.5.1.tgz", + "integrity": "sha512-2osyhNgFt7DEHnGHbgIifWawAqlc68gjJiGwO1xNw/S48jivj8kVaocsVkyJqUi3fm7fdYIDi4C6yOtcqR/aEQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "10.3.12", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", @@ -11158,8 +11185,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -13668,6 +13694,17 @@ "he": "^1.2.0" } }, + "node_modules/vue-timeago3": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/vue-timeago3/-/vue-timeago3-2.3.2.tgz", + "integrity": "sha512-yxb++H9ekLS8bSt3in7fMwIfW1ex9ceMYVCcfLiOppBYpp8hatlOTLqQyCmLXeDYB098uwgfnCewEPKVh/gi6A==", + "dependencies": { + "date-fns": "^2.28.0" + }, + "peerDependencies": { + "vue": "^3.3.6" + } + }, "node_modules/vue-tsc": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.16.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6c8c431..a1993a9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "i-guide-catalog", - "version": "1.1.0", + "version": "1.1.1", "private": true, "scripts": { "serve": "vite --open", @@ -21,6 +21,7 @@ "@vuex-orm/core": "^0.36.4", "deepmerge": "^4.3.1", "dompurify": "^3.1.2", + "github-markdown-css": "^5.5.1", "google-maps": "^4.3.3", "lodash.isequal": "^4.5.0", "markdown-it": "^14.1.0", @@ -34,6 +35,7 @@ "vue-facing-decorator": "^3.0.4", "vue-i18n": "^9.13.1", "vue-router": "^4.3.2", + "vue-timeago3": "^2.3.2", "vuetify": "^3.6.3", "vuex": "^4.1.0", "vuex-persistedstate": "^4.1.0" @@ -74,4 +76,4 @@ "lint-staged": { "*": "eslint --fix" } -} \ No newline at end of file +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 342728c..793c7a5 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -240,11 +240,6 @@ class App extends Vue { label: "Register", icon: "mdi-link-plus", }, - // { - // attrs: { href: "https://dsp.criticalzone.org/" }, - // label: "Contribute Data", - // icon: "mdi-book-plus", - // }, ]; User.fetchSchemas(); diff --git a/frontend/src/assets/css/theme.scss b/frontend/src/assets/css/theme.scss index 44d4f8b..6243dff 100644 --- a/frontend/src/assets/css/theme.scss +++ b/frontend/src/assets/css/theme.scss @@ -3,6 +3,7 @@ // } @import '@fortawesome/fontawesome-free/css/all.min.css'; @import '@cznethub/cznet-vue-core/styles'; +@import 'github-markdown-css/github-markdown-light.css'; html { @@ -94,4 +95,8 @@ mark { -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; +} + +.v-container { + max-width: 1920px; } \ No newline at end of file diff --git a/frontend/src/components/base/cd.footer.vue b/frontend/src/components/base/cd.footer.vue index 4adbfea..e178c0d 100644 --- a/frontend/src/components/base/cd.footer.vue +++ b/frontend/src/components/base/cd.footer.vue @@ -24,13 +24,12 @@
Open Source

The I-GUIDE Catalog and Discover system are Open Source. Find us on - GitHubGitHub.

Report a bug - here

@@ -42,9 +41,8 @@

- (c) {{ year }} CUAHSI. This material is based upon work supported by - the National Science Foundation (NSF) under awards 2012893, 2012593, and - 2012748.
+ © {{ year }} I-GUIDE Developers. This material is based upon work + supported by the National Science Foundation (NSF) under award 2118329. Any opinions, findings, conclusions, or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the NSF. diff --git a/frontend/src/components/dataset/cd.dataset.vue b/frontend/src/components/dataset/cd.dataset.vue index 16507cd..f0f852f 100644 --- a/frontend/src/components/dataset/cd.dataset.vue +++ b/frontend/src/components/dataset/cd.dataset.vue @@ -198,21 +198,24 @@ class="page-content" :class="{ 'is-sm': $vuetify.display.mdAndDown }" > -

{{ data.name }}

- - + +
--> + README -
+
+ +
+
@@ -800,17 +810,19 @@ - + - + Content URL - {{ - selectedMetadata.metadata?.contentUrl - }} + {{ selectedMetadata.metadata?.contentUrl }} @@ -821,7 +833,7 @@ - + @@ -839,6 +851,11 @@ import { CzFileExplorer, Notifications } from "@cznethub/cznet-vue-core"; import { Loader, LoaderOptions } from "google-maps"; import CdSpatialCoverageMap from "@/components/search-results/cd.spatial-coverage-map.vue"; import User from "@/models/user.model"; +import markdownit from "markdown-it"; +import { Component, Vue, toNative } from "vue-facing-decorator"; +import { useGoTo } from "vuetify/lib/framework.mjs"; +import { useRoute, useRouter } from "vue-router"; +import { EnumCreativeWorkStatus } from "@/types"; const options: LoaderOptions = { libraries: ["drawing"] }; const loader: Loader = new Loader( @@ -846,12 +863,11 @@ const loader: Loader = new Loader( options, ); -import markdownit from "markdown-it"; -const md = markdownit(); - -import { Component, Vue, toNative } from "vue-facing-decorator"; -import { useGoTo } from "vuetify/lib/framework.mjs"; -import { useRoute, useRouter } from "vue-router"; +const md = markdownit({ + linkify: true, + typographer: true, + breaks: true, +}); @Component({ name: "cd-dataset", @@ -868,11 +884,10 @@ class CdDataset extends Vue { tab = 0; selectedMetadata: any = false; readmeMd = ""; - // marked = marked; showCoordinateSystem = false; showExtent = false; + isLoadingMD = false; - /** Example folder/file tree structure */ rootDirectory = { name: "root", children: [] as any[], @@ -919,6 +934,32 @@ class CdDataset extends Vue { route = useRoute(); router = useRouter(); + get hasSpatialFeatures(): boolean { + const feat = this.data.spatialCoverage?.["@type"]; + return feat === "GeoShape" || feat === "GeoCoordinates" || feat === "Place"; + } + + get schema() { + return User.$state.schema; + } + + get uiSchema() { + return User.$state.uiSchema; + } + + get boxCoordinates() { + const extents = this.data.spatialCoverage.geo.box + .trim() + .split(" ") + .map((n: string) => +n); + return { + north: extents[0], + east: extents[1], + south: extents[2], + west: extents[3], + }; + } + async created() { await this.loadDataset(); @@ -980,10 +1021,25 @@ class CdDataset extends Vue { Notifications.toast({ message: "Copied to clipboard", type: "info" }); } + getStautsColor(status: EnumCreativeWorkStatus) { + switch (status) { + case EnumCreativeWorkStatus.Draft: + return "primary"; + case EnumCreativeWorkStatus.Incomplete: + return "red"; + case EnumCreativeWorkStatus.Obsolete: + return "orange"; + case EnumCreativeWorkStatus.Published: + return "green"; + default: + "primary"; + } + } + loadFileExporer() { // Load file explorer if (this.data.associatedMedia?.length) { - this.data.associatedMedia.map((m: any, index: number) => { + this.data.associatedMedia.map((m: any, _index: number) => { let fileSizeBytes; if (typeof m.contentSize === "string") { @@ -1060,12 +1116,16 @@ class CdDataset extends Vue { ); if (readmeFile?.contentUrl) { + const url = readmeFile.contentUrl.replace("http:", "https:"); try { - const response = await fetch(readmeFile.contentUrl); + this.isLoadingMD = true; + const response = await fetch(url); const rawMd = await response.text(); this.readmeMd = md.render(rawMd); } catch (e) { console.log(e); + } finally { + this.isLoadingMD = false; } } } @@ -1089,6 +1149,14 @@ class CdDataset extends Vue { } } + getFileURL(fileMetadata: any): string { + const url = fileMetadata.metadata?.contentUrl; + if (url) { + return url.startsWith("http:") ? url.replace("http:", "https:") : url; + } + return ""; + } + parseDate(date: string): string { const parsed = new Date(Date.parse(date)); return parsed.toLocaleString("default", { @@ -1097,36 +1165,6 @@ class CdDataset extends Vue { year: "numeric", }); } - - // getTransformedSpatialCoverage() { - // return { ...data.spatialCoverage, } - // } - - get hasSpatialFeatures(): boolean { - const feat = this.data.spatialCoverage?.["@type"]; - return feat === "GeoShape" || feat === "GeoCoordinates" || feat === "Place"; - } - - get schema() { - return User.$state.schema; - } - - get uiSchema() { - return User.$state.uiSchema; - } - - get boxCoordinates() { - const extents = this.data.spatialCoverage.geo.box - .trim() - .split(" ") - .map((n: string) => +n); - return { - north: extents[0], - east: extents[1], - south: extents[2], - west: extents[3], - }; - } } export default toNative(CdDataset); @@ -1151,6 +1189,20 @@ export default toNative(CdDataset); overflow: auto; resize: vertical; } + + .markdown-body { + box-sizing: border-box; + min-width: 200px; + max-width: 980px; + padding: 45px; + font-family: inherit; + } + + @media (max-width: 767px) { + .markdown-body { + padding: 15px; + } + } } .page-content { @@ -1172,7 +1224,7 @@ export default toNative(CdDataset); .citation-text { min-width: 0; - word-break: break-all; + word-break: break-word; } #graph-container { diff --git a/frontend/src/modules/timeago.ts b/frontend/src/modules/timeago.ts new file mode 100644 index 0000000..3487473 --- /dev/null +++ b/frontend/src/modules/timeago.ts @@ -0,0 +1,6 @@ +import timeago from "vue-timeago3"; +import type { UserModule } from "@/types"; + +export const install: UserModule = ({ app }) => { + app.use(timeago); +}; diff --git a/frontend/src/modules/vuex.ts b/frontend/src/modules/vuex.ts index 30fb07e..f931f15 100644 --- a/frontend/src/modules/vuex.ts +++ b/frontend/src/modules/vuex.ts @@ -4,9 +4,8 @@ import createPersistedState from "vuex-persistedstate"; import { orm } from "@/models/orm"; import { persistedPaths } from "@/models/persistedPaths"; import type { UserModule } from "@/types"; +import { APP_NAME } from "@/constants"; -// Setup Pinia -// https://pinia.vuejs.org/ export const install: UserModule = ({ app }) => { // Create Vuex Store and register database through Vuex ORM. @@ -15,7 +14,7 @@ export const install: UserModule = ({ app }) => { VuexORM.install(orm), createPersistedState({ paths: persistedPaths, - key: `CZ Hub`, + key: APP_NAME || 'iguide-catalog', }), ], // state() { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index b298e84..aee3dd9 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -73,3 +73,10 @@ export interface ISearchParams { export interface ITypeaheadParams { term: string; } + +export enum EnumCreativeWorkStatus { + Draft = "Draft", + Incomplete = "Incomplete", + Obsolete = "Obsolete", + Published = "Published", +} diff --git a/triggers/management/change_streams_pre_and_post.py b/triggers/management/change_streams_pre_and_post.py deleted file mode 100755 index ebfaef1..0000000 --- a/triggers/management/change_streams_pre_and_post.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio - -from beanie import init_beanie -from motor.motor_asyncio import AsyncIOMotorClient -from api.config import get_settings -from api.models.catalog import DatasetMetadataDOC - - -async def main(): - db = AsyncIOMotorClient(get_settings().db_connection_string) - await init_beanie(database=db[get_settings().database_name], document_models=[DatasetMetadataDOC]) - # https://www.mongodb.com/docs/manual/reference/command/collMod/#change-streams-with-document-pre--and-post-images - # This enables us to get the record id of a deleted submission within a trigger. We need this for removing - # discovery records when a submission is deleted - await db[get_settings().database_name].command( - ({'collMod': DatasetMetadataDOC.get_collection_name(), "changeStreamPreAndPostImages": {'enabled': True}})) - db.close() - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file