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

Integrate hierarchy view into schemes list placeholder #18, add concept and scheme routing #15 #86

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
11aceac
Show concept hierarchy in collapsible panel in scheme page
jacobtylerwalls Aug 27, 2024
cec159e
Add <LetterCircle>
jacobtylerwalls Aug 29, 2024
665bb7a
Factor out <TreeRow>
jacobtylerwalls Aug 29, 2024
c7ae512
Factor out tree builder util
jacobtylerwalls Aug 29, 2024
bf208d9
Avoid some gettext overhead
jacobtylerwalls Aug 29, 2024
310c686
Test treeFromSchemes()
jacobtylerwalls Aug 29, 2024
1f107bc
Add changelog
jacobtylerwalls Aug 29, 2024
1576f8e
Wire up concept route with search modal
jacobtylerwalls Aug 31, 2024
2393372
Port routing implementation from arches-references
jacobtylerwalls Aug 31, 2024
cf19e7e
Close the loop on routing (navigating inside tree)
jacobtylerwalls Aug 31, 2024
2daedd5
Toggle modal
chrabyrd Sep 3, 2024
a3fe079
Clean up typescript
jacobtylerwalls Sep 5, 2024
d17266c
Lift up selected language
jacobtylerwalls Sep 6, 2024
b507acb
Improve hierarchy toggle
jacobtylerwalls Sep 12, 2024
c0de86b
Show concept label in page header
jacobtylerwalls Sep 12, 2024
fdd5de0
Add scheme route and detail pages #15
jacobtylerwalls Sep 12, 2024
92b3db2
Adjust language type imports
jacobtylerwalls Oct 7, 2024
01a1da9
Add primevue 4.1 compatibility
jacobtylerwalls Oct 7, 2024
2709053
Merge label finder implementations #92
jacobtylerwalls Sep 11, 2024
fa972f5
Increase coverage
jacobtylerwalls Oct 7, 2024
6b89b9d
Add ts ignore
jacobtylerwalls Oct 29, 2024
9ff774d
Make placeholder marginally prettier
jacobtylerwalls Oct 29, 2024
2601b94
Capture stdout from loaddata command
jacobtylerwalls Oct 30, 2024
e2bf0f4
Spruce up typescript after rebase
jacobtylerwalls Oct 30, 2024
95d04df
Revert "Show concept label in page header"
jacobtylerwalls Nov 4, 2024
e6d6c48
Stub out a subheading
jacobtylerwalls Nov 4, 2024
a0e40c9
Simplify fetch error handling
jacobtylerwalls Nov 4, 2024
54fc162
Add regression test
jacobtylerwalls Nov 4, 2024
f635b26
Standardize font in tooltip
jacobtylerwalls Nov 4, 2024
28de11e
Simplify util
jacobtylerwalls Nov 4, 2024
5d4c9ef
Improve performance
jacobtylerwalls Nov 4, 2024
19b166a
Replace `<LetterCircle>` with icons
jacobtylerwalls Nov 11, 2024
4ce1757
Remove "base" from file name
jacobtylerwalls Nov 11, 2024
e222c8f
Add comment
jacobtylerwalls Nov 18, 2024
6fa4d26
Add concept & scheme serializers & views
jacobtylerwalls Oct 19, 2024
8874de9
Serialize all nodegroups
jacobtylerwalls Nov 5, 2024
0872525
Use RDMAdministrator permission class
jacobtylerwalls Nov 12, 2024
b82dfa0
Update arches-references pin
jacobtylerwalls Nov 19, 2024
225ddc5
Harden backend against incomplete data #135
jacobtylerwalls Nov 20, 2024
dde9bc8
Set arches-references back to main
jacobtylerwalls Nov 22, 2024
d1107fb
Factor out <HierarchySplitter>
jacobtylerwalls Nov 22, 2024
1862113
Leave an idea for an optimization
jacobtylerwalls Nov 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add login interface [#13](https://github.com/archesproject/arches-lingo/issues/13)
- Add front-end router [#11](https://github.com/archesproject/arches-lingo/issues/11)
- Add concept and scheme serializers [#103](https://github.com/archesproject/arches-lingo/issues/103)
- Add backend for search [#67](https://github.com/archesproject/arches-lingo/issues/67)
- Add concept and scheme pages [#15](https://github.com/archesproject/arches-lingo/issues/15)
- Add concept hierarchy component [#18](https://github.com/archesproject/arches-lingo/issues/18)

### Fixed
- Merge language finder implementations [#92](https://github.com/archesproject/arches-lingo/issues/92)

### Deprecated

Expand Down
3 changes: 2 additions & 1 deletion arches_lingo/migrations/0002_load_lingo_lists.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import io
import os

from django.db import migrations
from django.core import management
from django.db import migrations

from arches_lingo.settings import APP_ROOT


Expand Down
34 changes: 34 additions & 0 deletions arches_lingo/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from arches.app.models.models import ResourceInstance, TileModel
from arches.app.models.serializers import ArchesModelSerializer, ArchesTileSerializer


class SchemeStatementSerializer(ArchesTileSerializer):
class Meta:
model = TileModel
graph_slug = "scheme"
root_node = "statement"
fields = "__all__"


class SchemeSerializer(ArchesModelSerializer):
class Meta:
model = ResourceInstance
graph_slug = "scheme"
nodegroups = "__all__"
fields = "__all__"


class ConceptStatementSerializer(ArchesTileSerializer):
class Meta:
model = TileModel
graph_slug = "concept"
root_node = "statement"
fields = "__all__"


class ConceptSerializer(ArchesModelSerializer):
class Meta:
model = ResourceInstance
graph_slug = "concept"
nodegroups = "__all__"
fields = "__all__"
4 changes: 1 addition & 3 deletions arches_lingo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
Django settings for arches_lingo project.
"""

import json
import os
import sys
import arches
import inspect
import semantic_version
from datetime import datetime, timedelta
Expand Down Expand Up @@ -249,6 +246,7 @@ def get_optional_env_variable(var_name, default=None) -> str:
"guardian",
"captcha",
"revproxy",
"rest_framework",
"corsheaders",
"oauth2_provider",
"django_celery_results",
Expand Down
21 changes: 16 additions & 5 deletions arches_lingo/src/arches_lingo/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ import { useToast } from "primevue/usetoast";
import {
ANONYMOUS,
DEFAULT_ERROR_TOAST_LIFE,
ENGLISH,
ERROR,
USER_KEY,
selectedLanguageKey,
systemLanguageKey,
} from "@/arches_lingo/constants.ts";

import { routeNames } from "@/arches_lingo/routes.ts";
import { fetchUser } from "@/arches_lingo/api.ts";

import type { User } from "@/arches_lingo/types.ts";

import HierarchySplitter from "@/arches_lingo/components/tree/HierarchySplitter.vue";
import PageHeader from "@/arches_lingo/components/header/PageHeader.vue";
import SideNav from "@/arches_lingo/components/sidenav/SideNav.vue";

import type { Ref } from "vue";
import type { Language } from "@/arches_vue_utils/types";
import type { User } from "@/arches_lingo/types";

const user = ref<User | null>(null);
const setUser = (userToSet: User | null) => {
user.value = userToSet;
};
provide(USER_KEY, { user, setUser });

const selectedLanguage: Ref<Language> = ref(ENGLISH);
provide(selectedLanguageKey, selectedLanguage);
const systemLanguage = ENGLISH; // TODO: get from settings
provide(systemLanguageKey, systemLanguage);

const router = useRouter();
const route = useRoute();
const toast = useToast();
Expand Down Expand Up @@ -64,8 +74,9 @@ router.beforeEach(async (to, _from, next) => {
<PageHeader v-if="route.meta.shouldShowNavigation" />
<div style="display: flex; flex: auto; flex-direction: row">
<SideNav v-if="route.meta.shouldShowNavigation" />
<div style="margin: 1rem; flex: auto">
<RouterView />
<div style="flex: auto">
<HierarchySplitter v-if="route.meta.shouldShowHierarchy" />
<RouterView v-else />
</div>
</div>
</main>
Expand Down
56 changes: 19 additions & 37 deletions arches_lingo/src/arches_lingo/api.ts
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,26 @@ export const login = async (username: string, password: string) => {
headers: { "X-CSRFTOKEN": getToken() },
body: JSON.stringify({ username, password }),
});
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
const parsed = await response.json();
if (!response.ok) throw new Error(parsed.message || response.statusText);
return parsed;
};

export const logout = async () => {
const response = await fetch(arches.urls.api_logout, {
method: "POST",
headers: { "X-CSRFTOKEN": getToken() },
});
if (response.ok) {
return true;
}
try {
const error = await response.json();
throw new Error(error.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
if (response.ok) return true;
const parsedError = await response.json();
throw new Error(parsedError.message || response.statusText);
};

export const fetchUser = async () => {
const response = await fetch(arches.urls.api_user);
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
const parsed = await response.json();
if (!response.ok) throw new Error(parsed.message || response.statusText);
return parsed;
};

export const fetchSearchResults = async (
Expand All @@ -67,15 +49,15 @@ export const fetchSearchResults = async (
});

const url = `${arches.urls.api_search}?${params.toString()}`;

const response = await fetch(url);
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
const parsed = await response.json();
if (!response.ok) throw new Error(parsed.message || response.statusText);
return parsed;
};

export const fetchConcepts = async () => {
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
const response = await fetch(arches.urls.api_concepts);
const parsed = await response.json();
if (!response.ok) throw new Error(parsed.message || response.statusText);
return parsed;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { nextTick, ref, watch, onMounted } from "vue";
import { useGettext } from "vue3-gettext";
import { useRouter } from "vue-router";

import AutoComplete from "primevue/autocomplete";
import Button from "primevue/button";
Expand All @@ -12,24 +13,22 @@ import SearchResult from "@/arches_lingo/components/basic-search/SearchResult.vu

import { fetchSearchResults } from "@/arches_lingo/api.ts";
import { DEFAULT_ERROR_TOAST_LIFE, ERROR } from "@/arches_lingo/constants.ts";
import { routeNames } from "@/arches_lingo/routes.ts";

import type { AutoCompleteOptionSelectEvent } from "primevue/autocomplete";
import type { VirtualScrollerLazyEvent } from "primevue/virtualscroller";

import type { Concept } from "@/arches_lingo/types.ts";
import type { SearchResultItem } from "@/arches_lingo/types.ts";

const { $gettext } = useGettext();
const router = useRouter();
const toast = useToast();

const props = defineProps({
searchResultsPerPage: {
type: Number,
required: true,
},
searchResultItemSize: {
type: Number,
required: true,
},
});
interface Props {
searchResultsPerPage: number;
searchResultItemSize: number;
toggleModal: () => void;
}
const props = defineProps<Props>();

const autoCompleteInstance = ref<InstanceType<typeof AutoComplete> | null>(
null,
Expand All @@ -38,7 +37,7 @@ const autoCompleteKey = ref(0);
const computedSearchResultsHeight = ref("");
const isLoading = ref(false);
const isLoadingAdditionalResults = ref(false);
const searchResults = ref<Concept[]>([]);
const searchResults = ref<SearchResultItem[]>([]);
const searchResultsPage = ref(1);
const searchResultsTotalCount = ref(0);
const query = ref("");
Expand Down Expand Up @@ -125,7 +124,10 @@ const loadAdditionalSearchResults = (event: VirtualScrollerLazyEvent) => {
}
};

const navigateToReport = () => {};
const navigateToReport = (event: AutoCompleteOptionSelectEvent) => {
props.toggleModal();
router.push({ name: routeNames.concept, params: { id: event.value.id } });
};

onMounted(focusInput);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
<script setup lang="ts">
import { getItemLabel } from "@/arches_vue_utils/utils.ts";
import { ENGLISH } from "@/arches_lingo/constants.ts";

import type { PropType } from "vue";
import type { Concept } from "@/arches_lingo/types.ts";
import type { SearchResultItem } from "@/arches_lingo/types.ts";

defineProps({
searchResult: {
type: Object as PropType<{ index: number; option: Concept }>,
type: Object as PropType<{ index: number; option: SearchResultItem }>,
required: true,
},
});

const getParentLabels = (item: Concept, preferredLanguage: string): string => {
const getParentLabels = (
item: SearchResultItem,
preferredLanguageCode: string,
systemLanguageCode: string,
): string => {
const arrowIcon = " → ";

return item.parents.reduce((acc, parent, index) => {
const label = getItemLabel(parent, preferredLanguage, "en-US").value;
const label = getItemLabel(
parent,
preferredLanguageCode,
systemLanguageCode,
).value;
if (label) {
return acc + (index > 0 ? arrowIcon : "") + label;
}
Expand All @@ -35,11 +44,18 @@ const getParentLabels = (item: Concept, preferredLanguage: string): string => {
/>

<div style="margin: 0 0.5rem">
{{ getItemLabel(searchResult.option, "en-US", "en-US").value }}
{{
getItemLabel(searchResult.option, ENGLISH.code, ENGLISH.code)
.value
}}
</div>

<div class="search-result-hierarchy">
[ {{ getParentLabels(searchResult.option, "en-US") }} ]
[
{{
getParentLabels(searchResult.option, ENGLISH.code, ENGLISH.code)
}}
]
</div>
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ import {
const { $gettext } = useGettext();

const visible = ref(false);
const toggleModal = () => {
visible.value = !visible.value;
};
</script>

<template>
<Button
icon="pi pi-search"
:label="$gettext('Search')"
@click="visible = true"
@click="toggleModal"
/>

<Dialog
Expand All @@ -48,6 +51,7 @@ const visible = ref(false);
<BasicSearch
:search-results-per-page="SEARCH_RESULTS_PER_PAGE"
:search-result-item-size="SEARCH_RESULT_ITEM_SIZE"
:toggle-modal="toggleModal"
/>
</div>
</Dialog>
Expand Down
Loading
Loading