Skip to content

Semantic search, rough but functional #471

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

Merged
merged 47 commits into from
Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
063a9a8
Add the semantic search MultibranchView (#455)
jgonggrijp May 15, 2021
a4c12b6
Add a semantic search radio channel (#455)
jgonggrijp May 15, 2021
bcf8b98
Add the top-level semantic search view (#455)
jgonggrijp May 15, 2021
5193c76
Obtain branchout view from semChannel (#455)
jgonggrijp May 15, 2021
7ef3334
Translate semSearch Dropdown labels after i18nPromise resolves (#455)
jgonggrijp May 16, 2021
809e3a9
Add semantic search to the welcome view (#455)
jgonggrijp May 16, 2021
552576b
Make normalizeRange helper function async (#455)
jgonggrijp May 16, 2021
dfa44b0
Test semantic search Multibranch (#455)
jgonggrijp May 16, 2021
a648e3d
Give Multibranch an empty model by default (#455)
jgonggrijp May 16, 2021
e8408ed
Use Dropdown double wrapper construction as dictated by Bulma (#455)
jgonggrijp May 16, 2021
27909a4
Give semSearch Dropdown an empty option to start with (#455)
jgonggrijp May 16, 2021
94c92cd
Give semSearch Chain empty model by default, propagate precedent (#455)
jgonggrijp May 16, 2021
09c3643
Fix positioning of nested multibranches (#455)
jgonggrijp May 16, 2021
39178a6
Use composition instead of inheritance for MultibranchRow (#455)
jgonggrijp May 17, 2021
c9e8ebe
Add preliminary shape-shifting semSearch filter input (#455)
jgonggrijp May 18, 2021
9c14db3
Enable filtering in chains (#455)
jgonggrijp May 18, 2021
96528aa
Make the add button green (#455)
jgonggrijp May 18, 2021
da56abe
Use smaller title and subtitle for semantic search (#455)
jgonggrijp May 18, 2021
ee6ecfb
Create a single hierarchical semantic search datamodel (#455)
jgonggrijp May 18, 2021
542e0ff
Dump semantic search data to console on submit (#455)
jgonggrijp May 18, 2021
cc48d47
Annotate whether a dropdown choice is a predicate traversal (#455)
jgonggrijp May 18, 2021
b07ba5b
Automatically focus controls for typing in semantic search (#455)
jgonggrijp May 18, 2021
7f78f34
Propagate filter model to next Dropdown in Chain (#455)
jgonggrijp May 20, 2021
a6cbd8e
Encapsulate serialization specifics in filter models (#455)
jgonggrijp May 20, 2021
3de323f
Cache the computed range in the Dropdown model (#455)
jgonggrijp May 20, 2021
2131032
Factor out common hbs partial for the SPARQL preamble (#455)
jgonggrijp May 25, 2021
8b5f657
Add template for semantic SPARQL query global outline (#455)
jgonggrijp May 25, 2021
571c746
Add modelToQuery implementation (#455)
jgonggrijp May 26, 2021
59e8ac4
Log the query to console when submitting semSearch (#455)
jgonggrijp May 26, 2021
9c72238
Propagate the range (#455)
jgonggrijp May 26, 2021
b2ebd9e
Test base units of modelToQuery (#455)
jgonggrijp May 26, 2021
6ffbf62
Fix tagging, spacing and wrapping in combine{And,Or} (#455)
jgonggrijp May 27, 2021
ad90b26
Prevent superfluous wrapping in combine{And,Or} (#455)
jgonggrijp May 27, 2021
9da69ae
Test recursive units of modelToQuery (#455)
jgonggrijp May 29, 2021
295a152
Document modelToQuery (#455)
jgonggrijp May 30, 2021
081eed3
Position textual/semantic search UIs as equals with tabs (#455)
jgonggrijp May 30, 2021
5b578da
Toggle tabs (#455)
jgonggrijp May 31, 2021
03f8d81
Trigger event instead of logging to the console (#455)
jgonggrijp May 31, 2021
9aaeb18
Perform the search and show the results (#455)
jgonggrijp May 31, 2021
6475ac8
Replace SearchResultItemView by ItemSummaryBlockView (#455)
jgonggrijp May 31, 2021
94fb8f9
Remove superfluous whitespace around ItemSummaryBlock (#455)
jgonggrijp May 31, 2021
e80a65f
Open AnnotationPanel for bare item search results (#455)
jgonggrijp May 31, 2021
d0a3e35
Merge branch 'develop' into feature/semantic-search
jgonggrijp May 31, 2021
77ec7f4
Allow superclasses as traversal target (#455, #467)
jgonggrijp May 31, 2021
838a63d
Update tests to reflect superclass allowance (#455)
jgonggrijp Jun 1, 2021
b948751
Add CIDOC, FRBRoo and Erlangen to the prefetch vocabularies (#455)
jgonggrijp Jun 2, 2021
7f47745
Work around improperly served RDF/XML from CIDOC CRM (#455)
jgonggrijp Jun 2, 2021
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
4 changes: 4 additions & 0 deletions frontend/src/aspects/exploration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import mainRouter from '../global/main-router';
import explorer from '../global/explorer-view';
import controller from '../global/explorer-controller';
import welcomeView from '../global/welcome-view';
import semanticSearchView from '../global/semantic-search';
import SuggestionsPanel from '../panel-suggestions/suggestions-view';
import deparam from '../utilities/deparam';

Expand Down Expand Up @@ -89,5 +90,8 @@ channel.on('currentRoute', (route, panel) => {
// panel.
browserHistory.replaceState(panel.cid, document.title);
});

welcomeView.on({'search:start': controller.resetSourceListFromSearchResults}, controller);
welcomeView.on({'suggestions:show': controller.showSuggestionsPanel}, controller);

semanticSearchView.on('search', controller.resetSemanticSearch, controller);
4 changes: 4 additions & 0 deletions frontend/src/aspects/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import explorationRouter from '../global/exploration-router';
import userFsm from '../global/user-fsm';
import explorerView from '../global/explorer-view';
import notFoundView from '../global/notfound-view';
import semanticSearchView from '../global/semantic-search';

history.once('route notfound', () => {
menuView.render().$el.appendTo('#header');
Expand Down Expand Up @@ -54,5 +55,8 @@ userFsm.on('exit:lost', () => notFoundView.$el.detach());
menuView.on('feedback', () => feedbackView.render().$el.appendTo('body'));

feedbackView.on('close', () => feedbackView.$el.detach());

welcomeView.on('search:start', () => userFsm.handle('explore'));
welcomeView.on('suggestions:show', () => userFsm.handle('explore'));

semanticSearchView.on('search', () => userFsm.handle('explore'));
18 changes: 18 additions & 0 deletions frontend/src/explorer/explorer-event-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
isOntologyClass,
} from '../utilities/linked-data-utilities';
import { itemsForSourceQuery } from '../sparql/compile-query';
import modelToQuery from '../semantic-search/modelToQuery';

interface ExplorerEventController extends Events {}
class ExplorerEventController {
Expand Down Expand Up @@ -90,6 +91,19 @@ class ExplorerEventController {
this.explorerView.reset(sourceListPanel);
}

resetSemanticSearch(model: Model): SearchResultListView {
const query = modelToQuery(model);
const items = new ItemGraph();
items.sparqlQuery(query);
const collection = new FlatItemCollection(items);
const resultsView = new SearchResultListView({
collection,
selectable: false,
});
this.explorerView.reset(resultsView);
return resultsView;
}

showSuggestionsPanel() {
const suggestionsView = new SuggestionsView();
this.explorerView.reset(suggestionsView);
Expand All @@ -111,6 +125,10 @@ class ExplorerEventController {
const flat = collection.get(annotation.id);
flat.trigger('focus', flat);
});
} else {
const itemPanel = new AnnotationView({ model: result });
this.explorerView.popUntil(searchResults).push(itemPanel);
return itemPanel;
}
}

Expand Down
1 change: 1 addition & 0 deletions frontend/src/explorer/route-patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export default {
'item:external:edit': 'explore/item/:serial/external/edit',
'item:annotations': 'explore/item/:serial/annotations',
'search:results:sources': 'explore/sources?*queryParams',
'search:results:semantic': 'explore/query',
};
2 changes: 1 addition & 1 deletion frontend/src/forms/add-button-template.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button type=button class="button">
<button type=button class="button is-success">
<span>Add</span>
<span class="icon is-small"><i class="fas fa-plus"></i></span>
</button>
1 change: 1 addition & 0 deletions frontend/src/forms/multifield-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class Multifield extends CompositeView {
}

extend(Multifield.prototype, {
className: 'rit-multifield',
template: multifieldTemplate,
subviews: [{
view: 'collectionView',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/forms/select2-picker-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ export default class Select2PickerView extends BasePicker {
this.$('select').select2('destroy');
return super.remove();
}

open(): this {
this.$('select').select2('open');
return this;
}
}
5 changes: 5 additions & 0 deletions frontend/src/global/hbsPartials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Handlebars from 'handlebars/dist/handlebars.runtime';

import sparqlPreamble from '../sparql/query-templates/preamble-template';

Handlebars.registerPartial('sparqlPreamble', sparqlPreamble);
3 changes: 3 additions & 0 deletions frontend/src/global/semantic-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SemanticSearchView from '../semantic-search/semantic-search-view';

export default new SemanticSearchView();
3 changes: 2 additions & 1 deletion frontend/src/global/welcome-view.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import WelcomeView from '../welcome/welcome-view';
import searchBox from './searchbox';
import semSearch from './semantic-search';

export default new WelcomeView({searchBox});
export default new WelcomeView({searchBox, semSearch});
1 change: 1 addition & 0 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import './global/scroll-easings';
import { i18nPromise } from './global/i18n';
import './global/internalLinks';
import './global/hbsHelpers';
import './global/hbsPartials';
import user from './global/user';
import { prefetch } from './global/ld-store';
import './global/item-cache';
Expand Down
13 changes: 7 additions & 6 deletions frontend/src/panel-search-results/search-result-base-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { extend, after, constant } from 'lodash';

import View, { CompositeView, ViewOptions as BaseOpt } from '../core/view';
import FlatItem from '../common-adapters/flat-item-model';
import SearchResultItemView from '../item-summary-block/item-summary-block-view';

import SearchResultSourceView from './search-result-source-view';
import SearchResultItemView from './search-result-item-view';
import searchResultBaseTemplate from './search-result-base-template';

export interface ViewOptions extends BaseOpt {
Expand Down Expand Up @@ -36,11 +36,12 @@ export default class SearchResultView extends CompositeView<FlatItem> {
}

setContentView(model: FlatItem): void {
const ctor = (
model.has('annotation') ?
SearchResultSourceView :
SearchResultItemView
);
let ctor: any = SearchResultSourceView;
if (!model.has('annotation')) {
ctor = SearchResultItemView
this.$el.removeClass('search-result box');
this.undelegate('click');
}
this.contentView = new ctor({ model }).render();
this.render().activateContent();
}
Expand Down
18 changes: 14 additions & 4 deletions frontend/src/semantic-search/chain-view-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { constant } from 'lodash';
import { enableI18n, startStore, endStore, event } from '../test-util';
import mockOntology from '../mock-data/mock-ontology';

import Model from '../core/model';
import ldChannel from '../common-rdf/radio';
import { readit } from '../common-rdf/ns';
import Graph from '../common-rdf/graph';
Expand All @@ -17,35 +18,44 @@ describe('semantic search ChainView', function() {
beforeEach(function() {
const ontology = new Graph(mockOntology);
ldChannel.reply('ontology:graph', constant(ontology));
ldChannel.reply(
'ontology:promise', constant(Promise.resolve(ontology))
);
});

afterEach(function() {
ldChannel.stopReplying('ontology:graph');
ldChannel.stopReplying('ontology:promise');
});

afterEach(endStore);

it('can be constructed in isolation', function() {
it('can be constructed in isolation', async function() {
const view = new Chain();
expect(view.items.length).toBe(1);
await event(view.items[0], 'ready');
expect(view.items[0]['typeGroup']).toBeDefined();
expect(view.items[0]['typeGroup'].collection.length).toBe(3);
});

it('can be constructed with a preselection', function() {
it('can be constructed with a preselection', async function() {
const Reader = ldChannel.request('obtain', readit('Reader'));
const view = new Chain({ model: Reader });
const model = new Model({ precedent: Reader });
const view = new Chain({ model });
expect(view.items.length).toBe(1);
await event(view.items[0], 'ready');
expect(view.items[0]['predicateGroup']).toBeDefined();
expect(view.items[0]['predicateGroup'].collection.length).toBe(2);
});

it('appends dropdowns as choices are made', function() {
it('appends dropdowns as choices are made', async function() {
const view = new Chain();
const dropdown1 = view.items[0] as Dropdown;
await event(dropdown1, 'ready');
dropdown1.val(readit('Reader'));
expect(view.items.length).toBe(2);
const dropdown2 = view.items[1] as Dropdown;
await event(dropdown2, 'ready');
expect(dropdown2.predicateGroup).toBeDefined();
expect(dropdown2.predicateGroup.collection.length).toBe(2);
});
Expand Down
40 changes: 29 additions & 11 deletions frontend/src/semantic-search/chain-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,55 @@ import { extend } from 'lodash';
import Model from '../core/model';
import Collection from '../core/collection';
import View, { CollectionView } from '../core/view';
import { xsd } from '../common-rdf/ns';

import semChannel from './radio';
import Dropdown from './dropdown-view';
import FilterInput from './filter-input-view';

export default class Chain extends CollectionView {
initialize(): void {
let collection = this.collection;
this.model = this.model || new Model();
let collection = this.collection || this.model.get('chain');
if (!collection) {
this.collection = collection = new Collection([
{ precedent: this.model } as unknown as Model,
collection = new Collection([
this.model.pick(['precedent', 'range']) as unknown as Model,
]);
}
this.model.set('chain', this.collection = collection);
this.initItems().render().initCollectionEvents();
this.listenTo(collection, 'change:selection', this.updateControls);
}

makeItem(model: Model): View {
const precedent = model.get('precedent');
const scheme = precedent && precedent.id.split(':')[0];
switch (scheme) {
case 'logic':
// TODO
case 'filter':
// TODO
const scheme = model.get('scheme');
if (scheme === 'logic' && model.get('action') !== 'not') {
return semChannel.request('branchout', model);
}
if (scheme === 'filter') {
return new FilterInput({ model });
}
return new Dropdown({ model });
}

updateControls(model, selection): void {
const collection = this.collection;
while (collection.last() !== model) collection.pop();
collection.push({ precedent: selection } as unknown as Model);
if (!selection) return;
const [scheme, action] = selection.id.split(':');
const precedent = model.get('precedent');
const range = model.get('range');
const newModel = new Model();
switch (scheme) {
case 'filter':
newModel.set('filter', selection);
case 'logic':
newModel.set({ precedent, range, scheme, action });
break;
default:
newModel.set('precedent', selection);
}
collection.push(newModel);
}
}

Expand Down
Loading