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

Track the type of saved searches #215

Merged
merged 5 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions api/db/apps-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { v4 as uuid } from 'uuid';
import { ApiApp } from '../shapes/app.js';
import { getAllApps, getAppById, insertApp } from './apps-queries.js';
import { pool, transaction } from './index.js';
import { closeDbPool, pool, transaction } from './index.js';

const appId = 'apps-queries-test-app';
const app: ApiApp = {
Expand All @@ -13,7 +13,7 @@ const app: ApiApp = {

beforeEach(() => pool.query({ text: 'delete from apps where id = $1', values: [appId] }));

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can create a new app', async () => {
await transaction(async (client) => {
Expand Down
4 changes: 2 additions & 2 deletions api/db/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pool, readTransaction, transaction } from './index.js';
import { closeDbPool, pool, readTransaction, transaction } from './index.js';

beforeEach(async () => {
try {
Expand All @@ -14,7 +14,7 @@ afterAll(async () => {
try {
await pool.query(`DROP TABLE transaction_test`);
} catch {}
await pool.end();
await closeDbPool();
});

describe('transaction', () => {
Expand Down
7 changes: 6 additions & 1 deletion api/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ pool.on('remove', () => {
metrics.increment('db.pool.remove.count');
});

setInterval(() => {
const metricsInterval = setInterval(() => {
metrics.gauge('db.pool.total', pool.totalCount);
metrics.gauge('db.pool.idle', pool.idleCount);
metrics.gauge('db.pool.waiting', pool.waitingCount);
}, 10000);

export async function closeDbPool() {
clearInterval(metricsInterval);
return pool.end();
}

/**
* A helper that gets a connection from the pool and then executes fn within a transaction.
*/
Expand Down
4 changes: 2 additions & 2 deletions api/db/item-annotations-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TagVariant } from '../shapes/item-annotations.js';
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import {
deleteAllItemAnnotations,
deleteItemAnnotation,
Expand All @@ -18,7 +18,7 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can insert tags where none exist before', async () => {
await transaction(async (client) => {
Expand Down
4 changes: 2 additions & 2 deletions api/db/item-hash-tags-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import {
deleteAllItemHashTags,
deleteItemHashTag,
Expand All @@ -15,7 +15,7 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can insert item hash tags where none exist before', async () => {
await transaction(async (client) => {
Expand Down
4 changes: 2 additions & 2 deletions api/db/loadout-share-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v4 as uuid } from 'uuid';
import { Loadout, LoadoutItem } from '../shapes/loadouts.js';
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import { addLoadoutShare, getLoadoutShare, recordAccess } from './loadout-share-queries.js';

const appId = 'settings-queries-test-app';
Expand All @@ -15,7 +15,7 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

const loadout: Loadout = {
id: uuid(),
Expand Down
4 changes: 2 additions & 2 deletions api/db/loadouts-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v4 as uuid } from 'uuid';
import { Loadout, LoadoutItem } from '../shapes/loadouts.js';
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import { deleteLoadout, getLoadoutsForProfile, updateLoadout } from './loadouts-queries.js';

const appId = 'settings-queries-test-app';
Expand All @@ -13,7 +13,7 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

const loadout: Loadout = {
id: uuid(),
Expand Down
62 changes: 47 additions & 15 deletions api/db/searches-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { pool, transaction } from './index.js';
import { SearchType } from '../shapes/search.js';
import { closeDbPool, transaction } from './index.js';
import {
deleteAllSearches,
deleteSearch,
Expand All @@ -18,11 +19,11 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can record a used search where none was recorded before', async () => {
await transaction(async (client) => {
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -35,8 +36,8 @@ it('can record a used search where none was recorded before', async () => {

it('can track search multiple times', async () => {
await transaction(async (client) => {
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -49,8 +50,8 @@ it('can track search multiple times', async () => {

it('can mark a search as favorite', async () => {
await transaction(async (client) => {
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true);
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);
await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, true);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -59,7 +60,7 @@ it('can mark a search as favorite', async () => {
expect(searches[0].saved).toBe(true);
expect(searches[0].usageCount).toBe(1);

await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', false);
await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, false);

const searches2 = await getSearchesForProfile(client, bungieMembershipId, 2);
expect(searches2[0].query).toBe('tag:junk');
Expand All @@ -71,7 +72,7 @@ it('can mark a search as favorite', async () => {
});
it('can mark a search as favorite even when it hasnt been used', async () => {
await transaction(async (client) => {
await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true);
await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, true);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -84,8 +85,8 @@ it('can mark a search as favorite even when it hasnt been used', async () => {

it('can get all searches across profiles', async () => {
await transaction(async (client) => {
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await updateUsedSearch(client, appId, bungieMembershipId, 1, 'is:tagged');
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);
await updateUsedSearch(client, appId, bungieMembershipId, 1, 'is:tagged', SearchType.Item);

const searches = await getSearchesForUser(client, bungieMembershipId);
expect(searches.length).toEqual(2);
Expand All @@ -97,7 +98,7 @@ it('can increment usage for one of the built-in searches', async () => {
const searches = await getSearchesForProfile(client, bungieMembershipId, 2);
const query = searches[searches.length - 1].query;

await updateUsedSearch(client, appId, bungieMembershipId, 2, query);
await updateUsedSearch(client, appId, bungieMembershipId, 2, query, SearchType.Item);

const searches2 = await getSearchesForProfile(client, bungieMembershipId, 2);
const search = searches2.find((s) => s.query === query);
Expand All @@ -108,8 +109,8 @@ it('can increment usage for one of the built-in searches', async () => {

it('can delete a search', async () => {
await transaction(async (client) => {
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk');
await deleteSearch(client, bungieMembershipId, 2, 'tag:junk');
await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item);
await deleteSearch(client, bungieMembershipId, 2, 'tag:junk', SearchType.Item);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -120,7 +121,17 @@ it('can delete a search', async () => {

it('can import a search', async () => {
await transaction(async (client) => {
await importSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true, 1598199188576, 5);
await importSearch(
client,
appId,
bungieMembershipId,
2,
'tag:junk',
true,
1598199188576,
5,
SearchType.Item,
);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
Expand All @@ -130,3 +141,24 @@ it('can import a search', async () => {
expect(searches[0].usageCount).toBe(5);
});
});

it('can record searches for loadouts', async () => {
await transaction(async (client) => {
await updateUsedSearch(
client,
appId,
bungieMembershipId,
2,
'subclass:void',
SearchType.Loadout,
);

const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter(
(s) => s.usageCount > 0,
);
expect(searches[0].query).toBe('subclass:void');
expect(searches[0].saved).toBe(false);
expect(searches[0].usageCount).toBe(1);
expect(searches[0].type).toBe(SearchType.Loadout);
});
});
30 changes: 19 additions & 11 deletions api/db/searches-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ClientBase, QueryResult } from 'pg';
import { metrics } from '../metrics/index.js';
import { ExportResponse } from '../shapes/export.js';
import { DestinyVersion } from '../shapes/general.js';
import { Search } from '../shapes/search.js';
import { Search, SearchType } from '../shapes/search.js';

/*
* These "canned searches" get sent to everyone as a "starter pack" of example searches that'll show up in the recent search dropdown and autocomplete.
Expand All @@ -17,6 +17,7 @@ const cannedSearchesForD2: Search[] = [
saved: false,
usageCount: 0,
lastUsage: 0,
type: SearchType.Item,
}));

const cannedSearchesForD1: Search[] = ['-is:equipped is:haslight is:incurrentchar'].map(
Expand All @@ -25,6 +26,7 @@ const cannedSearchesForD1: Search[] = ['-is:equipped is:haslight is:incurrentcha
saved: false,
usageCount: 0,
lastUsage: 0,
type: SearchType.Item,
}),
);
/*
Expand Down Expand Up @@ -91,6 +93,7 @@ function convertSearch(row: any): Search {
usageCount: row.usage_count,
saved: row.saved,
lastUsage: row.last_updated_at.getTime(),
type: row.search_type,
};
}

Expand All @@ -105,15 +108,16 @@ export async function updateUsedSearch(
bungieMembershipId: number,
destinyVersion: DestinyVersion,
query: string,
type: SearchType,
): Promise<QueryResult<any>> {
try {
const response = await client.query({
name: 'upsert_search',
text: `insert INTO searches (membership_id, destiny_version, query, created_by, last_updated_by)
values ($1, $2, $3, $4, $4)
text: `insert INTO searches (membership_id, destiny_version, query, search_type, created_by, last_updated_by)
values ($1, $2, $3, $5, $4, $4)
on conflict (membership_id, destiny_version, qhash)
do update set (usage_count, last_used, last_updated_at, last_updated_by) = (searches.usage_count + 1, current_timestamp, current_timestamp, $4)`,
values: [bungieMembershipId, destinyVersion, query, appId],
values: [bungieMembershipId, destinyVersion, query, appId, type],
});

if (response.rowCount < 1) {
Expand All @@ -137,6 +141,7 @@ export async function saveSearch(
bungieMembershipId: number,
destinyVersion: DestinyVersion,
query: string,
type: SearchType,
saved?: boolean,
): Promise<QueryResult<any>> {
try {
Expand All @@ -151,9 +156,9 @@ export async function saveSearch(
metrics.increment('db.searches.noRowUpdated.count', 1);
const insertSavedResponse = await client.query({
name: 'insert_search_fallback',
text: `insert INTO searches (membership_id, destiny_version, query, saved, created_by, last_updated_by)
values ($1, $2, $3, true, $4, $4)`,
values: [bungieMembershipId, destinyVersion, query, appId],
text: `insert INTO searches (membership_id, destiny_version, query, search_type, saved, created_by, last_updated_by)
values ($1, $2, $3, $5, true, $4, $4)`,
values: [bungieMembershipId, destinyVersion, query, appId, type],
});
return insertSavedResponse;
}
Expand All @@ -175,12 +180,13 @@ export async function importSearch(
saved: boolean,
lastUsage: number,
usageCount: number,
type: SearchType,
): Promise<QueryResult<any>> {
try {
const response = await client.query({
name: 'insert_search',
text: `insert INTO searches (membership_id, destiny_version, query, saved, usage_count, last_used, created_by, last_updated_by)
values ($1, $2, $3, $4, $5, $6, $7, $7)`,
text: `insert INTO searches (membership_id, destiny_version, query, saved, search_type, usage_count, last_used, created_by, last_updated_by)
values ($1, $2, $3, $4, $8, $5, $6, $7, $7)`,
values: [
bungieMembershipId,
destinyVersion,
Expand All @@ -189,6 +195,7 @@ values ($1, $2, $3, $4, $5, $6, $7, $7)`,
usageCount,
new Date(lastUsage),
appId,
type,
],
});

Expand All @@ -212,12 +219,13 @@ export async function deleteSearch(
bungieMembershipId: number,
destinyVersion: DestinyVersion,
query: string,
type: SearchType,
): Promise<QueryResult<any>> {
try {
return client.query({
name: 'delete_search',
text: `delete from searches where membership_id = $1 and destiny_version = $2 and qhash = decode(md5($3), 'hex') and query = $3`,
values: [bungieMembershipId, destinyVersion, query],
text: `delete from searches where membership_id = $1 and destiny_version = $2 and qhash = decode(md5($3), 'hex') and query = $3 and search_type = $4`,
values: [bungieMembershipId, destinyVersion, query, type],
});
} catch (e) {
throw new Error(e.name + ': ' + e.message);
Expand Down
4 changes: 2 additions & 2 deletions api/db/settings-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import { getSettings, setSetting } from './settings-queries.js';

const appId = 'settings-queries-test-app';
const bungieMembershipId = 4321;

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can insert settings where none exist before', async () => {
await transaction(async (client) => {
Expand Down
4 changes: 2 additions & 2 deletions api/db/triumphs-queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pool, transaction } from './index.js';
import { closeDbPool, transaction } from './index.js';
import {
deleteAllTrackedTriumphs,
getAllTrackedTriumphsForUser,
Expand All @@ -17,7 +17,7 @@ beforeEach(() =>
}),
);

afterAll(() => pool.end());
afterAll(() => closeDbPool());

it('can track a triumph where none was tracked before', async () => {
await transaction(async (client) => {
Expand Down
Loading
Loading