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

API request refactor #100

Merged
merged 8 commits into from
Sep 5, 2023
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
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