Skip to content

Commit

Permalink
Merge pull request #100 from RDFLib/feature/api-requests-refactor
Browse files Browse the repository at this point in the history
API request refactor
  • Loading branch information
jamiefeiss authored Sep 5, 2023
2 parents 54e4b45 + 1e8298d commit 6b8e4a1
Show file tree
Hide file tree
Showing 24 changed files with 1,247 additions and 1,297 deletions.
176 changes: 93 additions & 83 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { RouterView, useRoute } from "vue-router";
import { DataFactory } from "n3";
import { useUiStore } from "@/stores/ui";
import { useRdfStore } from "@/composables/rdfStore";
import { useGetRequest } from "@/composables/api";
import { sidenavConfigKey, apiBaseUrlConfigKey, type Profile } from "@/types";
import { useApiRequest, useConcurrentApiRequests } from "@/composables/api";
import { sidenavConfigKey, type Profile } from "@/types";
import MainNav from "@/components/navs/MainNav.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
import RightSideBar from "@/components/navs/RightSideBar.vue";
Expand All @@ -18,111 +18,121 @@ const { namedNode } = DataFactory;
const version = packageJson.version;
const sidenav = inject(sidenavConfigKey) as boolean;
const apiBaseUrl = inject(apiBaseUrlConfigKey) as string;
const route = useRoute();
const ui = useUiStore();
const { data, profiles, loading, error, doRequest } = useGetRequest();
const { data: profData, profiles: profProfiles, loading: profLoading, error: profError, doRequest: profDoRequest } = useGetRequest();
const { store, prefixes, parseIntoStore, qnameToIri } = useRdfStore();
const { store: profStore, prefixes: profPrefixes, parseIntoStore: profParseIntoStore, qnameToIri: profQnameToIri } = useRdfStore();
const { store: combinedStore, prefixes: combinedPrefixes, parseIntoStore: combinedParseIntoStore, qnameToIri: combinedQnameToIri } = useRdfStore();
const { loading: rootLoading, error: rootError, apiGetRequest: rootApiGetRequest } = useApiRequest(); // main request to API root
const { loading: profLoading, error: profError, apiGetRequest: profApiGetRequest } = useApiRequest(); // profiles request
const { loading: concurrentLoading, hasError: concurrentHasError, concurrentApiRequests } = useConcurrentApiRequests(); // concurrent profile requests
const { store: rootStore, parseIntoStore: rootParseIntoStore, qnameToIri: rootQnameToIri } = useRdfStore(); // store for API root data
const { store: profStore, parseIntoStore: profParseIntoStore, qnameToIri: profQnameToIri } = useRdfStore(); // profiles store
document.title = ui.pageTitle;
onMounted(() => {
async function getRootApiMetadata() {
// get API details
const { data: rootData } = await rootApiGetRequest("/");
if (rootData && !rootError.value) {
rootParseIntoStore(rootData);
// get API version
const version = rootStore.value.getObjects(null, rootQnameToIri("prez:version"), null)[0];
ui.apiVersion = version.value;
// get search methods per flavour
let searchMethods: {[key: string]: string[]} = {};
rootStore.value.forObjects(object => {
let flavour = "";
let methods: string[] = [];
rootStore.value.forEach(q => {
if (q.predicate.value === rootQnameToIri("a")) {
flavour = q.object.value.split(`${rootQnameToIri("prez:")}`)[1];
} else if (q.predicate.value === rootQnameToIri("prez:availableSearchMethod")) {
methods.push(q.object.value.split(`${rootQnameToIri("prez:")}`)[1]);
}
}, object, null, null, null);
searchMethods[flavour] = methods;
}, null, rootQnameToIri("prez:enabledPrezFlavour"), null);
ui.searchMethods = searchMethods;
}
}
async function getProfiles() {
// if profiles don't exist in pinia
if (Object.keys(ui.profiles).length === 0) {
profDoRequest(`${apiBaseUrl}/profiles`, () => {
profParseIntoStore(profData.value);
const { data: profData } = await profApiGetRequest("/profiles");
if (profData && !profError.value) {
profParseIntoStore(profData);
// get list of profiles
let profileUris: {[uri: string]: {
token: string;
link: string;
}} = {};
profStore.value.forSubjects(subject => {
profStore.value.forEach(q => {
profileUris[q.subject.value] = {
token: q.object.value.replace("/profiles/", ""),
link: `${apiBaseUrl}${q.object.value}`
link: q.object.value
}
}, subject, namedNode(profQnameToIri("prez:link")), null, null);
}, namedNode(profQnameToIri("a")), namedNode(profQnameToIri("prof:Profile")), null);
// promise.all request for each profile in parallel
Promise.all(Object.values(profileUris).map(p => fetch(p.link).then(r => r.text()))).then(values => {
// parse all results into store
values.forEach(value => {
combinedParseIntoStore(value)
});
let profs: Profile[] = [];
combinedStore.value.forSubjects(subject => {
let p: Profile = {
namespace: subject.id,
token: profileUris[subject.id].token,
title: "",
description: "",
mediatypes: [],
defaultMediatype: "",
labelPredicates: [],
descriptionPredicates: [],
explanationPredicates: []
};
combinedStore.value.forEach(q => {
if (q.predicate.value === combinedQnameToIri("dcterms:title")) {
p.title = q.object.value;
} else if (q.predicate.value === combinedQnameToIri("dcterms:description")) {
p.description = q.object.value;
// } else if (q.predicate.value === combinedQnameToIri("dcterms:identifier")) {
// p.token = q.object.value;
} else if (q.predicate.value === combinedQnameToIri("altr-ext:hasResourceFormat")) {
p.mediatypes.push(q.object.value);
} else if (q.predicate.value === combinedQnameToIri("altr-ext:hasDefaultResourceFormat")) {
p.defaultMediatype = q.object.value;
} else if (q.predicate.value === combinedQnameToIri("altr-ext:hasLabelPredicate")) {
p.labelPredicates.push(q.object.value);
} else if (q.predicate.value === combinedQnameToIri("altr-ext:hasDescriptionPredicate")) {
p.descriptionPredicates.push(q.object.value);
} else if (q.predicate.value === combinedQnameToIri("altr-ext:hasExplanationPredicate")) {
p.explanationPredicates.push(q.object.value);
}
}, subject, null, null, null);
p.mediatypes.sort((a, b) => Number(b === p.defaultMediatype) - Number(a === p.defaultMediatype));
profs.push(p);
}, namedNode(combinedQnameToIri("a")), namedNode(combinedQnameToIri("prof:Profile")), null);
ui.profiles = profs.reduce<{[namespace: string]: Profile}>((obj, prof) => (obj[prof.namespace] = prof, obj), {}); // {uri: {...}, ...}
});
});
}
// get API details
doRequest(apiBaseUrl, () => {
parseIntoStore(data.value);
// get API version
const version = store.value.getObjects(null, qnameToIri("prez:version"), null)[0];
ui.apiVersion = version.value;
// request each profile in parallel
const profilesData = await concurrentApiRequests(Object.values(profileUris).map(p => p.link));
// get search methods per flavour
let searchMethods: {[key: string]: string[]} = {};
store.value.forObjects(object => {
let flavour = "";
let methods: string[] = [];
store.value.forEach(q => {
if (q.predicate.value === qnameToIri("a")) {
flavour = q.object.value.split(`${qnameToIri('prez:')}`)[1];
} else if (q.predicate.value === qnameToIri("prez:availableSearchMethod")) {
methods.push(q.object.value.split(`${qnameToIri('prez:')}`)[1]);
profilesData.forEach(r => {
if (r.value) {
profParseIntoStore(r.value);
}
}, object, null, null, null);
searchMethods[flavour] = methods;
}, null, qnameToIri("prez:enabledPrezFlavour"), null);
ui.searchMethods = searchMethods;
});
});
let profs: Profile[] = [];
profStore.value.forSubjects(subject => {
let p: Profile = {
namespace: subject.id,
token: profileUris[subject.id].token,
title: "",
description: "",
mediatypes: [],
defaultMediatype: "",
labelPredicates: [],
descriptionPredicates: [],
explanationPredicates: []
};
profStore.value.forEach(q => {
if (q.predicate.value === profQnameToIri("dcterms:title")) { // need to use label predicate from profile
p.title = q.object.value;
} else if (q.predicate.value === profQnameToIri("dcterms:description")) { // need to use description predicate from profile
p.description = q.object.value;
// } else if (q.predicate.value === profQnameToIri("dcterms:identifier")) {
// p.token = q.object.value;
} else if (q.predicate.value === profQnameToIri("altr-ext:hasResourceFormat")) {
p.mediatypes.push(q.object.value);
} else if (q.predicate.value === profQnameToIri("altr-ext:hasDefaultResourceFormat")) {
p.defaultMediatype = q.object.value;
} else if (q.predicate.value === profQnameToIri("altr-ext:hasLabelPredicate")) {
p.labelPredicates.push(q.object.value);
} else if (q.predicate.value === profQnameToIri("altr-ext:hasDescriptionPredicate")) {
p.descriptionPredicates.push(q.object.value);
} else if (q.predicate.value === profQnameToIri("altr-ext:hasExplanationPredicate")) {
p.explanationPredicates.push(q.object.value);
}
}, subject, null, null, null);
p.mediatypes.sort((a, b) => Number(b === p.defaultMediatype) - Number(a === p.defaultMediatype));
profs.push(p);
}, namedNode(profQnameToIri("a")), namedNode(profQnameToIri("prof:Profile")), null);
ui.profiles = profs.reduce<{[namespace: string]: Profile}>((obj, prof) => (obj[prof.namespace] = prof, obj), {}); // {uri: {...}, ...}
}
}
}
onMounted(async () => {
await Promise.all([getRootApiMetadata(), getProfiles()]);
});
</script>

Expand Down
10 changes: 10 additions & 0 deletions src/components/MapClient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ export enum ShapeTypes {
}

export type DrawingModes = 'MARKER' | 'RECTANGLE' | 'POLYGON'

/** well known text simplified result object, as returned by mapSearch */
export type WKTResult = {
uri: string
link: string
wkt: string
fcLabel: string
label: string
id?: string
}
2 changes: 1 addition & 1 deletion src/components/MapClient.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { wktToGeoJSON } from "@terraformer/wkt"
import { mapConfigKey, type MapConfig } from "@/types";
import { convertConfigTypes } from '@/util/mapSearchHelper'
import type { MapOptionsCenter } from '@/types'
import type { WKTResult } from '@/stores/mapSearchStore.d';
import type { WKTResult } from "@/components/MapClient.d";
import { ShapeTypes, type DrawingModes } from "@/components/MapClient.d";
Expand Down
14 changes: 4 additions & 10 deletions src/components/ProfilesTable.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts" setup>
import { computed } from "vue";
import { useUiStore } from "@/stores/ui";
import type { ProfileHeader } from "@/types";
const mediatypeNames: {[key: string]: string} = {
"text/html": "HTML",
Expand All @@ -13,23 +12,18 @@ const mediatypeNames: {[key: string]: string} = {
"application/geo+json": "GeoJSON"
};
const props = defineProps<{
profiles: ProfileHeader[];
path: string;
}>();
const ui = useUiStore();
const defaultToken = computed(() => {
const defaultProfile = props.profiles.find(p => p.default);
const defaultProfile = ui.rightNavConfig.profiles!.find(p => p.default);
if (defaultProfile === undefined) {
throw new TypeError("A default profile must exist.");
}
return defaultProfile.token;
});
const orderedProfiles = computed(() => {
const includedProfiles = props.profiles.map(prof => prof.token);
const includedProfiles = ui.rightNavConfig.profiles!.map(prof => prof.token);
return Object.values(ui.profiles)
.filter(prof => includedProfiles.includes(prof.token))
.sort((a, b) => Number(b.token === defaultToken.value) - Number(a.token === defaultToken.value))
Expand All @@ -49,15 +43,15 @@ const orderedProfiles = computed(() => {
</tr>
<tr v-for="profile in orderedProfiles">
<td>
<RouterLink :to="`${props.path}?_profile=${profile.token}`" title="Get profile representation">
<RouterLink :to="`${ui.rightNavConfig.currentUrl}?_profile=${profile.token}`" title="Get profile representation">
{{ profile.token }}
</RouterLink>
<span v-if="(profile.token === defaultToken)" class="badge" title="This is the default profile for this endpoint">default</span>
</td>
<td><RouterLink :to="`/profiles/${profile.token}`" title="Go to profile page">{{ profile.title }}</RouterLink></td>
<td>
<div v-for="mediatype in profile.mediatypes">
<RouterLink :to="`${props.path}?_profile=${profile.token}&_mediatype=${mediatype}`">
<RouterLink :to="`${ui.rightNavConfig.currentUrl}?_profile=${profile.token}&_mediatype=${mediatype}`">
{{ mediatypeNames[mediatype] || mediatype }}
</RouterLink>
<span v-if="(mediatype === profile.defaultMediatype)" class="badge" title="This is the default format for this profile">default</span>
Expand Down
17 changes: 8 additions & 9 deletions src/components/search/CatPrezSearch.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
<script lang="ts" setup>
import { ref, onMounted, inject, watch } from "vue";
import { ref, onMounted, watch } from "vue";
import { DataFactory } from "n3";
import { apiBaseUrlConfigKey } from "@/types";
import { useGetRequest } from "@/composables/api";
import { useApiRequest } from "@/composables/api";
import { useRdfStore } from "@/composables/rdfStore";
const { namedNode } = DataFactory;
const apiBaseUrl = inject(apiBaseUrlConfigKey) as string;
const { data, loading, error, doRequest } = useGetRequest();
const { loading, error, apiGetRequest } = useApiRequest();
const { store, parseIntoStore, qnameToIri } = useRdfStore();
const props = defineProps<{
Expand All @@ -34,9 +32,10 @@ watch(() => props.defaultSelected, (newValue, oldValue) => {
}
});
onMounted(() => {
doRequest(`${apiBaseUrl}/c/catalogs`, () => {
parseIntoStore(data.value);
onMounted(async () => {
const { data } = await apiGetRequest("/c/catalogs");
if (data && !error.value) {
parseIntoStore(data);
store.value.forSubjects(member => {
let option: CatalogOption = {
Expand All @@ -50,7 +49,7 @@ onMounted(() => {
}, member, null, null, null);
options.value.push(option);
}, namedNode(qnameToIri("a")), namedNode(qnameToIri("dcat:Catalog")), null);
});
}
});
</script>

Expand Down
Loading

0 comments on commit 6b8e4a1

Please sign in to comment.