Skip to content

Commit eaa1345

Browse files
committed
Merge branch 'feature/semantic-search' into develop (merge PR #471)
2 parents bd5246e + 7f47745 commit eaa1345

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1334
-89
lines changed

frontend/src/aspects/exploration.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import mainRouter from '../global/main-router';
77
import explorer from '../global/explorer-view';
88
import controller from '../global/explorer-controller';
99
import welcomeView from '../global/welcome-view';
10+
import semanticSearchView from '../global/semantic-search';
1011
import SuggestionsPanel from '../panel-suggestions/suggestions-view';
1112
import deparam from '../utilities/deparam';
1213

@@ -89,5 +90,8 @@ channel.on('currentRoute', (route, panel) => {
8990
// panel.
9091
browserHistory.replaceState(panel.cid, document.title);
9192
});
93+
9294
welcomeView.on({'search:start': controller.resetSourceListFromSearchResults}, controller);
9395
welcomeView.on({'suggestions:show': controller.showSuggestionsPanel}, controller);
96+
97+
semanticSearchView.on('search', controller.resetSemanticSearch, controller);

frontend/src/aspects/navigation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import explorationRouter from '../global/exploration-router';
1212
import userFsm from '../global/user-fsm';
1313
import explorerView from '../global/explorer-view';
1414
import notFoundView from '../global/notfound-view';
15+
import semanticSearchView from '../global/semantic-search';
1516

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

5758
feedbackView.on('close', () => feedbackView.$el.detach());
59+
5860
welcomeView.on('search:start', () => userFsm.handle('explore'));
5961
welcomeView.on('suggestions:show', () => userFsm.handle('explore'));
62+
63+
semanticSearchView.on('search', () => userFsm.handle('explore'));

frontend/src/common-rdf/sync.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const priorityOverrides = {
2424
'application/n-quads': 0.6,
2525
};
2626

27+
// CIDOC serves its rdf/xml with a text/html content type. ARGH!
28+
const rdfPattern = /<\?xml[^]+<rdf:rdf[^]+<\/rdf:rdf/i;
29+
2730
const defaultSyncOptions = (function() {
2831
let optionsPromise: any;
2932

@@ -86,6 +89,11 @@ export function transform(jqXHR: JQuery.jqXHR): Promise<[
8689
} else {
8790
plaintext = jqXHR.responseText;
8891
}
92+
if (contentType === 'text/html' && rdfPattern.test(plaintext)) {
93+
// Workaround for improper content negotiation in CIDOC and possibly
94+
// other vocabularies.
95+
contentType = 'application/rdf+xml';
96+
}
8997
const input = streamify(plaintext);
9098
const serializer = new Serializer();
9199
return new Promise((resolve, reject) => {

frontend/src/explorer/explorer-event-controller.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
isOntologyClass,
3333
} from '../utilities/linked-data-utilities';
3434
import { itemsForSourceQuery } from '../sparql/compile-query';
35+
import modelToQuery from '../semantic-search/modelToQuery';
3536

3637
interface ExplorerEventController extends Events { }
3738
class ExplorerEventController {
@@ -86,6 +87,19 @@ class ExplorerEventController {
8687
this.explorerView.reset(sourceListPanel);
8788
}
8889

90+
resetSemanticSearch(model: Model): SearchResultListView {
91+
const query = modelToQuery(model);
92+
const items = new ItemGraph();
93+
items.sparqlQuery(query);
94+
const collection = new FlatItemCollection(items);
95+
const resultsView = new SearchResultListView({
96+
collection,
97+
selectable: false,
98+
});
99+
this.explorerView.reset(resultsView);
100+
return resultsView;
101+
}
102+
89103
showSuggestionsPanel() {
90104
const suggestionsView = new SuggestionsView();
91105
this.explorerView.reset(suggestionsView);
@@ -107,6 +121,10 @@ class ExplorerEventController {
107121
const flat = collection.get(annotation.id);
108122
flat.trigger('focus', flat);
109123
});
124+
} else {
125+
const itemPanel = new AnnotationView({ model: result });
126+
this.explorerView.popUntil(searchResults).push(itemPanel);
127+
return itemPanel;
110128
}
111129
}
112130

frontend/src/explorer/route-patterns.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export default {
1111
'item:external:edit': 'explore/item/:serial/external/edit',
1212
'item:annotations': 'explore/item/:serial/annotations',
1313
'search:results:sources': 'explore/sources?*queryParams',
14+
'search:results:semantic': 'explore/query',
1415
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<button type=button class="button">
1+
<button type=button class="button is-success">
22
<span>Add</span>
33
<span class="icon is-small"><i class="fas fa-plus"></i></span>
44
</button>

frontend/src/forms/multifield-view.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default class Multifield extends CompositeView {
6060
}
6161

6262
extend(Multifield.prototype, {
63+
className: 'rit-multifield',
6364
template: multifieldTemplate,
6465
subviews: [{
6566
view: 'collectionView',

frontend/src/forms/select2-picker-view.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,9 @@ export default class Select2PickerView extends BasePicker {
2020
this.$('select').select2('destroy');
2121
return super.remove();
2222
}
23+
24+
open(): this {
25+
this.$('select').select2('open');
26+
return this;
27+
}
2328
}

frontend/src/global/hbsPartials.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Handlebars from 'handlebars/dist/handlebars.runtime';
2+
3+
import sparqlPreamble from '../sparql/query-templates/preamble-template';
4+
5+
Handlebars.registerPartial('sparqlPreamble', sparqlPreamble);

frontend/src/global/ld-store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const defaultGraphs = [
1919
'http://dublincore.org/2012/06/14/dcterms.ttl',
2020
'http://dublincore.org/2012/06/14/dctype.ttl',
2121
'http://dublincore.org/2012/06/14/dcelements.ttl',
22+
'http://www.cidoc-crm.org/sites/default/files/cidoc_crm_v6.2.1-2018April.rdfs',
23+
'http://iflastandards.info/ns/fr/frbr/frbroo.xml',
24+
'http://erlangen-crm.org/current/',
2225
// activitystreams appears to consist of only a context.
2326
// as(),
2427
// We do not prefetch schema.org, because it is large and we do
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import SemanticSearchView from '../semantic-search/semantic-search-view';
2+
3+
export default new SemanticSearchView();

frontend/src/global/welcome-view.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import WelcomeView from '../welcome/welcome-view';
22
import searchBox from './searchbox';
3+
import semSearch from './semantic-search';
34

4-
export default new WelcomeView({searchBox});
5+
export default new WelcomeView({searchBox, semSearch});

frontend/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import './global/scroll-easings';
77
import { i18nPromise } from './global/i18n';
88
import './global/internalLinks';
99
import './global/hbsHelpers';
10+
import './global/hbsPartials';
1011
import user from './global/user';
1112
import { prefetch } from './global/ld-store';
1213
import './global/item-cache';

frontend/src/panel-search-results/search-result-base-view.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { extend, after, constant } from 'lodash';
22

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

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

1010
export interface ViewOptions extends BaseOpt {
@@ -36,11 +36,12 @@ export default class SearchResultView extends CompositeView<FlatItem> {
3636
}
3737

3838
setContentView(model: FlatItem): void {
39-
const ctor = (
40-
model.has('annotation') ?
41-
SearchResultSourceView :
42-
SearchResultItemView
43-
);
39+
let ctor: any = SearchResultSourceView;
40+
if (!model.has('annotation')) {
41+
ctor = SearchResultItemView
42+
this.$el.removeClass('search-result box');
43+
this.undelegate('click');
44+
}
4445
this.contentView = new ctor({ model }).render();
4546
this.render().activateContent();
4647
}

frontend/src/semantic-search/chain-view-test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { constant } from 'lodash';
33
import { enableI18n, startStore, endStore, event } from '../test-util';
44
import mockOntology from '../mock-data/mock-ontology';
55

6+
import Model from '../core/model';
67
import ldChannel from '../common-rdf/radio';
78
import { readit } from '../common-rdf/ns';
89
import Graph from '../common-rdf/graph';
@@ -17,35 +18,44 @@ describe('semantic search ChainView', function() {
1718
beforeEach(function() {
1819
const ontology = new Graph(mockOntology);
1920
ldChannel.reply('ontology:graph', constant(ontology));
21+
ldChannel.reply(
22+
'ontology:promise', constant(Promise.resolve(ontology))
23+
);
2024
});
2125

2226
afterEach(function() {
2327
ldChannel.stopReplying('ontology:graph');
28+
ldChannel.stopReplying('ontology:promise');
2429
});
2530

2631
afterEach(endStore);
2732

28-
it('can be constructed in isolation', function() {
33+
it('can be constructed in isolation', async function() {
2934
const view = new Chain();
3035
expect(view.items.length).toBe(1);
36+
await event(view.items[0], 'ready');
3137
expect(view.items[0]['typeGroup']).toBeDefined();
32-
expect(view.items[0]['typeGroup'].collection.length).toBe(3);
38+
expect(view.items[0]['typeGroup'].collection.length).toBe(4);
3339
});
3440

35-
it('can be constructed with a preselection', function() {
41+
it('can be constructed with a preselection', async function() {
3642
const Reader = ldChannel.request('obtain', readit('Reader'));
37-
const view = new Chain({ model: Reader });
43+
const model = new Model({ precedent: Reader });
44+
const view = new Chain({ model });
3845
expect(view.items.length).toBe(1);
46+
await event(view.items[0], 'ready');
3947
expect(view.items[0]['predicateGroup']).toBeDefined();
4048
expect(view.items[0]['predicateGroup'].collection.length).toBe(2);
4149
});
4250

43-
it('appends dropdowns as choices are made', function() {
51+
it('appends dropdowns as choices are made', async function() {
4452
const view = new Chain();
4553
const dropdown1 = view.items[0] as Dropdown;
54+
await event(dropdown1, 'ready');
4655
dropdown1.val(readit('Reader'));
4756
expect(view.items.length).toBe(2);
4857
const dropdown2 = view.items[1] as Dropdown;
58+
await event(dropdown2, 'ready');
4959
expect(dropdown2.predicateGroup).toBeDefined();
5060
expect(dropdown2.predicateGroup.collection.length).toBe(2);
5161
});

frontend/src/semantic-search/chain-view.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,55 @@ import { extend } from 'lodash';
33
import Model from '../core/model';
44
import Collection from '../core/collection';
55
import View, { CollectionView } from '../core/view';
6+
import { xsd } from '../common-rdf/ns';
67

8+
import semChannel from './radio';
79
import Dropdown from './dropdown-view';
10+
import FilterInput from './filter-input-view';
811

912
export default class Chain extends CollectionView {
1013
initialize(): void {
11-
let collection = this.collection;
14+
this.model = this.model || new Model();
15+
let collection = this.collection || this.model.get('chain');
1216
if (!collection) {
13-
this.collection = collection = new Collection([
14-
{ precedent: this.model } as unknown as Model,
17+
collection = new Collection([
18+
this.model.pick(['precedent', 'range']) as unknown as Model,
1519
]);
1620
}
21+
this.model.set('chain', this.collection = collection);
1722
this.initItems().render().initCollectionEvents();
1823
this.listenTo(collection, 'change:selection', this.updateControls);
1924
}
2025

2126
makeItem(model: Model): View {
22-
const precedent = model.get('precedent');
23-
const scheme = precedent && precedent.id.split(':')[0];
24-
switch (scheme) {
25-
case 'logic':
26-
// TODO
27-
case 'filter':
28-
// TODO
27+
const scheme = model.get('scheme');
28+
if (scheme === 'logic' && model.get('action') !== 'not') {
29+
return semChannel.request('branchout', model);
30+
}
31+
if (scheme === 'filter') {
32+
return new FilterInput({ model });
2933
}
3034
return new Dropdown({ model });
3135
}
3236

3337
updateControls(model, selection): void {
3438
const collection = this.collection;
3539
while (collection.last() !== model) collection.pop();
36-
collection.push({ precedent: selection } as unknown as Model);
40+
if (!selection) return;
41+
const [scheme, action] = selection.id.split(':');
42+
const precedent = model.get('precedent');
43+
const range = model.get('range');
44+
const newModel = new Model();
45+
switch (scheme) {
46+
case 'filter':
47+
newModel.set('filter', selection);
48+
case 'logic':
49+
newModel.set({ precedent, range, scheme, action });
50+
break;
51+
default:
52+
newModel.set('precedent', selection);
53+
}
54+
collection.push(newModel);
3755
}
3856
}
3957

0 commit comments

Comments
 (0)