mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-02 07:43:11 +03:00
Refactored search-index module to use class syntax
refs https://github.com/TryGhost/Team/issues/1665 - The class syntax allows to use constructor DI pattern that helps with unit testing and abstracting away the dependencies - It wasn't possible to test the internals without having access to the localStorage in tests (you couls access it but that's kind of leaky for tests to know what mechanism is used in the module intenally)
This commit is contained in:
parent
ce4206e91f
commit
d9e9d451c9
@ -2,23 +2,24 @@ import React from 'react';
|
||||
import './App.css';
|
||||
import AppContext from './AppContext';
|
||||
import PopupModal from './components/PopupModal';
|
||||
import {init as initSearchIndex} from './search-index.js';
|
||||
import SearchIndex from './search-index.js';
|
||||
|
||||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
const searchIndex = new SearchIndex({
|
||||
apiUrl: props.apiUrl,
|
||||
apiKey: props.apiKey
|
||||
});
|
||||
|
||||
this.state = {
|
||||
searchIndex
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
initSearchIndex({
|
||||
apiUrl: this.state.apiUrl,
|
||||
apiKey: this.state.apiKey
|
||||
});
|
||||
this.state.searchIndex.init();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -1,57 +1,68 @@
|
||||
import elasticlunr from 'elasticlunr';
|
||||
|
||||
let index;
|
||||
export default class SearchIndex {
|
||||
constructor({apiUrl, apiKey, storage = localStorage}) {
|
||||
this.apiUrl = apiUrl;
|
||||
this.apiKey = apiKey;
|
||||
this.storage = storage;
|
||||
|
||||
export const init = async function ({apiUrl, apiKey}) {
|
||||
// remove default stop words to search of *any* word
|
||||
elasticlunr.clearStopWords();
|
||||
this.index = null;
|
||||
|
||||
const url = `${apiUrl}/posts/?key=${apiKey}&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext`;
|
||||
this.init = this.init.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
const indexDump = JSON.parse(localStorage.getItem('ease_search_index'));
|
||||
|
||||
localStorage.removeItem('ease_index');
|
||||
localStorage.removeItem('ease_last');
|
||||
|
||||
function update(data) {
|
||||
data.posts.forEach(function (post) {
|
||||
index.addDoc(post);
|
||||
#updateIndex(data) {
|
||||
data.posts.forEach((post) => {
|
||||
this.index.addDoc(post);
|
||||
});
|
||||
|
||||
localStorage.setItem('ease_search_index', JSON.stringify(index));
|
||||
localStorage.setItem('ease_search_last', data.posts[0].updated_at);
|
||||
this.storage.setItem('ease_search_index', JSON.stringify(this.index));
|
||||
this.storage.setItem('ease_search_last', data.posts[0].updated_at);
|
||||
}
|
||||
|
||||
if (
|
||||
!indexDump
|
||||
) {
|
||||
return fetch(url)
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
if (data.posts.length > 0) {
|
||||
index = elasticlunr(function () {
|
||||
this.addField('title');
|
||||
this.addField('plaintext');
|
||||
this.setRef('id');
|
||||
});
|
||||
async init() {
|
||||
// remove default stop words to search of *any* word
|
||||
elasticlunr.clearStopWords();
|
||||
|
||||
update(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
index = elasticlunr.Index.load(indexDump);
|
||||
const url = `${this.apiUrl}/posts/?key=${this.apiKey}&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext`;
|
||||
|
||||
return fetch(`${url}&filter=updated_at:>'${localStorage.getItem('ease_search_last').replace(/\..*/, '').replace(/T/, ' ')}'`
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
if (data.posts.length > 0) {
|
||||
update(data);
|
||||
}
|
||||
});
|
||||
const indexDump = JSON.parse(this.storage.getItem('ease_search_index'));
|
||||
|
||||
this.storage.removeItem('ease_index');
|
||||
this.storage.removeItem('ease_last');
|
||||
|
||||
if (
|
||||
!indexDump
|
||||
) {
|
||||
return fetch(url)
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
if (data.posts.length > 0) {
|
||||
this.index = elasticlunr(function () {
|
||||
this.addField('title');
|
||||
this.addField('plaintext');
|
||||
this.setRef('id');
|
||||
});
|
||||
|
||||
this.#updateIndex(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.index = elasticlunr.Index.load(indexDump);
|
||||
|
||||
return fetch(`${url}&filter=updated_at:>'${this.storage.getItem('ease_search_last').replace(/\..*/, '').replace(/T/, ' ')}'`
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
if (data.posts.length > 0) {
|
||||
this.#updateIndex(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const search = function (value) {
|
||||
return index.search(value, {expand: true});
|
||||
};
|
||||
search(value) {
|
||||
return this.index.search(value, {expand: true});
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
import {init} from './search-index';
|
||||
import SearchIndex from './search-index';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('search index', function () {
|
||||
afterEach(function () {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
test('initializes search index', async () => {
|
||||
const apiUrl = 'http://localhost/ghost/api/content';
|
||||
const apiKey = 'secret_key';
|
||||
const searchIndex = new SearchIndex({apiUrl, apiKey, storage: localStorage});
|
||||
|
||||
const scope = nock('http://localhost/ghost/api/content')
|
||||
.get('/posts/?key=secret_key&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext')
|
||||
@ -12,8 +17,30 @@ describe('search index', function () {
|
||||
posts: [{}]
|
||||
});
|
||||
|
||||
await init({apiUrl, apiKey});
|
||||
await searchIndex.init({apiUrl, apiKey});
|
||||
|
||||
expect(scope.isDone()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('allows to search for indexed posts', async () => {
|
||||
const apiUrl = 'http://localhost/ghost/api/content';
|
||||
const apiKey = 'secret_key';
|
||||
const searchIndex = new SearchIndex({apiUrl, apiKey, storage: localStorage});
|
||||
|
||||
nock('http://localhost/ghost/api/content')
|
||||
.get('/posts/?key=secret_key&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext')
|
||||
.reply(200, {
|
||||
posts: [{
|
||||
id: 'sounique',
|
||||
title: 'Amazing Barcelona Life',
|
||||
plaintext: 'We are sitting by the pool and smashing out search features'
|
||||
}]
|
||||
});
|
||||
|
||||
await searchIndex.init({apiUrl, apiKey});
|
||||
|
||||
const searchResults = searchIndex.search('Barcelona');
|
||||
|
||||
expect(searchResults.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user