Add document route
This commit is contained in:
parent
38d86a3785
commit
c62db432c7
15 changed files with 320 additions and 210 deletions
1
app/components/icons/pencil-outline.js
Normal file
1
app/components/icons/pencil-outline.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { PencilOutline as default } from 'ember-mdi';
|
||||
|
|
@ -1,66 +1,3 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { Magnify, Table, MapOutline } from 'ember-mdi';
|
||||
|
||||
export default class AdminIndexesItemDocumentsController extends Controller {
|
||||
@tracked q = null;
|
||||
@tracked limit = 20;
|
||||
@tracked page = 1;
|
||||
@tracked dataView = 'table';
|
||||
@tracked sort;
|
||||
|
||||
queryParams = ['q', 'page', 'limit', 'sort'];
|
||||
|
||||
Magnify = Magnify;
|
||||
|
||||
// key: view name, value: icon
|
||||
views = {
|
||||
table: Table,
|
||||
map: MapOutline,
|
||||
};
|
||||
|
||||
get fromHits() {
|
||||
return 1 + this.model.data.offset;
|
||||
}
|
||||
|
||||
get toHits() {
|
||||
return this.model.data.offset + this.model.data.hits.length;
|
||||
}
|
||||
|
||||
get sortedFields() {
|
||||
const fields = Object.keys(this.model.stats.fieldDistribution);
|
||||
const primaryKey = this.model.index.primaryKey ?? 'id';
|
||||
const sortedArray = fields
|
||||
.filter((item) => item !== primaryKey)
|
||||
.sort((a, b) => {
|
||||
if (a > b || a.charAt(0) === '_') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return [primaryKey].concat(sortedArray);
|
||||
}
|
||||
|
||||
@action
|
||||
searchTextChanged(value) {
|
||||
this.q = value === '' ? null : value;
|
||||
}
|
||||
|
||||
@action
|
||||
onSort(newSort) {
|
||||
if (`${newSort}:desc` === this.sort) {
|
||||
this.sort = null;
|
||||
} else if (`${newSort}:asc` === this.sort) {
|
||||
this.sort = `${newSort}:desc`;
|
||||
} else {
|
||||
this.sort = `${newSort}:asc`;
|
||||
}
|
||||
}
|
||||
}
|
||||
export default class AdminIndexesItemDocumentsController extends Controller {}
|
||||
|
|
|
|||
66
app/controllers/admin/indexes/item/documents/index.js
Normal file
66
app/controllers/admin/indexes/item/documents/index.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { Magnify, Table, MapOutline } from 'ember-mdi';
|
||||
|
||||
export default class AdminIndexesItemDocumentsIndexController extends Controller {
|
||||
@tracked q = null;
|
||||
@tracked limit = 20;
|
||||
@tracked page = 1;
|
||||
@tracked dataView = 'table';
|
||||
@tracked sort;
|
||||
|
||||
queryParams = ['q', 'page', 'limit', 'sort'];
|
||||
|
||||
Magnify = Magnify;
|
||||
|
||||
// key: view name, value: icon
|
||||
views = {
|
||||
table: Table,
|
||||
map: MapOutline,
|
||||
};
|
||||
|
||||
get fromHits() {
|
||||
return 1 + this.model.data.offset;
|
||||
}
|
||||
|
||||
get toHits() {
|
||||
return this.model.data.offset + this.model.data.hits.length;
|
||||
}
|
||||
|
||||
get sortedFields() {
|
||||
const fields = Object.keys(this.model.stats.fieldDistribution);
|
||||
const primaryKey = this.model.index.primaryKey ?? 'id';
|
||||
const sortedArray = fields
|
||||
.filter((item) => item !== primaryKey)
|
||||
.sort((a, b) => {
|
||||
if (a > b || a.charAt(0) === '_') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return [primaryKey].concat(sortedArray);
|
||||
}
|
||||
|
||||
@action
|
||||
searchTextChanged(value) {
|
||||
this.q = value === '' ? null : value;
|
||||
}
|
||||
|
||||
@action
|
||||
onSort(newSort) {
|
||||
if (`${newSort}:desc` === this.sort) {
|
||||
this.sort = null;
|
||||
} else if (`${newSort}:asc` === this.sort) {
|
||||
this.sort = `${newSort}:desc`;
|
||||
} else {
|
||||
this.sort = `${newSort}:asc`;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
app/models/document.js
Normal file
21
app/models/document.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import query from '../utils/query';
|
||||
|
||||
export default class Document {
|
||||
constructor(data = {}) {
|
||||
for (const key in data) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
// get path() {
|
||||
// return `${this.#index.path}/documents/${this.documentId}`;
|
||||
// }
|
||||
|
||||
// get isNew() {
|
||||
// return Boolean(!this.documentId);
|
||||
// }
|
||||
|
||||
// get documentId() {
|
||||
// return this[this.#index.primaryKey];
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
import query from '../utils/query';
|
||||
// import { TrackedArray } from 'tracked-built-ins';
|
||||
// import { trackedFunction } from 'ember-resources/util/function';
|
||||
import Document from './document';
|
||||
|
||||
export default class Index {
|
||||
// stats = trackedFunction(this, () => query(`${this.indexPath}/stats`));
|
||||
// displayedAttributes = trackedFunction(this, () =>
|
||||
// query(`${this.indexPath}/settings/displayed-attributes`).then(
|
||||
// (arr) => new TrackedArray(arr)
|
||||
// )
|
||||
// );
|
||||
|
||||
constructor(data = {}) {
|
||||
for (const key in data) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
get indexPath() {
|
||||
get path() {
|
||||
return `indexes/${this.uid}`;
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +18,45 @@ export default class Index {
|
|||
return Boolean(!this.uid);
|
||||
}
|
||||
|
||||
getSortableAttributes() {
|
||||
return query(`${this.path}/settings/sortable-attributes`);
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return query(`${this.path}/stats`);
|
||||
}
|
||||
|
||||
// getDocuments(searchParams) {
|
||||
// return query(`${this.path}/documents`, {
|
||||
// body: searchParams,
|
||||
// });
|
||||
// }
|
||||
|
||||
getDocument(uid) {
|
||||
return query(`${this.path}/documents/${uid}`).then(
|
||||
(result) => new Document(this, result)
|
||||
);
|
||||
}
|
||||
|
||||
search(q, options = {}) {
|
||||
options.q = q;
|
||||
|
||||
return query(`${this.path}/search`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
});
|
||||
}
|
||||
|
||||
save(data) {
|
||||
const path = this.isNew ? 'indexes' : this.path;
|
||||
const method = this.isNew ? 'POST' : 'PATCH';
|
||||
|
||||
return query(path, {
|
||||
method: method,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// get allAttributesAreDisplayed() {
|
||||
// return (
|
||||
// this.displayedAttributes.value &&
|
||||
|
|
@ -33,73 +66,53 @@ export default class Index {
|
|||
// }
|
||||
|
||||
// getDisplayedAttributes() {
|
||||
// return query(`${this.indexPath}/settings/displayed-attributes`);
|
||||
// return query(`${this.path}/settings/displayed-attributes`);
|
||||
// }
|
||||
|
||||
// updateDisplayedAttributes(value) {
|
||||
// return query(`${this.indexPath}/settings/displayed-attributes`, {
|
||||
// return query(`${this.path}/settings/displayed-attributes`, {
|
||||
// method: 'POST',
|
||||
// body: JSON.stringify(value),
|
||||
// });
|
||||
// }
|
||||
|
||||
// resetDisplayedAttributes() {
|
||||
// return query(`${this.indexPath}/settings/displayed-attributes`, {
|
||||
// return query(`${this.path}/settings/displayed-attributes`, {
|
||||
// method: 'DELETE',
|
||||
// });
|
||||
// }
|
||||
|
||||
// getSearchableAttributes() {
|
||||
// return query(`${this.indexPath}/settings/searchable-attributes`);
|
||||
// return query(`${this.path}/settings/searchable-attributes`);
|
||||
// }
|
||||
|
||||
// updateSearchableAttributes(value) {
|
||||
// return query(`${this.indexPath}/settings/searchable-attributes`, {
|
||||
// return query(`${this.path}/settings/searchable-attributes`, {
|
||||
// method: 'POST',
|
||||
// body: JSON.stringify(value),
|
||||
// });
|
||||
// }
|
||||
|
||||
// resetSearchableAttributes() {
|
||||
// return query(`${this.indexPath}/settings/searchable-attributes`, {
|
||||
// return query(`${this.path}/settings/searchable-attributes`, {
|
||||
// method: 'DELETE',
|
||||
// });
|
||||
// }
|
||||
|
||||
getSortableAttributes() {
|
||||
return query(`${this.indexPath}/settings/sortable-attributes`);
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return query(`${this.indexPath}/stats`);
|
||||
}
|
||||
|
||||
// getSettings() {
|
||||
// return query(`${this.indexPath}/settings`);
|
||||
// }
|
||||
|
||||
search(q, options = {}) {
|
||||
options.q = q;
|
||||
|
||||
return query(`${this.indexPath}/search`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
});
|
||||
}
|
||||
|
||||
// get fields() {
|
||||
// return this.stats.value
|
||||
// ? Object.keys(this.stats.value.fieldDistribution)
|
||||
// : [];
|
||||
// }
|
||||
|
||||
save(data) {
|
||||
const path = this.isNew ? 'indexes' : this.indexPath;
|
||||
const method = this.isNew ? 'POST' : 'PATCH';
|
||||
// getSettings() {
|
||||
// return query(`${this.path}/settings`);
|
||||
// }
|
||||
|
||||
return query(path, {
|
||||
method: method,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
// stats = trackedFunction(this, () => query(`${this.path}/stats`));
|
||||
// displayedAttributes = trackedFunction(this, () =>
|
||||
// query(`${this.path}/settings/displayed-attributes`).then(
|
||||
// (arr) => new TrackedArray(arr)
|
||||
// )
|
||||
// );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ Router.map(function () {
|
|||
this.route('settings');
|
||||
this.route('info');
|
||||
this.route('edit');
|
||||
this.route('documents');
|
||||
this.route('documents', function () {
|
||||
this.route('document', { path: '/:uid' });
|
||||
});
|
||||
});
|
||||
this.route('new');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,45 +1,3 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class AdminIndexesItemDocumentsRoute extends Route {
|
||||
queryParams = {
|
||||
q: {
|
||||
refreshModel: true,
|
||||
},
|
||||
limit: {
|
||||
refreshModel: true,
|
||||
},
|
||||
page: {
|
||||
refreshModel: true,
|
||||
},
|
||||
sort: {
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
|
||||
async model({ q, page, limit, sort }) {
|
||||
const offset = limit * --page;
|
||||
const index = this.modelFor('admin.indexes.item');
|
||||
const options = { offset, limit };
|
||||
|
||||
if (sort) {
|
||||
options.sort = [sort];
|
||||
}
|
||||
|
||||
return hash({
|
||||
index,
|
||||
data: index.search(q, options),
|
||||
sortableAttributes: index.getSortableAttributes(),
|
||||
stats: index.getStats(),
|
||||
});
|
||||
}
|
||||
|
||||
resetController(controller, isExiting) {
|
||||
if (isExiting) {
|
||||
controller.q = null;
|
||||
controller.page = 1;
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
export default class AdminIndexesItemDocumentsRoute extends Route {}
|
||||
|
|
|
|||
13
app/routes/admin/indexes/item/documents/document.js
Normal file
13
app/routes/admin/indexes/item/documents/document.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class AdminIndexesItemDocumentsDocumentRoute extends Route {
|
||||
model({ uid }) {
|
||||
const index = this.modelFor('admin.indexes.item');
|
||||
|
||||
return hash({
|
||||
index,
|
||||
document: index.getDocument(uid),
|
||||
});
|
||||
}
|
||||
}
|
||||
45
app/routes/admin/indexes/item/documents/index.js
Normal file
45
app/routes/admin/indexes/item/documents/index.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class AdminIndexesItemDocumentsIndexRoute extends Route {
|
||||
queryParams = {
|
||||
q: {
|
||||
refreshModel: true,
|
||||
},
|
||||
limit: {
|
||||
refreshModel: true,
|
||||
},
|
||||
page: {
|
||||
refreshModel: true,
|
||||
},
|
||||
sort: {
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
|
||||
async model({ q, page, limit, sort }) {
|
||||
const offset = limit * --page;
|
||||
const index = this.modelFor('admin.indexes.item');
|
||||
const options = { offset, limit };
|
||||
|
||||
if (sort) {
|
||||
options.sort = [sort];
|
||||
}
|
||||
|
||||
return hash({
|
||||
index,
|
||||
data: index.search(q, options),
|
||||
sortableAttributes: index.getSortableAttributes(),
|
||||
stats: index.getStats(),
|
||||
});
|
||||
}
|
||||
|
||||
resetController(controller, isExiting) {
|
||||
if (isExiting) {
|
||||
controller.q = null;
|
||||
controller.page = 1;
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +1 @@
|
|||
<InputText
|
||||
@placeholder='Search'
|
||||
@prefixIcon={{this.Magnify}}
|
||||
@value={{this.q}}
|
||||
@onChange={{this.searchTextChanged}}
|
||||
/>
|
||||
|
||||
{{!-- <div class='flex items-center mb-4'>
|
||||
<div class='flex items-center ml-auto'>
|
||||
<Icons::Check class='mr-1' @size='18' />
|
||||
Hits:
|
||||
{{@model.data.estimatedTotalHits}}
|
||||
<Icons::ClockOutline class='ml-6 mr-1' @size='18' />
|
||||
Time spent:
|
||||
{{@model.data.processingTimeMs}}
|
||||
ms
|
||||
</div>
|
||||
</div> --}}
|
||||
|
||||
{{#if (gt @model.data.estimatedTotalHits 0)}}
|
||||
<RoyalTable
|
||||
@data={{@model.data.hits}}
|
||||
@sortingProp={{this.sort}}
|
||||
@onSort={{this.onSort}}
|
||||
class='w-full'
|
||||
as |Row data|
|
||||
>
|
||||
<Row as |Column|>
|
||||
{{#each-in @model.stats.fieldDistribution as |field|}}
|
||||
<Column
|
||||
@name={{capitalize field}}
|
||||
@prop={{field}}
|
||||
@sortable={{includes field @model.sortableAttributes}}
|
||||
>
|
||||
{{#let (get data field) as |value|}}
|
||||
{{if (eq (type-of value) 'object') (stringify value) value}}
|
||||
{{/let}}
|
||||
</Column>
|
||||
{{/each-in}}
|
||||
</Row>
|
||||
</RoyalTable>
|
||||
|
||||
<div class='flex items-center'>
|
||||
{{this.fromHits}}
|
||||
—
|
||||
{{this.toHits}}
|
||||
of
|
||||
{{@model.data.estimatedTotalHits}}
|
||||
|
||||
<Pagination
|
||||
@count={{@model.data.estimatedTotalHits}}
|
||||
@current={{this.page}}
|
||||
@perPage={{@model.data.limit}}
|
||||
class='ml-auto'
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
No results found
|
||||
{{/if}}
|
||||
{{outlet}}
|
||||
|
|
|
|||
0
app/templates/admin/indexes/item/documents/document.hbs
Normal file
0
app/templates/admin/indexes/item/documents/document.hbs
Normal file
68
app/templates/admin/indexes/item/documents/index.hbs
Normal file
68
app/templates/admin/indexes/item/documents/index.hbs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<InputText
|
||||
@placeholder='Search'
|
||||
@prefixIcon={{this.Magnify}}
|
||||
@value={{this.q}}
|
||||
@onChange={{this.searchTextChanged}}
|
||||
/>
|
||||
|
||||
{{!-- <div class='flex items-center mb-4'>
|
||||
<div class='flex items-center ml-auto'>
|
||||
<Icons::Check class='mr-1' @size='18' />
|
||||
Hits:
|
||||
{{@model.data.estimatedTotalHits}}
|
||||
<Icons::ClockOutline class='ml-6 mr-1' @size='18' />
|
||||
Time spent:
|
||||
{{@model.data.processingTimeMs}}
|
||||
ms
|
||||
</div>
|
||||
</div> --}}
|
||||
|
||||
{{#if (gt @model.data.estimatedTotalHits 0)}}
|
||||
<RoyalTable
|
||||
@data={{@model.data.hits}}
|
||||
@sortingProp={{this.sort}}
|
||||
@onSort={{this.onSort}}
|
||||
class='w-full'
|
||||
as |Row data|
|
||||
>
|
||||
<Row as |Column|>
|
||||
{{#each-in @model.stats.fieldDistribution as |field|}}
|
||||
<Column
|
||||
@name={{capitalize field}}
|
||||
@prop={{field}}
|
||||
@sortable={{includes field @model.sortableAttributes}}
|
||||
>
|
||||
{{#let (get data field) as |value|}}
|
||||
{{if (eq (type-of value) 'object') (stringify value) value}}
|
||||
{{/let}}
|
||||
</Column>
|
||||
{{/each-in}}
|
||||
<Column>
|
||||
<LinkTo
|
||||
@route='admin.indexes.item.documents.document'
|
||||
@models={{array @model.index.uid (get data @model.index.primaryKey)}}
|
||||
>
|
||||
<Icons::PencilOutline class='mr-1' />
|
||||
Edit
|
||||
</LinkTo>
|
||||
</Column>
|
||||
</Row>
|
||||
</RoyalTable>
|
||||
|
||||
<div class='flex items-center'>
|
||||
{{this.fromHits}}
|
||||
—
|
||||
{{this.toHits}}
|
||||
of
|
||||
{{@model.data.estimatedTotalHits}}
|
||||
|
||||
<Pagination
|
||||
@count={{@model.data.estimatedTotalHits}}
|
||||
@current={{this.page}}
|
||||
@perPage={{@model.data.limit}}
|
||||
class='ml-auto'
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
No results found
|
||||
{{/if}}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'meiliadmin/tests/helpers';
|
||||
|
||||
module(
|
||||
'Unit | Controller | admin/indexes/item/documents/index',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup(
|
||||
'controller:admin/indexes/item/documents/index'
|
||||
);
|
||||
assert.ok(controller);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'meiliadmin/tests/helpers';
|
||||
|
||||
module(
|
||||
'Unit | Route | admin/indexes/item/documents/document',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup(
|
||||
'route:admin/indexes/item/documents/document'
|
||||
);
|
||||
assert.ok(route);
|
||||
});
|
||||
}
|
||||
);
|
||||
11
tests/unit/routes/admin/indexes/item/documents/index-test.js
Normal file
11
tests/unit/routes/admin/indexes/item/documents/index-test.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'meiliadmin/tests/helpers';
|
||||
|
||||
module('Unit | Route | admin/indexes/item/documents/index', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:admin/indexes/item/documents/index');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue