Basic Indexing + search

This commit is contained in:
Roman Hotsiy 2017-01-23 23:29:52 +02:00
parent 21a3ab0b1f
commit e1ce5ea316
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
9 changed files with 178 additions and 15 deletions

View File

@ -159,7 +159,7 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
propName = relative[1];
}
let prop = props.find(p => p._name === propName);
if (!prop) return;
if (!prop || prop.isTrivial) return;
prop.expanded = true;
this.cdr.markForCheck();
this.cdr.detectChanges();

View File

@ -1,2 +1,6 @@
<input #search (keyup)="update(search.value)">
<button (click)="tmpSearch()"> Expand Search </button>
<input #search (keyup)="update(search.value)" placeholder="Search">
<ul class="search-results">
<li class="result" *ngFor="let item of items" (click)="clickSearch(item)">
{{item.menuItem.name}}
<li>
</ul>

View File

@ -1,3 +1,22 @@
:host {
display: block;
padding: 20px;
background: silver;
}
input {
width: 100%;
box-sizing: border-box;
padding: 5px;
}
.search-results {
margin: 10px 0 0;
padding: 0;
list-style: none;
> li {
display: block;
cursor: pointer;
}
}

View File

@ -1,6 +1,6 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
import { Marker, SearchService } from '../../services/';
import { Marker, SearchService, MenuService } from '../../services/';
@Component({
selector: 'redoc-search',
@ -10,27 +10,34 @@ import { Marker, SearchService } from '../../services/';
})
export class RedocSearch implements OnInit {
logo:any = {};
items: any[] = [];
constructor(private marker: Marker, public search: SearchService) {
constructor(private marker: Marker, public search: SearchService, public menu: MenuService) {
}
init() {
this.search.indexAll();
}
update(val) {
let searchRes = this.search.search(val);
this.items = Object.keys(searchRes).map(id => ({
menuItem: this.menu.getItemById(id),
pointers: searchRes[id].map(el => el.pointer)
}));
this.marker.mark(val);
}
tmpSearch() {
this.search.ensureSearchVisible([
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/category/properties/sub',
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/tags',
'/paths/~1pet/post/parameters/0/schema/properties/tags'
]);
clickSearch(item) {
this.search.ensureSearchVisible(
item.pointers
);
this.marker.remark();
this.menu.activate(item.menuItem.flatIdx);
this.menu.scrollToActive();
}
ngOnInit() {
this.init();
}
}

View File

@ -417,6 +417,10 @@ export class MenuService {
return res;
}
getItemById(id: string):MenuItem {
return this.flatItems.find(item => item.id === id);
}
destroy() {
this._hashSubscription.unsubscribe();
this._scrollSubscription.unsubscribe();

View File

@ -1,13 +1,124 @@
import { Injectable } from '@angular/core';
import { AppStateService } from './app-state.service';
import { JsonPointer, groupBy, SpecManager, StringMap} from '../utils/';
import * as lunr from 'lunr';
interface IndexElement {
menuPtr: string;
title: string;
body: string;
pointer: string;
}
const index = lunr(function () {
this.field('menuPtr', {boost: 0});
this.field('title', {boost: 1.5});
this.field('body');
this.ref('pointer');
})
const store:StringMap<IndexElement> = {};
@Injectable()
export class SearchService {
constructor(private app: AppStateService) {
window['locator'] = this;
constructor(private app: AppStateService, private spec: SpecManager) {
}
ensureSearchVisible(containingPointers: string[]) {
this.app.searchContainingPointers.next(containingPointers);
}
indexAll() {
const swagger = this.spec.schema;
this.indexPaths(swagger);
}
search(q):StringMap<IndexElement[]> {
const res:IndexElement[] = index.search(q).map(res => {
return store[res.ref];
});
const grouped = groupBy(res, 'menuPtr');
return grouped;
}
index(element: IndexElement) {
index.add(element);
store[element.pointer] = element;
}
indexPaths(swagger:any) {
const paths = swagger.paths
const basePtr = '#/paths';
Object.keys(paths).forEach(path => {
let opearations = paths[path];
Object.keys(opearations).forEach(verb => {
const opearation = opearations[verb];
const ptr = JsonPointer.join(basePtr, [path, verb]);
this.indexOperation(opearation, ptr);
});
});
}
indexOperation(operation:any, opPtr:string) {
this.index({
pointer: opPtr,
menuPtr: opPtr,
title: operation.summary,
body: operation.description
});
this.indexOperationResponses(operation, opPtr);
}
indexOperationResponses(operation:any, operationPtr:string) {
const responses = operation.responses;
if (!responses) return;
Object.keys(responses).forEach(code => {
const resp = responses[code];
const respPtr = JsonPointer.join(operationPtr, ['responses', code]);
this.index({
pointer: respPtr,
menuPtr: operationPtr,
title: code,
body: resp.description
});
if (resp.schema) {
this.indexSchema(resp.schema, '', JsonPointer.join(respPtr, 'schema'), operationPtr);
}
});
}
indexSchema(_schema:any, name: string, absolutePointer: string, menuPointer: string) {
if (!_schema) return;
let schema = _schema;
let title = name;
if (schema.$ref) {
schema = this.spec.byPointer(_schema.$ref);
title = name + ' ' + JsonPointer.baseName(_schema.$ref);
}
let body = schema.description; // TODO: defaults, examples, etc...
if (schema.type === 'array') {
this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer);
return;
}
this.index({
pointer: absolutePointer,
menuPtr: menuPointer,
title,
body
})
// TODO: allof etc
if (schema.properties) {
Object.keys(schema.properties).forEach(propName => {
let propPtr = JsonPointer.join(absolutePointer, ['properties', propName]);
this.indexSchema(schema.properties[propName], propName, propPtr, menuPointer);
})
}
}
}

View File

@ -1,5 +1,9 @@
'use strict';
export interface StringMap<T> {
[key: string]: T;
}
export function stringify(obj:any) {
return JSON.stringify(obj);
}
@ -16,6 +20,18 @@ export function isBlank(obj: any): boolean {
return obj == undefined;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
export function groupBy<T>(array: T[], key:string):StringMap<T[]> {
return array.reduce<StringMap<T[]>>(function(res, value) {
if (hasOwnProperty.call(res, value[key])) {
res[value[key]].push(value);
} else {
res[value[key]] = [value];
}
return res;
}, {});
}
export function statusCodeType(statusCode) {
if (statusCode < 100 || statusCode > 599) {
throw new Error('invalid HTTP code');

View File

@ -1,5 +1,6 @@
export * from './custom-error-handler';
export * from './helpers';
export * from './md-renderer';
export * from './spec-manager';
export { default as JsonPointer } from './JsonPointer';

View File

@ -111,6 +111,7 @@
"hint.css": "^2.3.2",
"json-pointer": "^0.6.0",
"json-schema-ref-parser": "^3.1.2",
"lunr": "^0.7.2",
"mark.js": "github:julmot/mark.js",
"openapi-sampler": "^0.3.3",
"prismjs": "^1.5.1",