Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next-queries): add next query preview logic #611

Merged
merged 17 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/search-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './suggestion.model';
export * from './tagging.model';
export * from './user-info.model';
export * from './x-components-adapter.model';
export * from './preview-results.model';
13 changes: 13 additions & 0 deletions packages/search-types/src/preview-results.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Result } from './result/result.model';

/**
* Interface to type the query preview objects.
*/
export interface PreviewResults {
/** The searched query. */
query: string;
/** The results to preview the search request. */
items: Result[];
/** The number of results of the query. */
totalResults: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ export interface NextQueriesConfig {
* Loads the next queries with the last searched query.
*/
loadOnInit: boolean;
/**
* Number of results that will be requested to preview the next queries.
*/
maxPreviewItemsToRequest: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ export interface NextQueriesXEvents {
* Payload: The next query that has been selected by the user.
*/
UserSelectedANextQuery: NextQuery;
/**
* The component to show a next query preview has been mounted.
* Payload: The next query to preview.
*/
NextQueryPreviewMounted: string;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { HistoryQuery } from '@empathyco/x-types';
import { HistoryQuery, SearchRequest } from '@empathyco/x-types';
import { createLocalVue } from '@vue/test-utils';
import Vuex, { Store } from 'vuex';
import { createHistoryQueries, getNextQueriesStub } from '../../../../__stubs__';
import {
createHistoryQueries,
getNextQueriesStub,
getSearchResponseStub
} from '../../../../__stubs__';
import { getMockedAdapter, installNewXPlugin } from '../../../../__tests__/utils';
import { SafeStore } from '../../../../store/__tests__/utils';
import { nextQueriesXStoreModule } from '../module';
Expand All @@ -15,7 +19,12 @@ import { resetNextQueriesStateWith } from './utils';

describe('testing next queries module actions', () => {
const mockedNextQueries = getNextQueriesStub();
const adapter = getMockedAdapter({ nextQueries: { nextQueries: mockedNextQueries } });
const mockedSearchResponse = getSearchResponseStub();

const adapter = getMockedAdapter({
nextQueries: { nextQueries: mockedNextQueries },
search: mockedSearchResponse
});

const localVue = createLocalVue();
localVue.config.productionTip = false; // Silent production console messages.
Expand Down Expand Up @@ -112,6 +121,69 @@ describe('testing next queries module actions', () => {
});
});

describe('fetchNextQueryPreview', () => {
it('should build the search request adding rows and extraParams from state', async () => {
resetNextQueriesStateWith(store, {
config: {
maxPreviewItemsToRequest: 3
},
params: {
extraParam: 'extra param'
}
});
const query = 'honeyboo';
await store.dispatch('fetchNextQueryPreview', query);
const expectedRequest: SearchRequest = {
query,
rows: 3,
extraParams: {
extraParam: 'extra param'
}
};
expect(adapter.search).toHaveBeenCalledWith(expectedRequest, {
id: 'fetchNextQueryPreview-honeyboo'
});
});

it('should return the search response', async () => {
const results = await store.dispatch('fetchNextQueryPreview', 'honeyboo');
expect(results).toEqual(mockedSearchResponse);
});

it('should return `null` if the query is empty', async () => {
expect(await store.dispatch('fetchNextQueryPreview', '')).toBeNull();
});
});

describe('fetchAndSaveNextQueryPreview', () => {
it('should request and store preview results in the state', async () => {
const query = 'tshirt';

const promise = store.dispatch('fetchAndSaveNextQueryPreview', query);
await promise;

const expectedResults = {
totalResults: mockedSearchResponse.totalResults,
items: mockedSearchResponse.results,
query
};
const stateResults = store.state.resultsPreview;

expect(query in stateResults).toBeTruthy();
expect(stateResults[query]).toEqual(expectedResults);
});

it('should send multiple requests if the queries are different', async () => {
const firstRequest = store.dispatch('fetchAndSaveNextQueryPreview', 'milk');
const secondRequest = store.dispatch('fetchAndSaveNextQueryPreview', 'cookies');

await Promise.all([firstRequest, secondRequest]);

expect('milk' in store.state.resultsPreview).toBeTruthy();
expect('cookies' in store.state.resultsPreview).toBeTruthy();
});
});

describe('setQueryFromLastHistoryQuery', () => {
it('should set the query with the first query of history query list', async () => {
const historyQueries = createHistoryQueries('shoes', 'shirt');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextQueriesXStoreModule } from '../types';

// eslint-disable-next-line max-len
export const fetchAndSaveNextQueryPreview: NextQueriesXStoreModule['actions']['fetchAndSaveNextQueryPreview'] =
({ dispatch, commit }, query): Promise<void> => {
return dispatch('fetchNextQueryPreview', query)
.then(response => {
if (response) {
commit('setResultsPreview', {
[query]: {
query,
totalResults: response.totalResults,
items: response.results
}
});
}
})
.catch(error => {
// eslint-disable-next-line no-console
console.error(error);
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextQueriesXStoreModule } from '../types';
import { XPlugin } from '../../../../plugins/x-plugin';

/**
* Default implementation for the {@link NextQueriesActions.fetchNextQueryPreview}.
*
* @param state - The state of the store, used to retrieve the rows and the extraParams to be sent
* in the request.
* @param query - The next query to send in the request.
* @returns A Promise of a SearchResponse when it fetches the results, `null` if the request was
* not made.
*/
export const fetchNextQueryPreview: NextQueriesXStoreModule['actions']['fetchNextQueryPreview'] = (
{ state },
query
) => {
if (!query) {
return null;
}
Comment on lines +17 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The query is a required parameter for this action, so no checks should be needed. We can trust TypeScript. With this I believe we can simplify the return type and just set a SearchResponse

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the query is a required parameter, nothing avoids calling the function with an empty string, although that would not be a legit use of this function 🤔
Should I remove that comprobation anyway?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Abraham is right, and I would leave the empty string check. Specially thinking of reusing the wire with custom wiring config in setups. Better prevent an error request to API

return XPlugin.adapter.search(
{
query,
rows: state.config.maxPreviewItemsToRequest,
extraParams: state.params
},
{
id: `fetchNextQueryPreview-${query}`
}
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
import { fetchNextQueries } from './actions/fetch-next-queries.action';
import { setQueryFromLastHistoryQuery } from './actions/set-query-from-last-history-query.action';
import { setUrlParams } from './actions/set-url-params.action';
import { fetchNextQueryPreview } from './actions/fetch-next-query-preview.action';
import { fetchAndSaveNextQueryPreview } from './actions/fetch-and-save-next-query-preview.action';
import { nextQueries } from './getters/next-queries.getter';
import { request } from './getters/request.getter';
import { NextQueriesXStoreModule } from './types';
Expand All @@ -25,9 +27,11 @@ export const nextQueriesXStoreModule: NextQueriesXStoreModule = {
config: {
maxItemsToRequest: 20,
hideSessionQueries: true,
loadOnInit: true
loadOnInit: true,
maxPreviewItemsToRequest: 8
},
params: {}
params: {},
resultsPreview: {}
}),
getters: {
request,
Expand All @@ -44,13 +48,21 @@ export const nextQueriesXStoreModule: NextQueriesXStoreModule = {
setStatus,
setParams(state, params) {
state.params = params;
},
setResultsPreview(state, resultsPreview) {
state.resultsPreview = { ...state.resultsPreview, ...resultsPreview };
},
resetResultsPreview(state) {
state.resultsPreview = {};
}
},
actions: {
cancelFetchAndSaveNextQueries,
fetchAndSaveNextQueries,
fetchNextQueries,
setQueryFromLastHistoryQuery,
setUrlParams
setUrlParams,
fetchNextQueryPreview,
fetchAndSaveNextQueryPreview
}
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { HistoryQuery, NextQuery, NextQueriesRequest } from '@empathyco/x-types';
import {
HistoryQuery,
NextQuery,
NextQueriesRequest,
SearchResponse,
PreviewResults
} from '@empathyco/x-types';
import { Dictionary } from '@empathyco/x-utils';
import { XActionContext, XStoreModule } from '../../../store';
import { QueryMutations, QueryState } from '../../../store/utils/query.utils';
Expand All @@ -23,6 +29,8 @@ export interface NextQueriesState extends StatusState, QueryState {
config: NextQueriesConfig;
/** The extra params property of the state. */
params: Dictionary<unknown>;
/** Results of the next queries requests. */
resultsPreview: Dictionary<PreviewResults>;
}

/**
Expand Down Expand Up @@ -70,6 +78,19 @@ export interface NextQueriesMutations extends StatusMutations, QueryMutations {
* @param params - The new extra params.
*/
setParams(params: Dictionary<unknown>): void;

/**
* Adds a new entry to the result's dictionary.
*
* @param resultsPreview - Object containing the next query,
* the totalResults and the results to add.
*/
setResultsPreview(resultsPreview: Dictionary<PreviewResults>): void;

/**
* Resets the result's dictionary.
*/
resetResultsPreview(): void;
}

/**
Expand Down Expand Up @@ -102,6 +123,20 @@ export interface NextQueriesActions {
* @param urlParams - List of params from the url.
*/
setUrlParams(urlParams: UrlParams): void;
/**
* Requests the results to preview a next query,
* limited by {@link NextQueriesConfig.maxPreviewItemsToRequest}.
*
* @param query - The next query to retrieve the results.
ajperezbau marked this conversation as resolved.
Show resolved Hide resolved
* @returns A search response based on the next query.
*/
fetchNextQueryPreview(query: string): SearchResponse | null;
/**
* Requests the results to preview a next query and saves them in the state.
*
* @param query - The next query to retrieve the results.
*/
fetchAndSaveNextQueryPreview(query: string): void;
}

/**
Expand Down
35 changes: 34 additions & 1 deletion packages/x-components/src/x-modules/next-queries/wiring.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import {
namespacedWireCommit,
namespacedWireCommitWithoutPayload,
namespacedWireDispatch
} from '../../wiring/namespaced-wires.factory';
import { NamespacedWireCommit, NamespacedWireDispatch } from '../../wiring/namespaced-wiring.types';
import {
NamespacedWireCommit,
NamespacedWireCommitWithoutPayload,
NamespacedWireDispatch
} from '../../wiring/namespaced-wiring.types';
import { createWiring } from '../../wiring/wiring.utils';

/**
Expand All @@ -11,12 +16,20 @@ import { createWiring } from '../../wiring/wiring.utils';
* @internal
*/
const moduleName = 'nextQueries';

/**
* WireCommit for {@link NextQueriesXModule}.
*
* @internal
*/
const wireCommit: NamespacedWireCommit<typeof moduleName> = namespacedWireCommit(moduleName);

/**
* WireCommitWithoutPayload for {@link NextQueriesXModule}.
*/
const wireCommitWithoutPayload: NamespacedWireCommitWithoutPayload<typeof moduleName> =
namespacedWireCommitWithoutPayload(moduleName);

/**
* WireDispatch for {@link NextQueriesXModule}.
*
Expand Down Expand Up @@ -59,6 +72,20 @@ export const fetchAndSaveNextQueriesWire = wireDispatch('fetchAndSaveNextQueries
*/
export const setQueryFromLastHistoryQueryWire = wireDispatch('setQueryFromLastHistoryQuery');

/**
* Requests and store the next query preview results.
*
* @public
*/
export const fetchAndSaveNextQueryPreviewWire = wireDispatch('fetchAndSaveNextQueryPreview');

/**
* Resets the next query preview results.
*
* @public
*/
export const resetResultsPreviewWire = wireCommitWithoutPayload('resetResultsPreview');

/**
* Sets the next queries state `searchedQueries` with the list of history queries.
*
Expand All @@ -75,6 +102,9 @@ export const nextQueriesWiring = createWiring({
ParamsLoadedFromUrl: {
setUrlParams
},
NextQueriesChanged: {
resetResultsPreviewWire
},
UserAcceptedAQuery: {
setNextQueriesQuery
},
Expand All @@ -88,5 +118,8 @@ export const nextQueriesWiring = createWiring({
},
ExtraParamsChanged: {
setNextQueriesExtraParams
},
NextQueryPreviewMounted: {
fetchAndSaveNextQueryPreviewWire
}
});