Recsys: capture and use x-uuid
from search results (#1727)
* Recsys/FYP: add documentation. * Recsys: capture and use `x-uuid` from search results Ticket: 1717
This commit is contained in:
parent
63a2430a7c
commit
486a557d75
7 changed files with 60 additions and 42 deletions
|
@ -1,8 +1,7 @@
|
|||
// @flow
|
||||
import { RECSYS_ENDPOINT } from 'config';
|
||||
import { selectUser } from 'redux/selectors/user';
|
||||
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
|
||||
import { v4 as Uuidv4 } from 'uuid';
|
||||
import { selectRecommendedMetaForClaimId } from 'redux/selectors/search';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import { getAuthToken } from 'util/saved-passwords';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
@ -16,7 +15,7 @@ import { selectIsSubscribedForClaimId } from 'redux/selectors/subscriptions';
|
|||
import { history } from 'ui/store';
|
||||
|
||||
const recsysEndpoint = RECSYS_ENDPOINT;
|
||||
const recsysId = 'lighthouse-v0';
|
||||
const DEFAULT_RECSYS_ID = 'lighthouse-v0';
|
||||
|
||||
const getClaimIdsFromUris = (uris) => {
|
||||
return uris
|
||||
|
@ -81,15 +80,23 @@ const recsys: Recsys = {
|
|||
* Page was loaded. Get or Create entry and populate it with default data,
|
||||
* plus recommended content, recsysId, etc.
|
||||
* Called from recommendedContent component
|
||||
*
|
||||
* @param claimId The ID of the content the recommendations are for.
|
||||
* @param uris The recommended uris for `claimId`.
|
||||
* @param uuid Specific uuid to use (e.g. for FYP); uses the recommendation's
|
||||
* uuid otherwise.
|
||||
*/
|
||||
onRecsLoaded: function (claimId, uris, uuid = '') {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
const recommendedMeta = selectRecommendedMetaForClaimId(state, claimId);
|
||||
|
||||
if (!recsys.entries[claimId]) {
|
||||
recsys.createRecsysEntry(claimId, null, uuid);
|
||||
recsys.createRecsysEntry(claimId, null, uuid || recommendedMeta.uuid);
|
||||
}
|
||||
|
||||
const claimIds = getClaimIdsFromUris(uris);
|
||||
recsys.entries[claimId]['recsysId'] = makeSelectRecommendedRecsysIdForClaimId(claimId)(state) || recsysId;
|
||||
recsys.entries[claimId]['recsysId'] = recommendedMeta.poweredBy || DEFAULT_RECSYS_ID;
|
||||
recsys.entries[claimId]['pageLoadedAt'] = Date.now();
|
||||
|
||||
// It is possible that `claimIds` include `null | undefined` entries
|
||||
|
@ -107,18 +114,20 @@ const recsys: Recsys = {
|
|||
* Creates an Entry with optional parentUuid
|
||||
* @param: claimId: string
|
||||
* @param: parentUuid: string (optional)
|
||||
* @param: uuid: string Specific uuid to use.
|
||||
* @param uuid Specific uuid to use (e.g. for FYP); uses the recommendation's
|
||||
* uuid otherwise.
|
||||
*/
|
||||
createRecsysEntry: function (claimId, parentUuid, uuid = '') {
|
||||
if (window && window.store && claimId) {
|
||||
const state = window.store.getState();
|
||||
const recommendedMeta = selectRecommendedMetaForClaimId(state, claimId);
|
||||
const user = selectUser(state);
|
||||
const userId = user ? user.id : null;
|
||||
|
||||
// Make a stub entry that will be filled out on page load
|
||||
// $FlowIgnore: not everything is defined since this is a stub
|
||||
recsys.entries[claimId] = {
|
||||
uuid: uuid || Uuidv4(),
|
||||
uuid: uuid || recommendedMeta.uuid,
|
||||
claimId: claimId,
|
||||
recClickedVideoIdx: [],
|
||||
pageLoadedAt: Date.now(),
|
||||
|
@ -154,6 +163,7 @@ const recsys: Recsys = {
|
|||
IS_WEB || (window && window.store && selectDaemonSettings(window.store.getState()).share_usage_data);
|
||||
|
||||
if (recsys.entries[claimId] && shareTelemetry) {
|
||||
// Exclude `events` in the submission https://github.com/OdyseeTeam/odysee-frontend/issues/1317
|
||||
const { events, ...entryData } = recsys.entries[claimId];
|
||||
const data = JSON.stringify(entryData);
|
||||
|
||||
|
|
11
flow-typed/search.js
vendored
11
flow-typed/search.js
vendored
|
@ -28,7 +28,7 @@ declare type SearchOptions = {
|
|||
|
||||
declare type SearchState = {
|
||||
options: SearchOptions,
|
||||
resultsByQuery: {},
|
||||
resultsByQuery: { [string]: { uris: Array<string>, recsys: string, uuid: string } },
|
||||
results: Array<string>,
|
||||
hasReachedMaxResultsLength: {},
|
||||
searching: boolean,
|
||||
|
@ -36,6 +36,12 @@ declare type SearchState = {
|
|||
personalRecommendations: { gid: string, uris: Array<string>, fetched: boolean },
|
||||
};
|
||||
|
||||
declare type SearchResults = {
|
||||
body: Array<{ name: string, claimId: string}>,
|
||||
poweredBy: string,
|
||||
uuid: string,
|
||||
};
|
||||
|
||||
declare type SearchSuccess = {
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
|
@ -43,7 +49,8 @@ declare type SearchSuccess = {
|
|||
from: number,
|
||||
size: number,
|
||||
uris: Array<string>,
|
||||
recsys: string,
|
||||
poweredBy: string,
|
||||
uuid: string,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
|||
// e.g. never in a floating popup. With that, we can grab the FYP ID from
|
||||
// the search param directly. Otherwise, the parent component would need to
|
||||
// pass it.
|
||||
// @see https://www.notion.so/FYP-Design-Notes-727782dde2cb485290c530ae96a34285
|
||||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const fypId = urlParams.get(FYP_ID);
|
||||
|
|
|
@ -171,11 +171,12 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
const cmd = isSearchingRecommendations ? lighthouse.searchRecommendations : lighthouse.search;
|
||||
|
||||
cmd(queryWithOptions)
|
||||
.then((data: { body: Array<{ name: string, claimId: string }>, poweredBy: string }) => {
|
||||
const { body: result, poweredBy } = data;
|
||||
.then((data: SearchResults) => {
|
||||
const { body: result, poweredBy, uuid } = data;
|
||||
const uris = processLighthouseResults(result);
|
||||
|
||||
if (isSearchingRecommendations) {
|
||||
// Temporarily resolve using `claim_search` until the SDK bug is fixed.
|
||||
const claimIds = result.map((x) => x.claimId);
|
||||
dispatch(doResolveClaimIds(claimIds)).finally(() => {
|
||||
dispatch({
|
||||
|
@ -185,7 +186,8 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
from: from,
|
||||
size: size,
|
||||
uris,
|
||||
recsys: poweredBy,
|
||||
poweredBy,
|
||||
uuid,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -201,7 +203,8 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
|||
from: from,
|
||||
size: size,
|
||||
uris,
|
||||
recsys: poweredBy,
|
||||
poweredBy,
|
||||
uuid,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ export default handleActions(
|
|||
searching: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => {
|
||||
const { query, uris, from, size, recsys } = action.data;
|
||||
const { query, uris, from, size, poweredBy: recsys, uuid } = action.data;
|
||||
const normalizedQuery = createNormalizedSearchKey(query);
|
||||
const urisForQuery = state.resultsByQuery[normalizedQuery] && state.resultsByQuery[normalizedQuery]['uris'];
|
||||
|
||||
|
@ -44,7 +44,7 @@ export default handleActions(
|
|||
// 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 };
|
||||
const results = { uris: newUris, recsys, uuid };
|
||||
return {
|
||||
...state,
|
||||
searching: false,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import {
|
||||
selectClaimsByUri,
|
||||
selectClaimForClaimId,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimForClaimId,
|
||||
selectClaimIsNsfwForUri,
|
||||
|
@ -25,8 +26,7 @@ export const selectState = (state: State): SearchState => state.search;
|
|||
export const selectSearchValue: (state: State) => string = (state) => selectState(state).searchQuery;
|
||||
export const selectSearchOptions: (state: State) => SearchOptions = (state) => selectState(state).options;
|
||||
export const selectIsSearching: (state: State) => boolean = (state) => selectState(state).searching;
|
||||
export const selectSearchResultByQuery: (state: State) => { [string]: Array<string> } = (state) =>
|
||||
selectState(state).resultsByQuery;
|
||||
export const selectSearchResultByQuery = (state: State) => selectState(state).resultsByQuery;
|
||||
export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = (state) =>
|
||||
selectState(state).hasReachedMaxResultsLength;
|
||||
export const selectMentionSearchResults: (state: State) => Array<string> = (state) => selectState(state).results;
|
||||
|
@ -142,34 +142,30 @@ export const selectRecommendedContentForUri = createCachedSelector(
|
|||
}
|
||||
)((state, uri) => String(uri));
|
||||
|
||||
export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForClaimId(claimId),
|
||||
selectShowMatureContent,
|
||||
selectSearchResultByQuery,
|
||||
(claim, matureEnabled, searchUrisByQuery) => {
|
||||
// TODO: DRY this out.
|
||||
let poweredBy;
|
||||
if (claim && claimId) {
|
||||
const isMature = isClaimNsfw(claim);
|
||||
const { title } = claim.value;
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
export const selectRecommendedMetaForClaimId = createCachedSelector(
|
||||
selectClaimForClaimId,
|
||||
selectShowMatureContent,
|
||||
selectSearchResultByQuery,
|
||||
(claim, matureEnabled, searchUrisByQuery) => {
|
||||
if (claim && claim?.value?.title && claim.claim_id) {
|
||||
const isMature = isClaimNsfw(claim);
|
||||
const title = claim.value.title;
|
||||
|
||||
const options = getRecommendationSearchOptions(matureEnabled, isMature, claimId);
|
||||
const normalizedSearchQuery = getRecommendationSearchKey(title, options);
|
||||
const options = getRecommendationSearchOptions(matureEnabled, isMature, claim.claim_id);
|
||||
const normalizedSearchQuery = getRecommendationSearchKey(title, options);
|
||||
|
||||
const searchResult = searchUrisByQuery[normalizedSearchQuery];
|
||||
if (searchResult) {
|
||||
poweredBy = searchResult.recsys;
|
||||
} else {
|
||||
return normalizedSearchQuery;
|
||||
}
|
||||
const searchResult = searchUrisByQuery[normalizedSearchQuery];
|
||||
if (searchResult) {
|
||||
return {
|
||||
poweredBy: searchResult.recsys,
|
||||
uuid: searchResult.uuid,
|
||||
};
|
||||
} else {
|
||||
return normalizedSearchQuery;
|
||||
}
|
||||
return poweredBy;
|
||||
}
|
||||
);
|
||||
}
|
||||
)((state, claimId) => String(claimId));
|
||||
|
||||
export const makeSelectWinningUriForQuery = (query: string) => {
|
||||
const uriFromQuery = `lbry://${query}`;
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
export default function handleFetchResponse(response: Response): Promise<any> {
|
||||
const headers = response.headers;
|
||||
const poweredBy = headers.get('x-powered-by');
|
||||
const uuid = headers.get('x-uuid');
|
||||
|
||||
return response.status === 200
|
||||
? response.json().then((body) => ({ body, poweredBy }))
|
||||
? response.json().then((body) => ({ body, poweredBy, uuid }))
|
||||
: Promise.reject(new Error(response.statusText));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue