diff --git a/app/assets/javascripts/base.js.erb b/app/assets/javascripts/base.js.erb
index dbd0abe73..220e133cf 100644
--- a/app/assets/javascripts/base.js.erb
+++ b/app/assets/javascripts/base.js.erb
@@ -5,7 +5,6 @@
//= require utilities/getImageForLink
//= require honeybadger-js/dist/honeybadger.js
//= require serviceworker-companion
-//= require algolia/v3/algoliasearch.min
var instantClick
, InstantClick = instantClick = function(document, location, $userAgent) {
diff --git a/app/assets/javascripts/initializers/initScrolling.js.erb b/app/assets/javascripts/initializers/initScrolling.js.erb
index ad86e213a..663d9d353 100644
--- a/app/assets/javascripts/initializers/initScrolling.js.erb
+++ b/app/assets/javascripts/initializers/initScrolling.js.erb
@@ -1,6 +1,6 @@
'use strict';
-/* global algoliasearch, insertAfter, insertArticles, buildArticleHTML, nextPage:writable, fetching:writable, done:writable */
+/* global insertAfter, insertArticles, buildArticleHTML, nextPage:writable, fetching:writable, done:writable */
var client;
diff --git a/app/controllers/reading_list_items_controller.rb b/app/controllers/reading_list_items_controller.rb
index 211d0ce30..f17c1e813 100644
--- a/app/controllers/reading_list_items_controller.rb
+++ b/app/controllers/reading_list_items_controller.rb
@@ -2,7 +2,6 @@ class ReadingListItemsController < ApplicationController
def index
@reading_list_items_index = true
set_view
- generate_algolia_search_key
end
def update
@@ -16,18 +15,11 @@ class ReadingListItemsController < ApplicationController
private
- def generate_algolia_search_key
- params = { filters: "viewable_by:#{session_current_user_id}" }
- @secured_algolia_key = Algolia.generate_secured_api_key(
- ApplicationConfig["ALGOLIASEARCH_SEARCH_ONLY_KEY"], params
- )
- end
-
def set_view
@view = if params[:view] == "archive"
"archived"
else
- "valid"
+ "valid,confirmed"
end
end
end
diff --git a/app/javascript/.eslintrc.js b/app/javascript/.eslintrc.js
index c4b111c0b..8020fc0b8 100644
--- a/app/javascript/.eslintrc.js
+++ b/app/javascript/.eslintrc.js
@@ -45,7 +45,6 @@ module.exports = {
InstantClick: false,
filterXSS: false,
Pusher: false,
- algoliasearch: false,
ga: false,
Honeybadger: false,
AndroidBridge: false,
diff --git a/app/javascript/article-form/elements/__mocks__/algoliasearch.js b/app/javascript/article-form/elements/__mocks__/algoliasearch.js
deleted file mode 100644
index 28693ea0b..000000000
--- a/app/javascript/article-form/elements/__mocks__/algoliasearch.js
+++ /dev/null
@@ -1,62 +0,0 @@
-global.document.head.innerHTML =
- "" +
- "" +
- "";
-
-const mockIndex = {
- search: (query, _options) =>
- new Promise((resolve, _reject) => {
- process.nextTick(() => {
- const searchResults = {
- gi: {
- hits: [
- {
- name: 'git',
- bg_color_hex: '#888751',
- text_color_hex: '#56c938',
- hotness_score: 0,
- supported: true,
- objectID: '4',
- _highlightResult: {
- name: {
- value: 'git',
- matchLevel: 'full',
- fullyHighlighted: false,
- matchedWords: ['gi'],
- },
- bg_color_hex: {
- value: '#888751',
- matchLevel: 'none',
- matchedWords: [],
- },
- text_color_hex: {
- value: '#56c938',
- matchLevel: 'none',
- matchedWords: [],
- },
- },
- },
- ],
- nbHits: 1,
- page: 0,
- nbPages: 1,
- hitsPerPage: 10,
- processingTimeMS: 1,
- exhaustiveNbHits: true,
- query: 'gi',
- params:
- 'query=gi&hitsPerPage=10&filters=supported%3Atrue&restrictIndices=searchables_development%2CTag_development%2Cordered_articles_development%2Cordered_articles_by_published_at_development%2Cordered_articles_by_positive_reactions_count_development',
- },
- };
-
- const results = searchResults[query] || { hits: [] };
-
- resolve(results);
- });
- }),
-};
-const client = {
- initIndex: _index => mockIndex,
-};
-
-export default jest.fn().mockImplementation((_id, _key) => client);
diff --git a/app/javascript/readingList/readingList.jsx b/app/javascript/readingList/readingList.jsx
index 5d5fb2914..ff059640c 100644
--- a/app/javascript/readingList/readingList.jsx
+++ b/app/javascript/readingList/readingList.jsx
@@ -16,7 +16,7 @@ import { ItemListItemArchiveButton } from '../src/components/ItemList/ItemListIt
import { ItemListLoadMoreButton } from '../src/components/ItemList/ItemListLoadMoreButton';
import { ItemListTags } from '../src/components/ItemList/ItemListTags';
-const STATUS_VIEW_VALID = 'valid';
+const STATUS_VIEW_VALID = 'valid,confirmed';
const STATUS_VIEW_ARCHIVED = 'archived';
const READING_LIST_ARCHIVE_PATH = '/readinglist/archive';
const READING_LIST_PATH = '/readinglist';
@@ -50,19 +50,14 @@ export class ReadingList extends Component {
}
componentDidMount() {
- const { hitsPerPage, statusView } = this.state;
+ const { statusView } = this.state;
this.performInitialSearch({
- containerId: 'reading-list',
- indexName: 'SecuredReactions',
- searchOptions: {
- hitsPerPage,
- filters: `status:${statusView}`,
- },
+ searchOptions: { status: `${statusView}` },
});
}
- toggleStatusView = event => {
+ toggleStatusView = (event) => {
event.preventDefault();
const { query, selectedTags } = this.state;
@@ -174,12 +169,12 @@ export class ReadingList extends Component {
const isStatusViewValid = this.statusViewValid();
const archiveButtonLabel = isStatusViewValid ? 'archive' : 'unarchive';
- const itemsToRender = items.map(item => {
+ const itemsToRender = items.map((item) => {
return (
this.toggleArchiveStatus(e, item)}
+ onClick={(e) => this.toggleArchiveStatus(e, item)}
/>
);
@@ -226,7 +221,7 @@ export class ReadingList extends Component {
this.toggleStatusView(e)}
+ onClick={(e) => this.toggleStatusView(e)}
data-no-instant
>
{isStatusViewValid ? 'View Archive' : 'View Reading List'}
diff --git a/app/javascript/searchableItemList/searchableItemList.js b/app/javascript/searchableItemList/searchableItemList.js
index dca05149d..109866bcd 100644
--- a/app/javascript/searchableItemList/searchableItemList.js
+++ b/app/javascript/searchableItemList/searchableItemList.js
@@ -1,5 +1,5 @@
// Shared behavior between the reading list and history pages
-import setupAlgoliaIndex from '../src/utils/algolia';
+import { fetchSearch } from '../src/utils/search';
// Provides the initial state for the component
export function defaultState(options) {
@@ -59,25 +59,24 @@ export function clearSelectedTags(event) {
}
// Perform the initial search
-export function performInitialSearch({
- containerId,
- indexName,
- searchOptions = {},
-}) {
+export function performInitialSearch({ searchOptions = {} }) {
const component = this;
const { hitsPerPage } = component.state;
+ const dataHash = { page: 0, per_page: hitsPerPage };
- const index = setupAlgoliaIndex({ containerId, indexName });
+ if (searchOptions.status) {
+ dataHash.status = searchOptions.status.split(',');
+ }
- index.search('', searchOptions).then(result => {
+ const responsePromise = fetchSearch('reactions', dataHash);
+ return responsePromise.then((response) => {
+ const reactions = response.result;
component.setState({
- items: result.hits,
- totalCount: result.nbHits,
- index, // set the index in the component state, to be retrieved later
+ page: 0,
+ items: reactions,
itemsLoaded: true,
- // show the button if the number of total results is greater
- // than the number of results for the current page
- showLoadMoreButton: result.nbHits > hitsPerPage,
+ totalCount: response.total,
+ showLoadMoreButton: hitsPerPage < response.total,
});
});
}
@@ -90,28 +89,31 @@ export function search(query, { page, tags, statusView }) {
// we check `undefined` because page can be 0
const newPage = page === undefined ? component.state.page : page;
- const { index, hitsPerPage, items } = component.state;
+ const { hitsPerPage } = component.state;
+ const dataHash = {
+ search_fields: query,
+ page: newPage,
+ per_page: hitsPerPage,
+ };
- const filters = { hitsPerPage, page: newPage };
if (tags && tags.length > 0) {
- filters.tagFilters = tags;
+ dataHash.tag_names = tags;
+ dataHash.tag_boolean_mode = 'all';
}
if (statusView) {
- filters.filters = `status:${statusView}`;
+ dataHash.status = statusView.split(',');
}
- index.search(query, filters).then(result => {
- // append new items at the end
- const allItems =
- page === undefined ? result.hits : [...items, ...result.hits];
+
+ const responsePromise = fetchSearch('reactions', dataHash);
+ return responsePromise.then((response) => {
+ const reactions = response.result;
component.setState({
query,
page: newPage,
- items: result.hits,
- totalCount: allItems.length,
- // show the button if the number of items is lower than the number
- // of available results
- showLoadMoreButton: allItems.length < result.nbHits,
+ items: reactions,
+ totalCount: response.total,
+ showLoadMoreButton: reactions.length < response.total,
});
});
}
diff --git a/app/javascript/src/components/ItemList/ItemListItem.jsx b/app/javascript/src/components/ItemList/ItemListItem.jsx
index d755bfd97..6e9cfd515 100644
--- a/app/javascript/src/components/ItemList/ItemListItem.jsx
+++ b/app/javascript/src/components/ItemList/ItemListItem.jsx
@@ -4,13 +4,12 @@ import { PropTypes } from 'preact-compat';
export const ItemListItem = ({ item, children }) => {
const adaptedItem = {
- path: item.article_path || item.searchable_reactable_path,
- title: item.article_title || item.searchable_reactable_title,
- user: item.article_user || item.reactable_user,
- publishedDate: item.reactable_published_date,
- visitedDate: item.readable_visited_at,
- readingTime: item.article_reading_time || item.reading_time,
- tags: item.article_tags || item.reactable_tags,
+ path: item.reactable.path,
+ title: item.reactable.title,
+ user: item.reactable.user,
+ publishedDate: item.reactable.published_date_string,
+ readingTime: item.reactable.reading_time,
+ tags: item.reactable.tags,
};
// update readingTime to 1 min if the reading time is less than 1 min
@@ -24,17 +23,15 @@ export const ItemListItem = ({ item, children }) => {
{`${adaptedItem.user.name}・`}
- {adaptedItem.visitedDate
- ? `visited on ${adaptedItem.visitedDate}・`
- : `${adaptedItem.publishedDate}・`}
+ {`${adaptedItem.publishedDate}・`}
{`${adaptedItem.readingTime} min read・`}
{adaptedItem.tags.length > 0 ? (
- {adaptedItem.tags.map(tag => (
-
- {`#${tag}`}
+ {adaptedItem.tags.map((tag) => (
+
+ {`#${tag.name}`}
))}
@@ -49,30 +46,19 @@ export const ItemListItem = ({ item, children }) => {
);
};
-const historyItemPropTypes = PropTypes.shape({
- article_path: PropTypes.string.isRequired,
- article_title: PropTypes.string.isRequired,
- article_user: PropTypes.shape({
- username: PropTypes.string.isRequired,
- profile_image_90: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- }),
- article_reading_time: PropTypes.number.isRequired,
- readable_visited_at: PropTypes.string.isRequired,
- article_tags: PropTypes.arrayOf(PropTypes.string).isRequired,
-});
-
const readingListItemPropTypes = PropTypes.shape({
- searchable_reactable_path: PropTypes.string.isRequired,
- searchable_reactable_title: PropTypes.string.isRequired,
- reactable_user: PropTypes.shape({
- username: PropTypes.string.isRequired,
- profile_image_90: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- }),
- reading_time: PropTypes.number.isRequired,
- reactable_published_date: PropTypes.string.isRequired,
- reactable_tags: PropTypes.arrayOf(PropTypes.string).isRequired,
+ reactable: {
+ path: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ reading_time: PropTypes.number.isRequired,
+ published_date_string: PropTypes.string.isRequired,
+ user: PropTypes.shape({
+ username: PropTypes.string.isRequired,
+ profile_image_90: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ }),
+ tags: PropTypes.arrayOf(PropTypes.string).isRequired,
+ },
});
ItemListItem.defaultProps = {
@@ -80,7 +66,6 @@ ItemListItem.defaultProps = {
};
ItemListItem.propTypes = {
- item: PropTypes.oneOfType([historyItemPropTypes, readingListItemPropTypes])
- .isRequired,
+ item: PropTypes.oneOfType([readingListItemPropTypes]).isRequired,
children: PropTypes.element,
};
diff --git a/app/javascript/src/components/ItemList/__tests__/ItemListItem.test.jsx b/app/javascript/src/components/ItemList/__tests__/ItemListItem.test.jsx
index f3f090700..c194ece8e 100644
--- a/app/javascript/src/components/ItemList/__tests__/ItemListItem.test.jsx
+++ b/app/javascript/src/components/ItemList/__tests__/ItemListItem.test.jsx
@@ -3,55 +3,61 @@ import render from 'preact-render-to-json';
import { shallow } from 'preact-render-spy';
import { ItemListItem } from '../ItemListItem';
-const historyItem = {
- article_path: '/article',
- article_title: 'Title',
- article_user: {
- username: 'bob',
- profile_image_90: 'https://dummyimage.com/90x90',
- name: 'Bob',
- },
- article_reading_time: 1,
- readable_visited_at: 'Jun 29',
- article_tags: ['discuss'],
-};
-
const item = {
- searchable_reactable_path: '/article',
- searchable_reactable_title: 'Title',
- reactable_user: {
- username: 'bob',
- profile_image_90: 'https://dummyimage.com/90x90',
- name: 'Bob',
+ reactable: {
+ path: '/article',
+ title: 'Title',
+ published_date_string: 'Jun 29',
+ reading_time: 1,
+ user: {
+ username: 'bob',
+ profile_image_90: 'https://dummyimage.com/90x90',
+ name: 'Bob',
+ },
+ tags: [{ name: 'discuss' }],
},
- reading_time: 1,
- reactable_published_date: 'Jun 29',
- reactable_tags: ['discuss'],
};
describe('
', () => {
- it('renders properly with a history item', () => {
- const tree = render(
);
- expect(tree).toMatchSnapshot();
- });
-
it('renders properly with a readinglist item', () => {
const tree = render(
);
expect(tree).toMatchSnapshot();
});
it('renders with readingtime of 1 min if reading time is less than 1 min.', () => {
- const wrapper = shallow(
);
+ item.reactable.reading_time = 0.5;
+ const wrapper = shallow(
);
expect(wrapper.find('.item-user').text()).toContain('1 min read');
});
it('renders with readingtime of 1 min if reading time is null.', () => {
- const wrapper = shallow(
);
+ item.reactable.reading_time = null;
+ const wrapper = shallow(
);
expect(wrapper.find('.item-user').text()).toContain('1 min read');
});
+ it('renders correct readingtime.', () => {
+ item.reactable.reading_time = 10;
+ const wrapper = shallow(
);
+ expect(wrapper.find('.item-user').text()).toContain('10 min read');
+ });
+
it('renders without any tags if the tags array is empty.', () => {
- const wrapper = shallow(
);
- expect(wrapper.find('.item-user').text()).toContain('1 min read');
+ item.reactable.tags = [];
+ const wrapper = shallow(
);
+ expect(wrapper.find('.item-tags').exists()).toEqual(false);
+ });
+
+ it('renders tags with links if present.', () => {
+ item.reactable.tags = [{ name: 'discuss' }];
+ const wrapper = shallow(
);
+ expect(wrapper.find('.item-tag')[0].attributes.href).toEqual('/t/discuss');
+ expect(wrapper.find('.item-tag').text()).toContain('discuss');
+ });
+
+ it('renders user information', () => {
+ const wrapper = shallow(
);
+ expect(wrapper.find('.item-user')[0].attributes.href).toEqual('/bob');
+ expect(wrapper.find('.item-user').text()).toContain('Bob');
});
});
diff --git a/app/javascript/src/components/ItemList/__tests__/__snapshots__/ItemListItem.test.jsx.snap b/app/javascript/src/components/ItemList/__tests__/__snapshots__/ItemListItem.test.jsx.snap
index 4c324d433..8595095ad 100644
--- a/app/javascript/src/components/ItemList/__tests__/__snapshots__/ItemListItem.test.jsx.snap
+++ b/app/javascript/src/components/ItemList/__tests__/__snapshots__/ItemListItem.test.jsx.snap
@@ -1,46 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`
renders properly with a history item 1`] = `
-
-`;
-
exports[`
renders properly with a readinglist item 1`] = `
val } } }
else
{ terms: { search_key => values } }
diff --git a/spec/services/search/reaction_spec.rb b/spec/services/search/reaction_spec.rb
index 05a407be8..0528d1c80 100644
--- a/spec/services/search/reaction_spec.rb
+++ b/spec/services/search/reaction_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Search::Reaction, type: :service do
it "filters by status" do
reaction1.update(status: "invalid")
index_documents([reaction1, reaction2])
- query_params[:status] = ["valid"]
+ query_params[:status] = %w[valid confirmed]
reaction_docs = described_class.search_documents(params: query_params)["reactions"]
expect(reaction_docs.count).to eq(1)