Skip to content

Commit

Permalink
feat: integrate terms and support term network #347
Browse files Browse the repository at this point in the history
* chore: created files

* chore: moved old specs

* docs: created docs files

* docs: wrote down ideas for term search component

* docs: wrote down idea for term machine

* docs: added docs for term service

* docs: added extra information to term machine spec

* docs: updated spec

* chore: typo in spec

* chore: fixed typo in spec

* feat: created term service and models

* feat: created term machine

* test: fixed broken test

* chore: aligned form-element styling with figma

* chore: term machine integration WIP

* chore: form-search component progress WIP

* chore: saving single term uri works WIP

* feat: dropdown form element styling WIP

* chore: term integration progress WIP

* chore: translation changes WIP

* chore: update CSS version WIP

* chore: added extra info to selected terms WIP

* feat: keep terms in the search results when selected WIP

* test: updated tests for components package WIP

* test: fixed broken tests WIP

* feat: group search results by source WIP

* test: updated tests

* chore: visual improvements

* chore: allowed clicking of dropdown labels

* chore: improved integration with object root WIP

* chore: fixed saving of objects with terms WIP

* chore: added <ul> for every Term property WIP

* fix: updated old input fields WIP

* test: updated tests

* chore: styling fix

* fix: fixed broken object form validation

* fix: clicking term's form element opens term page

* chore: revert term form element click changes

* chore: final fixes

* test: updated tests

* chore: improved switching between term and object page

* fix: fixed save double click when term page closes

* chore: fix build error

Co-authored-by: Arthur Joppart <arthur@digita.ai>
  • Loading branch information
lem-onade and BelgianNoise authored Jul 14, 2021
1 parent 133026d commit aef3483
Show file tree
Hide file tree
Showing 69 changed files with 6,102 additions and 2,222 deletions.
144 changes: 144 additions & 0 deletions docs/modules/specs/images/objects/object-term-search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A basic implementation of the `CollectionObjectFormComponent` exists, or should

The finished component should look like this:

image::../../assets/objects/object-details.svg[CollectionObjectRootComponent]
image::../images/objects/object-details.svg[CollectionObjectRootComponent]

Create in '@netwerk-digitaal-erfgoed/solid-crs-manage' package under 'lib/collection-object/collection-object-root.component'.

Expand Down
53 changes: 53 additions & 0 deletions docs/modules/specs/pages/create-term-search-component.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
= Technical Documentation: Create term search component
:sectanchors:
:url-repo: https://github.com/netwerk-digitaal-erfgoed/solid-crs
:imagesdir: ../images

== Author(s)

* Stijn Taelemans


== References


// * https://www.wrike.com/open.htm?id=682525025[Wrike task]
* Branch: `feat/create-term-search-component`
* Projects: https://github.com/netwerk-digitaal-erfgoed/solid-crs[nde-erfgoed-components]


== Introduction


=== Overview

This document is about the creation of a reusable sidebar list component.


=== Assumptions

The `TermMachine` is created.


== Solution


=== Suggested or proposed solution


==== TermSearchComponent

The finished component should look like this:

image::../images/objects/object-term-search.svg[TermSearchComponent]


Generate in '@netwerk-digitaal-erfgoed/solid-crs-manage' package under 'lib/features/object/terms/term-search.component.ts', with tag `<nde-term-search>`

Make use of existing `<form-element>` components. Take a look at the demo form component for example usage of a multiple select dropdown.

Making use of properties, pass the search results into the `TermSearchComponent` and display them using `CardComponent` s. Their titles should be the name of the found term, the description is their URI. Set the id of the `CardComponent` to the `uri` of the `Term`. When clicked, send a `TermEvents.CLICKED_TERM` to the `TermMachine`. This should add the selected terms to the `TermMachine` context. (`selected`)

Selected results should show a `CheckboxChecked` svg on the left, otherwise `CheckboxUnchecked`.

Pressing the submit button should fire `SUBMITTED` to the `TermMachine`.
167 changes: 167 additions & 0 deletions docs/modules/specs/pages/set-up-term-machine.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
= Technical Documentation: Set up term machine

== Author(s)

* Stijn Taelemans


== References

// * https://www.wrike.com/open.htm?id=692044114[Wrike task]
* Branch: `feat/set-up-term-machine`
* Project: https://github.com/netwerk-digitaal-erfgoed/solid-crs[nde-erfgoed-manage]

== Introduction

=== Overview

This document is about the set up of the term machine, which can be invoked when editing an object. It acts as the brain of the `TermSearchComponent`, which allows a user to query the term network provided by NDE.


== Solution

=== Suggested or proposed solution


==== Term Model

Create 'term.ts' under 'lib/terms/' in the 'nde-erfgoed-core' package.

The model should look like this. It extends `Resource`.

[source, ts]
----
{
name: string,
}
----

==== Collection Object Model

