send recsys powered-by #6875
9 changed files with 72 additions and 29 deletions
3
flow-typed/search.js
vendored
3
flow-typed/search.js
vendored
|
@ -28,7 +28,7 @@ declare type SearchOptions = {
|
|||
|
||||
declare type SearchState = {
|
||||
options: SearchOptions,
|
||||
urisByQuery: {},
|
||||
resultsByQuery: {},
|
||||
hasReachedMaxResultsLength: {},
|
||||
searching: boolean,
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ declare type SearchSuccess = {
|
|||
from: number,
|
||||
size: number,
|
||||
uris: Array<string>,
|
||||
recsys: string,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ function ChannelContent(props: Props) {
|
|||
!showMature ? '&nsfw=false&size=50&from=0' : ''
|
||||
}`
|
||||
)
|
||||
.then((results) => {
|
||||
.then(({ body: results }) => {
|
||||
const urls = results.map(({ name, claimId }) => {
|
||||
return `lbry://${name}#${claimId}`;
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
makeSelectRecommendedClaimIds,
|
||||
makeSelectRecommendationClicks,
|
||||
} from 'redux/selectors/content';
|
||||
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
|
||||
|
||||
const VERSION = '0.0.1';
|
||||
|
||||
|
@ -36,7 +37,7 @@ function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
|
|||
claimId: claimId,
|
||||
pageLoadedAt: pageLoadedAt,
|
||||
pageExitedAt: pageExitedAt,
|
||||
recsysId: recsysId,
|
||||
recsysId: makeSelectRecommendedRecsysIdForClaimId(claimId)(state) || recsysId,
|
||||
recClaimIds: makeSelectRecommendedClaimIds(claimId)(state),
|
||||
recClickedVideoIdx: makeSelectRecommendationClicks(claimId)(state),
|
||||
events: events,
|
||||
|
|
|
@ -25,7 +25,7 @@ export default function useLighthouse(
|
|||
let isSubscribed = true;
|
||||
lighthouse
|
||||
.search(throttledQuery)
|
||||
.then((results) => {
|
||||
.then(({ body: results }) => {
|
||||
if (isSubscribed) {
|
||||
setResults(
|
||||
results.map((result) => `lbry://${result.name}#${result.claimId}`).filter((uri) => isURIValid(uri))
|
||||
|
|
|
@ -3,7 +3,7 @@ import { withRouter } from 'react-router';
|
|||
import { doSearch } from 'redux/actions/search';
|
||||
import {
|
||||
selectIsSearching,
|
||||
makeSelectSearchUris,
|
||||
makeSelectSearchUrisForQuery,
|
||||
selectSearchOptions,
|
||||
makeSelectHasReachedMaxResultsLength,
|
||||
} from 'redux/selectors/search';
|
||||
|
@ -28,7 +28,7 @@ const select = (state, props) => {
|
|||
};
|
||||
|
||||
const query = getSearchQueryString(urlQuery, searchOptions);
|
||||
const uris = makeSelectSearchUris(query)(state);
|
||||
const uris = makeSelectSearchUrisForQuery(query)(state);
|
||||
const hasReachedMaxResultsLength = makeSelectHasReachedMaxResultsLength(query)(state);
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
import { buildURI, doResolveUris, batchActions, isURIValid, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectSearchUris, selectSearchValue } from 'redux/selectors/search';
|
||||
import { makeSelectSearchUrisForQuery, selectSearchValue } from 'redux/selectors/search';
|
||||
import handleFetchResponse from 'util/handle-fetch';
|
||||
import { getSearchQueryString } from 'util/query-params';
|
||||
import { SIMPLE_SITE, SEARCH_SERVER_API } from 'config';
|
||||
|
@ -48,7 +48,7 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
const from = searchOptions.from;
|
||||
|
||||
// If we have already searched for something, we don't need to do anything
|
||||
const urisForQuery = makeSelectSearchUris(queryWithOptions)(state);
|
||||
const urisForQuery = makeSelectSearchUrisForQuery(queryWithOptions)(state);
|
||||
if (urisForQuery && !!urisForQuery.length) {
|
||||
if (!size || !from || from + size < urisForQuery.length) {
|
||||
return;
|
||||
|
@ -61,13 +61,14 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
|
||||
lighthouse
|
||||
.search(queryWithOptions)
|
||||
.then((data: Array<{ name: string, claimId: string }>) => {
|
||||
.then((data: { body: Array<{ name: string, claimId: string }>, poweredBy: string }) => {
|
||||
const { body: result, poweredBy } = data;
|
||||
const uris = [];
|
||||
const actions = [];
|
||||
|
||||
data.forEach((result) => {
|
||||
if (result) {
|
||||
const { name, claimId } = result;
|
||||
result.forEach((item) => {
|
||||
if (item) {
|
||||
const { name, claimId } = item;
|
||||
const urlObj: LbryUrlObj = {};
|
||||
|
||||
if (name.startsWith('@')) {
|
||||
|
@ -94,6 +95,7 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
from: from,
|
||||
size: size,
|
||||
uris,
|
||||
recsys: poweredBy,
|
||||
},
|
||||
});
|
||||
dispatch(batchActions(...actions));
|
||||
|
|
|
@ -17,7 +17,7 @@ const defaultState: SearchState = {
|
|||
[SEARCH_OPTIONS.MEDIA_IMAGE]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_IMAGE),
|
||||
[SEARCH_OPTIONS.MEDIA_APPLICATION]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_APPLICATION),
|
||||
},
|
||||
urisByQuery: {},
|
||||
resultsByQuery: {},
|
||||
hasReachedMaxResultsLength: {},
|
||||
searching: false,
|
||||
};
|
||||
|
@ -29,21 +29,23 @@ export default handleActions(
|
|||
searching: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => {
|
||||
const { query, uris, from, size } = action.data;
|
||||
const { query, uris, from, size, recsys } = action.data;
|
||||
const normalizedQuery = createNormalizedSearchKey(query);
|
||||
const urisForQuery = state.resultsByQuery[normalizedQuery] && state.resultsByQuery[normalizedQuery]['uris'];
|
||||
|
||||
let newUris = uris;
|
||||
if (from !== 0 && state.urisByQuery[normalizedQuery]) {
|
||||
newUris = Array.from(new Set(state.urisByQuery[normalizedQuery].concat(uris)));
|
||||
if (from !== 0 && urisForQuery) {
|
||||
newUris = Array.from(new Set(urisForQuery.concat(uris)));
|
||||
}
|
||||
|
||||
// The returned number of urls is less than the page size, so we're on the last page
|
||||
const noMoreResults = size && uris.length < size;
|
||||
|
||||
const results = { uris: newUris, recsys };
|
||||
return {
|
||||
...state,
|
||||
searching: false,
|
||||
urisByQuery: Object.assign({}, state.urisByQuery, { [normalizedQuery]: newUris }),
|
||||
resultsByQuery: Object.assign({}, state.resultsByQuery, { [normalizedQuery]: results }),
|
||||
hasReachedMaxResultsLength: Object.assign({}, state.hasReachedMaxResultsLength, {
|
||||
[normalizedQuery]: noMoreResults,
|
||||
}),
|
||||
|
|
|
@ -4,6 +4,7 @@ import { selectShowMatureContent } from 'redux/selectors/settings';
|
|||
import {
|
||||
parseURI,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimForClaimId,
|
||||
makeSelectClaimIsNsfw,
|
||||
buildURI,
|
||||
isClaimNsfw,
|
||||
|
@ -26,9 +27,9 @@ export const selectSearchOptions: (state: State) => SearchOptions = createSelect
|
|||
|
||||
export const selectIsSearching: (state: State) => boolean = createSelector(selectState, (state) => state.searching);
|
||||
|
||||
|
||||
export const selectSearchUrisByQuery: (state: State) => { [string]: Array<string> } = createSelector(
|
||||
export const selectSearchResultByQuery: (state: State) => { [string]: Array<string> } = createSelector(
|
||||
selectState,
|
||||
(state) => state.urisByQuery
|
||||
(state) => state.resultsByQuery
|
||||
);
|
||||
|
||||
export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = createSelector(
|
||||
|
@ -36,15 +37,15 @@ export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Ar
|
|||
(state) => state.hasReachedMaxResultsLength
|
||||
);
|
||||
|
||||
export const makeSelectSearchUris = (query: string): ((state: State) => Array<string>) =>
|
||||
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
|
||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||
createSelector(selectSearchUrisByQuery, (byQuery) => {
|
||||
createSelector(selectSearchResultByQuery, (byQuery) => {
|
||||
if (query) {
|
||||
query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||
const normalizedQuery = createNormalizedSearchKey(query);
|
||||
return byQuery[normalizedQuery];
|
||||
return byQuery[normalizedQuery] && byQuery[normalizedQuery]['uris'];
|
||||
}
|
||||
return byQuery[query];
|
||||
return byQuery[query] && byQuery[query]['uris'];
|
||||
});
|
||||
|
||||
export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: State) => boolean) =>
|
||||
|
@ -60,7 +61,7 @@ export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: St
|
|||
export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectSearchUrisByQuery,
|
||||
selectSearchResultByQuery,
|
||||
makeSelectClaimIsNsfw(uri),
|
||||
(claim, searchUrisByQuery, isMature) => {
|
||||
let recommendedContent;
|
||||
|
@ -84,16 +85,47 @@ export const makeSelectRecommendedContentForUri = (uri: string) =>
|
|||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
||||
|
||||
let searchUris = searchUrisByQuery[normalizedSearchQuery];
|
||||
if (searchUris) {
|
||||
searchUris = searchUris.filter((searchUri) => searchUri !== currentUri);
|
||||
recommendedContent = searchUris;
|
||||
let searchResult = searchUrisByQuery[normalizedSearchQuery];
|
||||
if (searchResult) {
|
||||
recommendedContent = searchResult['uris'].filter((searchUri) => searchUri !== currentUri);
|
||||
}
|
||||
}
|
||||
return recommendedContent;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) =>
|
||||
createSelector(makeSelectClaimForClaimId(claimId), selectSearchResultByQuery, (claim, searchUrisByQuery) => {
|
||||
// TODO: DRY this out.
|
||||
let poweredBy;
|
||||
if (claim) {
|
||||
const isMature = isClaimNsfw(claim);
|
||||
const { title } = claim.value;
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: {
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
} = { related_to: claim.claim_id, isBackgroundSearch: true };
|
||||
|
||||
options['nsfw'] = isMature;
|
||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
||||
|
||||
let searchResult = searchUrisByQuery[normalizedSearchQuery];
|
||||
if (searchResult) {
|
||||
poweredBy = searchResult.recsys;
|
||||
} else {
|
||||
return normalizedSearchQuery;
|
||||
}
|
||||
}
|
||||
return poweredBy;
|
||||
});
|
||||
|
||||
export const makeSelectWinningUriForQuery = (query: string) => {
|
||||
const uriFromQuery = `lbry://${query}`;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
// @flow
|
||||
export default function handleFetchResponse(response: Response): Promise<any> {
|
||||
return response.status === 200 ? Promise.resolve(response.json()) : Promise.reject(new Error(response.statusText));
|
||||
const headers = response.headers;
|
||||
const poweredBy = headers.get('x-powered-by');
|
||||
|
||||
return response.status === 200
|
||||
? response.json().then((body) => ({ body, poweredBy }))
|
||||
: Promise.reject(new Error(response.statusText));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue
Maybe a rename would be good to reflect the actual returned object.