Add score column to search_queries (#6253)
* Add score column to search_queries * Allow user to record search score
This commit is contained in:
13
server/migrations/20231206041706-search-query-score.js
Normal file
13
server/migrations/20231206041706-search-query-score.js
Normal file
@@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn("search_queries", "score", {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn("search_queries", "score");
|
||||
},
|
||||
};
|
||||
@@ -30,12 +30,27 @@ class SearchQuery extends Model {
|
||||
@CreatedAt
|
||||
createdAt: Date;
|
||||
|
||||
/**
|
||||
* Where the query originated.
|
||||
*/
|
||||
@Column(DataType.ENUM("slack", "app", "api"))
|
||||
source: string;
|
||||
|
||||
/**
|
||||
* The number of results returned for this query.
|
||||
*/
|
||||
@Column
|
||||
results: number;
|
||||
|
||||
/**
|
||||
* User score for the results for this query, -1 for negative, 1 for positive, null for neutral.
|
||||
*/
|
||||
@Column
|
||||
score: number;
|
||||
|
||||
/**
|
||||
* The query string, automatically truncated to 255 characters.
|
||||
*/
|
||||
@Column(DataType.STRING)
|
||||
set query(value: string) {
|
||||
this.setDataValue("query", value.substring(0, 255));
|
||||
|
||||
@@ -5,5 +5,6 @@ export default function presentSearchQuery(searchQuery: SearchQuery) {
|
||||
id: searchQuery.id,
|
||||
query: searchQuery.query,
|
||||
createdAt: searchQuery.createdAt,
|
||||
score: searchQuery.score,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,3 +12,12 @@ export const SearchesDeleteSchema = BaseSchema.extend({
|
||||
});
|
||||
|
||||
export type SearchesDeleteReq = z.infer<typeof SearchesDeleteSchema>;
|
||||
|
||||
export const SearchesUpdateSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
id: z.string().uuid(),
|
||||
score: z.number().min(-1).max(1),
|
||||
}),
|
||||
});
|
||||
|
||||
export type SearchesUpdateReq = z.infer<typeof SearchesUpdateSchema>;
|
||||
|
||||
@@ -44,6 +44,57 @@ describe("#searches.list", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#searches.update", () => {
|
||||
let user: User;
|
||||
let searchQuery: SearchQuery;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await buildUser();
|
||||
|
||||
searchQuery = await buildSearchQuery({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail with status 400 bad request when an invalid id is provided", async () => {
|
||||
const res = await server.post("/api/searches.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: "id",
|
||||
score: 1,
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
|
||||
it("should fail with status 400 bad request when an invalid score is provided", async () => {
|
||||
const res = await server.post("/api/searches.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: searchQuery.id,
|
||||
score: 2,
|
||||
},
|
||||
});
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
|
||||
it("should succeed with status 200 ok and successfully update the query", async () => {
|
||||
const res = await server.post("/api/searches.update", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
id: searchQuery.id,
|
||||
score: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.score).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#searches.delete", () => {
|
||||
let user: User;
|
||||
let searchQuery: SearchQuery;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Router from "koa-router";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { SearchQuery } from "@server/models";
|
||||
import { presentSearchQuery } from "@server/presenters";
|
||||
@@ -27,13 +28,41 @@ router.post("searches.list", auth(), pagination(), async (ctx: APIContext) => {
|
||||
};
|
||||
});
|
||||
|
||||
router.post(
|
||||
"searches.update",
|
||||
auth(),
|
||||
validate(T.SearchesUpdateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.SearchesUpdateReq>) => {
|
||||
const { id, score } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
|
||||
const search = await SearchQuery.findOne({
|
||||
where: {
|
||||
id,
|
||||
userId: user.id,
|
||||
},
|
||||
lock: transaction.LOCK.UPDATE,
|
||||
rejectOnEmpty: true,
|
||||
transaction,
|
||||
});
|
||||
|
||||
search.score = score;
|
||||
await search.save({ transaction });
|
||||
|
||||
ctx.body = {
|
||||
data: presentSearchQuery(search),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"searches.delete",
|
||||
auth(),
|
||||
validate(T.SearchesDeleteSchema),
|
||||
async (ctx: APIContext<T.SearchesDeleteReq>) => {
|
||||
const { id, query } = ctx.input.body;
|
||||
|
||||
const { user } = ctx.state.auth;
|
||||
|
||||
await SearchQuery.destroy({
|
||||
|
||||
Reference in New Issue
Block a user