This model should be edited to allow for multiple values for certain properties. (Those who should have term URIs as values)

Also update the related services. (e.g. collection-object-solid-store.ts)


==== Term service

The term service should contain the following functionality:

The service's constructor should allow for the input of an `endpoint` URL. This is the URL of the term network's GraphQL endpoint. (By default: `https://termennetwerk-api.netwerkdigitaalerfgoed.nl/graphql`)

A `query(query: string, sources: string[]): Promise<Term[]>` function that, when given a `query` string and a list of `sources`, sends a GraphQL query to the term network's endpoint. See example request payload below.

[source, json]
----
{
"query":
"query Terms ($sources: [ID]!, $query: String!) {
terms (sources: $sources query: $query) {
source {
name
uri
}
terms {
uri
prefLabel
altLabel
hiddenLabel
scopeNote
broader {
uri
prefLabel
}
narrower {
uri
prefLabel
}
}
}
}",
"variables": {
"sources":[
"http://data.bibliotheken.nl/thes/brinkman/sparql",
"https://data.netwerkdigitaalerfgoed.nl/beeldengeluid/gtaa-geografischenamen/sparql",
"http://data.bibliotheken.nl/thesp/sparql"
],
"query":"bidprentjes"
}
}
----


A `getSources(): string[]` function that queries the term network's endpoint for existing sources. Request payload:

[source, json]
----
{
"query": "query Sources { sources { name alternateName uri creators { uri alternateName } } }",
"variables": { }
}
----


==== Term Machine & States

The term machine is the brains of the `TermSearchComponent`.

Use the file structure and naming convention of the object feature.

* term.machine.ts
* term.events.ts

The `TermMachine` has four possible states:

* `TermStates.IDLE` (initial)
* `TermStates.QUERYING`

The `TermStates.IDLE` state is the initial state of the machine. Take a look at the events described below for a better picture of how the machine can transition.

When `TermEvents.QUERY_UPDATED` is fired, the machine should transition to `QUERYING`. The context should have been edited to include the form input. (query string and selected sources)

From `TermStates.QUERYING`, the machine transitions back to `IDLE` when `TermService.query()` resolves. When rejected, handle errors like we do in other features.

A user can select results, which fires the `CLICKED_TERM` event. This should add the clicked `Term` to `selectedTerms` in the machine's context.

When the `TermEvents.SUBMITTED` is fired, the `TermMachine` terminates and returns the clicked term.


==== Term Context

The context of the `TermMachine` consists of the following items.
[source, js]
----
{
field: string, // the form field this term belongs to (e.g. creator)
query: string, // the user input (value of text input field)
sources: string, // the sources selected by the user
searchResults: Term[], // a list containing all search results
selectedTerms: Term[], // the selected search results
}
----

==== Term Events

Create following events in 'lib/features/object/term/term.events.ts'

[options="header"]
|======================================
| Event | Payload

| `TermEvents.SUBMITTED`
| `{ selectedTerms: Term[] }`

| `TermEvents.CLICKED_TERM`
| `{ term: Term }`

| `TermEvents.QUERY_UPDATED`
| `{ query: string }`

|======================================


==== Collection Object Machine, Events

When a form field, containing a Term, is clicked, the `TermMachine` should be invoked in a new state: `ObjectStates.EDITING_FIELD`. Pass the clicked `FormElement.field` to the context of the `TermMachine`. Transition to this state with a new `ObjectEvents.CLICKED_FIELD` event.

