Skip to content

Commit

Permalink
Make one check-status call for all corpora
Browse files Browse the repository at this point in the history
  • Loading branch information
arildm committed Sep 7, 2023
1 parent 8827c35 commit f04aa67
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 90 deletions.
5 changes: 5 additions & 0 deletions src/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ class MinkApi {
return response.data;
}

async checkStatusAll() {
const response = await this.axios.get("check-status");
return response.data.jobs;
}

async runSparv(corpusId) {
const response = await this.axios
.put("run-sparv", null, { params: { corpus_id: corpusId } })
Expand Down
4 changes: 4 additions & 0 deletions src/api/backend.composable.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export default function useMinkBackend() {
const loadJob = (corpusId) =>
spin(api.checkStatus(corpusId), t("job.loading"), `corpus/${corpusId}/job`);

const loadJobs = () =>
spin(api.checkStatusAll(), t("job.loading"), `corpora`);

const runJob = (corpusId) =>
spin(api.runSparv(corpusId), t("job.starting"), `corpus/${corpusId}/job`);

Expand Down Expand Up @@ -129,6 +132,7 @@ export default function useMinkBackend() {
uploadSources,
deleteSource,
loadJob,
loadJobs,
runJob,
install,
abortJob,
Expand Down
105 changes: 26 additions & 79 deletions src/corpora/corpora.composable.js
Original file line number Diff line number Diff line change
@@ -1,101 +1,48 @@
import { useRouter } from "vue-router";
import { useAuth } from "@/auth/auth.composable";
import { emptyConfig } from "@/api/corpusConfig";
import useConfig from "@/corpus/config/config.composable";
import useSources from "@/corpus/sources/sources.composable";
import useMessenger from "@/message/messenger.composable";
import useMinkBackend from "@/api/backend.composable";
import useCorpus from "@/corpus/corpus.composable";
import { useCorpusStore } from "@/store/corpus.store";
import useMessenger from "@/message/messenger.composable";

/** Let corpus list be refreshed initially, but skip subsequent load calls. */
let isCorporaFresh = false;

/** Use this module-scope variable for the request, so that simultaneous calls don't procude multiple requests. */
let loadPromise = null;

export default function useCorpora() {
const corpusStore = useCorpusStore();
const router = useRouter();
const { refreshJwt } = useAuth();
const { deleteCorpus } = useCorpus();
const { uploadConfig } = useConfig();
const { uploadSources } = useSources();
const { alert, alertError } = useMessenger();
const { alertError } = useMessenger();
const mink = useMinkBackend();

async function loadCorpora(force = false) {
if (isCorporaFresh && !force) return;
const corpora = await mink.loadCorpora().catch(alertError);
corpusStore.setCorpusIds(corpora);
isCorporaFresh = true;
}

async function createCorpus() {
const corpusId = await mink.createCorpus().catch(alertError);
// Have the new corpus included in further API calls.
await refreshJwt();
// Adding the new id to store may trigger API calls, so do it after updating the JWT.
corpusStore.corpora[corpusId] = corpusStore.corpora[corpusId] || {};
return corpusId;
}

async function createFromUpload(files) {
const corpusId = await createCorpus().catch(alertError);
if (!corpusId) return;

const results = await Promise.allSettled([
uploadSources(files, corpusId),
uploadConfig(emptyConfig(), corpusId),
]);

const rejectedResults = results.filter(
(result) => result.status != "fulfilled"
);
if (rejectedResults.length) {
// Display error message(s).
rejectedResults.forEach((result) => alertError(result.reason));
// Discard the empty corpus.
await deleteCorpus(corpusId).catch(alertError);
return;
// Store the pending request in module scope, so simultaneous calls will await the same promise.
if (!loadPromise) {
const loadCorporaPromise = mink
.loadCorpora()
.catch(alertError)
.then((corpora) => corpusStore.setCorpusIds(corpora));

const loadJobsPromise = mink
.loadJobs()
.catch(alertError)
.then((jobs) =>
jobs.forEach((job) => {
corpusStore.corpora[job.corpus_id].status = job;
corpusStore.corpora[job.corpus_id].sources = job.available_files;
})
);

loadPromise = Promise.all([loadCorporaPromise, loadJobsPromise]);
}

router.push(`/corpus/${corpusId}`);
}

async function createFromConfig(name, description, format, textAnnotation) {
const config = {
name: { swe: name, eng: name },
description: { swe: description, eng: description },
format,
textAnnotation,
};
await loadPromise;

// Create an empty corpus. If it fails, abort.
let corpusId;
try {
corpusId = await createCorpus().catch(alertError);
} catch (e) {
alertError(e);
return;
}

// Upload the basic config.
try {
await uploadConfig(config, corpusId);
// Show the created corpus.
router.push(`/corpus/${corpusId}`);
return corpusId;
} catch (e) {
// If creating the config fails, there's a TypeError.
if (e.name == "TypeError") alert(e.message, "error");
// Otherwise it's probably a backend error when saving.
else alertError(e);
// Discard the empty corpus.
await deleteCorpus(corpusId).catch(alertError);
}
loadPromise = null;
isCorporaFresh = true;
}

return {
loadCorpora,
createFromUpload,
createFromConfig,
};
}
3 changes: 0 additions & 3 deletions src/corpus/Corpus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,19 @@ import { computed } from "vue";
import { useAuth } from "@/auth/auth.composable";
import { useCorpusStore } from "@/store/corpus.store";
import useCorpusIdParam from "./corpusIdParam.composable";
import useCorpora from "@/corpora/corpora.composable";
import useCorpus from "./corpus.composable.js";
import useConfig from "./config/config.composable";
import PageTitle from "@/components/PageTitle.vue";
const corpusStore = useCorpusStore();
const { requireAuthentication, isAuthenticated } = useAuth();
const corpusId = useCorpusIdParam();
const { loadCorpora } = useCorpora();
const { loadCorpus } = useCorpus(corpusId);
const { corpusName } = useConfig(corpusId);
const corpus = computed(() => corpusStore.corpora[corpusId]);
requireAuthentication(async () => {
await loadCorpora();
await loadCorpus();
});
</script>
Expand Down
12 changes: 4 additions & 8 deletions src/corpus/corpus.composable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { useAuth } from "@/auth/auth.composable";
import useMinkBackend from "@/api/backend.composable";
import { useCorpusStore } from "@/store/corpus.store";
import useMessenger from "@/message/messenger.composable";
import useCorpora from "@/corpora/corpora.composable";
import useConfig from "./config/config.composable";
import useExports from "./exports/exports.composable";
import useJob from "./job/job.composable";
import useSources from "./sources/sources.composable";

/** Let data be refreshed initially, but skip subsequent load calls. */
const isCorpusFresh = {};
Expand All @@ -14,22 +13,19 @@ export default function useCorpus(corpusId) {
const corpusStore = useCorpusStore();
const { refreshJwt } = useAuth();
const mink = useMinkBackend();
const { loadCorpora } = useCorpora();
const { alertError } = useMessenger();
const { loadConfig } = useConfig(corpusId);
const { loadExports } = useExports(corpusId);
const { loadJob } = useJob(corpusId);
const { loadSources } = useSources(corpusId);

async function loadCorpus(force = false) {
const isLoaded = Object.keys(corpusStore.corpora[corpusId]).length;
if (isLoaded && isCorpusFresh[corpusId] && !force) {
await loadCorpora();
if (isCorpusFresh[corpusId] && !force) {
return;
}
await Promise.all([
loadConfig(), //
loadExports(),
loadJob(),
loadSources(),
]);
isCorpusFresh[corpusId] = true;
}
Expand Down
90 changes: 90 additions & 0 deletions src/corpus/createCorpus.composable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useRouter } from "vue-router";
import { useAuth } from "@/auth/auth.composable";
import useMinkBackend from "@/api/backend.composable";
import { emptyConfig } from "@/api/corpusConfig";
import { useCorpusStore } from "@/store/corpus.store";
import useMessenger from "@/message/messenger.composable";
import useConfig from "./config/config.composable";
import useSources from "./sources/sources.composable";
import useCorpus from "./corpus.composable";

export default function useCorpora() {
const corpusStore = useCorpusStore();
const router = useRouter();
const { refreshJwt } = useAuth();
const { deleteCorpus } = useCorpus();
const { uploadConfig } = useConfig();
const { uploadSources } = useSources();
const { alert, alertError } = useMessenger();
const mink = useMinkBackend();

async function createCorpus() {
const corpusId = await mink.createCorpus().catch(alertError);
// Have the new corpus included in further API calls.
await refreshJwt();
// Adding the new id to store may trigger API calls, so do it after updating the JWT.
corpusStore.corpora[corpusId] = corpusStore.corpora[corpusId] || {};
return corpusId;
}

async function createFromUpload(files) {
const corpusId = await createCorpus().catch(alertError);
if (!corpusId) return;

const results = await Promise.allSettled([
uploadSources(files, corpusId),
uploadConfig(emptyConfig(), corpusId),
]);

const rejectedResults = results.filter(
(result) => result.status != "fulfilled"
);
if (rejectedResults.length) {
// Display error message(s).
rejectedResults.forEach((result) => alertError(result.reason));
// Discard the empty corpus.
await deleteCorpus(corpusId).catch(alertError);
return;
}

router.push(`/corpus/${corpusId}`);
}

async function createFromConfig(name, description, format, textAnnotation) {
const config = {
name: { swe: name, eng: name },
description: { swe: description, eng: description },
format,
textAnnotation,
};

// Create an empty corpus. If it fails, abort.
let corpusId;
try {
corpusId = await createCorpus().catch(alertError);
} catch (e) {
alertError(e);
return;
}

// Upload the basic config.
try {
await uploadConfig(config, corpusId);
// Show the created corpus.
router.push(`/corpus/${corpusId}`);
return corpusId;
} catch (e) {
// If creating the config fails, there's a TypeError.
if (e.name == "TypeError") alert(e.message, "error");
// Otherwise it's probably a backend error when saving.
else alertError(e);
// Discard the empty corpus.
await deleteCorpus(corpusId).catch(alertError);
}
}

return {
createFromUpload,
createFromConfig,
};
}

0 comments on commit f04aa67

Please sign in to comment.