When the `TermMachine` terminates, its `selectedTerms` should assigned to the corresponding object property.
2 changes: 1 addition & 1 deletion packages/solid-crs-client/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { login, handleIncomingRedirect, logout, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser';
export { getSolidDataset, getThing, getStringNoLocale, getStringNoLocaleAll, getUrl, getStringByLocaleAll, getStringWithLocale, getStringWithLocaleAll, getThingAll, getUrlAll, removeAll, removeThing, saveSolidDatasetAt, setThing, removeUrl, Thing, addStringWithLocale, addStringNoLocale, addUrl, addDatetime, getDatetime, createThing, asUrl, overwriteFile, deleteFile, ThingPersisted, getInteger, addInteger, access, addDecimal, getDecimal } from '@inrupt/solid-client';
export { getSolidDataset, getThing, getStringNoLocale, getStringNoLocaleAll, getUrl, getStringByLocaleAll, getStringWithLocale, getStringWithLocaleAll, getThingAll, getUrlAll, removeAll, removeThing, saveSolidDatasetAt, setThing, removeUrl, Thing, addStringWithLocale, addStringNoLocale, addUrl, addDatetime, getDatetime, createThing, asUrl, overwriteFile, deleteFile, ThingPersisted, getInteger, addInteger, access, addDecimal, getDecimal, SolidDataset, createSolidDataset } from '@inrupt/solid-client';
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class LargeCardComponent extends LitElement {
flex-direction: column;
gap: 0;
}
nde-content-header {
border: none;
}
.content {
background-color: var(--colors-foreground-inverse);
padding: var(--gap-large);
Expand Down Expand Up @@ -63,7 +66,7 @@ export class LargeCardComponent extends LitElement {
${this.showHeader
? html`
<nde-content-header inverse>
<nde-content-header inverse noBorder>
<slot name="icon" slot="icon"></slot>
<slot name="title" slot="title"></slot>
<slot name="subtitle" slot="subtitle"></slot>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ describe('ObjectCardComponent', () => {
uri: 'test uri',
name: 'test name',
description: 'test description',
subject: 'test subject',
subject: [ { uri: '', name: 'test subject' } ],
image: 'https://images.unsplash.com/photo-1615390164801-cf2e70f32b53?ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8M3x8fGVufDB8fHx8&ixlib=rb-1.2.1&w=1000&q=80',
updated: +new Date(),
updated: new Date().getTime().toString(),
type: '',
collection: 'uri',
} as CollectionObject;

beforeEach(() => {
Expand Down Expand Up @@ -60,7 +62,7 @@ describe('ObjectCardComponent', () => {
const html = window.document.body.getElementsByTagName(tag)[0].shadowRoot.innerHTML;

expect(html).toContain(testObject.name);
expect(html).toContain(testObject.subject);
expect(html).toContain(testObject.subject[0].name);
expect(html).not.toContain('Name unavailable');
expect(html).not.toContain('Subject unavailable');
expect(html).toContain('- Just Now');
Expand All @@ -77,23 +79,22 @@ describe('ObjectCardComponent', () => {

expect(html).not.toContain(testObject.name);
expect(html).toContain('Name unavailable');
expect(html).toContain(testObject.subject);
expect(html).toContain(testObject.subject[0].name);
expect(html).not.toContain('Subject unavailable');
expect(html).toContain('- Just Now');

});

it('should display message when subject of the object is undefined', async () => {
it('should display message when subject of the object is empty list', async () => {

component.object = { ...testObject, subject: undefined };
component.object = { ...testObject, subject: [] };
window.document.body.appendChild(component);
await component.updateComplete;

const html = window.document.body.getElementsByTagName(tag)[0].shadowRoot.innerHTML;

expect(html).toContain(testObject.name);
expect(html).not.toContain('Name unavailable');
expect(html).not.toContain(testObject.subject);
expect(html).not.toContain(testObject.subject[0].name);
expect(html).toContain('Subject unavailable');
expect(html).toContain('- Just Now');

Expand All @@ -109,7 +110,7 @@ describe('ObjectCardComponent', () => {

expect(html).toContain(testObject.name);
expect(html).not.toContain('Name unavailable');
expect(html).toContain(testObject.subject);
expect(html).toContain(testObject.subject[0].name);
expect(html).not.toContain('Subject unavailable');
expect(html).not.toContain('- Just Now');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class ObjectCardComponent extends LitElement {
</span>
<span slot='subtitle'>
<span class='subject'>
${this.object.subject ?? this.translator?.translate('nde.features.collections.card.subject-unavailable')}
${this.object.subject && this.object.subject[0]?.name ? this.object.subject[0].name : this.translator?.translate('nde.features.collections.card.subject-unavailable')}
</span>
<span class='time-ago'>
${this.object.updated ? ` - ${timeAgo}` : ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class DemoNDECardComponent extends LitElement {
name: 'Object nummertje 1',
description: 'Een object',
image: 'https://images.unsplash.com/photo-1615390164801-cf2e70f32b53?ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8M3x8fGVufDB8fHx8&ixlib=rb-1.2.1&w=1000&q=80',
subject: 'Wel Degelijk Geen Molen',
subject: [ { name: 'Wel Degelijk Geen Molen' } ],
type: 'type',
updated: '1620216600000',
collection: undefined,
Expand Down
8 changes: 4 additions & 4 deletions packages/solid-crs-components/lib/demo/demo-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,16 @@ export class DemoFormComponent extends RxLitElement {
<label for="title"></label>
</li>
<li>
<input type="checkbox" id="Plane" name="Plane" value="Car">
<input type="checkbox" id="Plane" name="Plane">
<label for="Plane">Plane</label>
</li>
<li>
<input type="checkbox" id="Car" name="Car" value="Plane">
<input type="checkbox" id="Car" name="Car">
<label for="Car">Car</label>
</li>
<li>
<input type="checkbox" id="Boat" name="Boat" value="Boat">
<label for="vehicle3">Boat</label>
<input type="checkbox" id="Boat" name="Boat">
<label for="Boat">Boat</label>
</li>
</ul>
<div slot="help">This still isn't helpful</div>
Expand Down
Loading

0 comments on commit aef3483

Please sign in to comment.