Skip to content

Commit cc1d865

Browse files
authored
Feature/pav 85 mensagem filtro ativo vagas (#188)
- Ajusta a mensagem de filtro ativo acima da tabela de vagas. - Faz a mensagem refletir os filtros selecionados na busca. - Remove a indicação fixa de vagas remotas quando o usuário altera os filtros. - Mantém o texto consistente com os resultados exibidos na tabela.
2 parents 4c2a9e7 + edcdfcd commit cc1d865

13 files changed

Lines changed: 271 additions & 136 deletions

File tree

.cspell/custom-dictionary-workspace.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Abaixo
2+
aberta
23
abertas
34
abordagem
45
aborta
@@ -23,6 +24,7 @@ acesso
2324
acessos
2425
achar
2526
acidental
27+
aciona
2628
acompanhamento
2729
acompanhar
2830
acompanhe
@@ -106,6 +108,7 @@ anos
106108
Anos
107109
Anotações
108110
anteriores
111+
antiga
109112
antigas
110113
antigo
111114
antigos
@@ -239,6 +242,7 @@ cadastrar
239242
Cadastro
240243
cadvisor
241244
caixa
245+
calcula
242246
calculável
243247
Camada
244248
camadas
@@ -299,6 +303,7 @@ Ciclos
299303
Círculo
300304
cirúrgica
301305
clampeia
306+
classifica
302307
clicar
303308
clientes
304309
cmdmeta
@@ -363,6 +368,7 @@ configuradas
363368
configurado
364369
configurados
365370
Configurar
371+
confirmada
366372
conflita
367373
conflitam
368374
conflito
@@ -400,6 +406,7 @@ controla
400406
Controle
401407
Controles
402408
conversão
409+
conversar
403410
Converte
404411
convites
405412
cooperado
@@ -441,6 +448,7 @@ currículo
441448
curta
442449
customuser
443450
daqui
451+
datas
444452
datname
445453
davecgh
446454
debuglib
@@ -783,6 +791,7 @@ frança
783791
França
784792
freela
785793
frequentemente
794+
frequentes
786795
fulano
787796
Fulano
788797
funciona
@@ -829,6 +838,8 @@ habilitado
829838
habilitar
830839
hashedpassword
831840
híbrid
841+
híbridas
842+
Híbridas
832843
híbrido
833844
Hierarquia
834845
hintrc
@@ -1131,6 +1142,7 @@ movimente
11311142
muda
11321143
mudam
11331144
mude
1145+
mudou
11341146
múltiplas
11351147
múltiplos
11361148
mundiais
@@ -1200,6 +1212,7 @@ oportunidade
12001212
oportunidades
12011213
optar
12021214
ordem
1215+
ordenação
12031216
Orfaos
12041217
órfãos
12051218
organicamente
@@ -1261,6 +1274,7 @@ perfeitamente
12611274
perfis
12621275
performático
12631276
pergunta
1277+
perguntas
12641278
periodo
12651279
permanecem
12661280
permanente
@@ -1290,6 +1304,7 @@ plataformas
12901304
plugar
12911305
pmezard
12921306
pôde
1307+
Podemos
12931308
poderem
12941309
poderia
12951310
poderosa
@@ -1314,6 +1329,7 @@ possivelmente
13141329
Possuam
13151330
possui
13161331
possuir
1332+
poucas
13171333
pouco
13181334
prática
13191335
práticas
@@ -1335,6 +1351,7 @@ prefixo
13351351
Prefixo
13361352
prefs
13371353
preparar
1354+
presenciais
13381355
presencial
13391356
presente
13401357
presentes
@@ -1428,6 +1445,7 @@ reconcilia
14281445
reconecta
14291446
reconhece
14301447
recorre
1448+
recrutador
14311449
Recrutadores
14321450
recrutamento
14331451
recupera
@@ -1557,6 +1575,7 @@ seguro
15571575
seguros
15581576
sejam
15591577
SELECIONADA
1578+
selecionar
15601579
seletivos
15611580
seletor
15621581
Selo

backend/src/lib/cache.ts

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,35 @@ export type CacheJobIndexFilters = {
197197
country?: string;
198198
state?: string;
199199
city?: string;
200-
type?: string;
201-
model?: string;
200+
type?: string | string[];
201+
model?: string | string[];
202202
contract?: string;
203203
};
204204

205-
export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] {
206-
const entries: Array<[string, string | undefined]> = [
205+
function filterValues(value: string | string[] | undefined): string[] {
206+
const values = Array.isArray(value) ? value : [value];
207+
208+
return values
209+
.flatMap((item) => item?.split(",") ?? [])
210+
.map((item) => item.trim())
211+
.filter(Boolean);
212+
}
213+
214+
function cacheJobIndexKey(kind: string, value: string): string {
215+
const normalized =
216+
kind === "level"
217+
? normalizeLevelIndexValue(value)
218+
: normalizeIndexValue(value);
219+
220+
if (!normalized || normalized === "todos" || normalized === "all") {
221+
return "";
222+
}
223+
224+
return `scraper:jobs:${kind}:${normalized}`;
225+
}
226+
227+
function cacheJobIndexKeyGroups(filters: CacheJobIndexFilters): string[][] {
228+
const entries: Array<[string, string | string[] | undefined]> = [
207229
["level", filters.level],
208230
["location", filters.location],
209231
["continent", filters.continent],
@@ -215,52 +237,64 @@ export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] {
215237
];
216238

217239
return entries
218-
.map(([kind, value]) => {
219-
const normalized =
220-
kind === "level"
221-
? normalizeLevelIndexValue(value ?? "")
222-
: normalizeIndexValue(value ?? "");
223-
if (!normalized || normalized === "todos" || normalized === "all") {
224-
return "";
225-
}
226-
return `scraper:jobs:${kind}:${normalized}`;
227-
})
228-
.filter(Boolean);
240+
.map(([kind, value]) =>
241+
filterValues(value)
242+
.map((item) => cacheJobIndexKey(kind, item))
243+
.filter(Boolean),
244+
)
245+
.filter((group) => group.length > 0);
246+
}
247+
248+
export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] {
249+
return cacheJobIndexKeyGroups(filters).flatMap((group) => group);
229250
}
230251

231252
export async function cacheSearchJobIds(
232253
filters: CacheJobIndexFilters,
233254
): Promise<string[]> {
234255
const client = await getCache();
235256
const keywordKeys = keywordSearchKeys(filters.keywords ?? []);
236-
const filterKeys = cacheJobIndexKeys(filters);
257+
const tempKeys: string[] = [];
237258

238-
if (keywordKeys.length === 0 && filterKeys.length === 0) {
239-
return await client.sMembers("scraper:jobs:index");
240-
}
259+
const filterKeys = await Promise.all(
260+
cacheJobIndexKeyGroups(filters).map(async (group) => {
261+
if (group.length === 1) return group[0];
241262

242-
if (keywordKeys.length === 0) {
243-
if (filterKeys.length === 1) return await client.sMembers(filterKeys[0]);
244-
return (await client.sendCommand(["SINTER", ...filterKeys])) as string[];
245-
}
263+
const tempKey = `scraper:jobs:filter:${randomUUID()}`;
264+
tempKeys.push(tempKey);
265+
await client.sendCommand(["SUNIONSTORE", tempKey, ...group]);
266+
await client.expire(tempKey, 30);
267+
return tempKey;
268+
}),
269+
);
246270

247-
if (keywordKeys.length === 1) {
248-
const keys = [keywordKeys[0], ...filterKeys];
249-
if (keys.length === 1) return await client.sMembers(keys[0]);
250-
return (await client.sendCommand(["SINTER", ...keys])) as string[];
251-
}
271+
try {
272+
if (keywordKeys.length === 0 && filterKeys.length === 0) {
273+
return await client.sMembers("scraper:jobs:index");
274+
}
252275

253-
const tempKey = `scraper:jobs:search:${randomUUID()}`;
276+
if (keywordKeys.length === 0) {
277+
if (filterKeys.length === 1) return await client.sMembers(filterKeys[0]);
278+
return (await client.sendCommand(["SINTER", ...filterKeys])) as string[];
279+
}
280+
281+
if (keywordKeys.length === 1) {
282+
const keys = [keywordKeys[0], ...filterKeys];
283+
if (keys.length === 1) return await client.sMembers(keys[0]);
284+
return (await client.sendCommand(["SINTER", ...keys])) as string[];
285+
}
286+
287+
const tempKey = `scraper:jobs:search:${randomUUID()}`;
288+
tempKeys.push(tempKey);
254289

255-
try {
256290
await client.sendCommand(["SUNIONSTORE", tempKey, ...keywordKeys]);
257291
await client.expire(tempKey, 30);
258292

259293
const keys = [tempKey, ...filterKeys];
260294
if (keys.length === 1) return await client.sMembers(keys[0]);
261295
return (await client.sendCommand(["SINTER", ...keys])) as string[];
262296
} finally {
263-
await client.del(tempKey);
297+
await Promise.all(tempKeys.map((key) => client.del(key)));
264298
}
265299
}
266300

backend/src/routes/jobs.routes.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ function firstQueryValue(value: unknown): string {
3535
return typeof value === "string" ? value.trim() : "";
3636
}
3737

38+
function queryValues(value: unknown): string[] {
39+
const values = Array.isArray(value) ? value : [value];
40+
41+
return values
42+
.flatMap((item) => (typeof item === "string" ? item.split(",") : []))
43+
.map((item) => item.trim())
44+
.filter(Boolean);
45+
}
46+
3847
function normalizeComparable(value: string): string {
3948
return value
4049
.normalize("NFD")
@@ -209,16 +218,22 @@ function matchesLocationFilter(jobLocation: string, location: string): boolean {
209218
return normalizedLocation.includes(location);
210219
}
211220

221+
function getTypeFilters(query: Request["query"]): string[] {
222+
const rawTypes = queryValues(query.model).length > 0
223+
? queryValues(query.model)
224+
: queryValues(query.type);
225+
226+
return [...new Set(rawTypes.map(normalizeComparable).filter(Boolean))];
227+
}
228+
212229
function filterJobs(jobs: unknown[], query: Request["query"]): unknown[] {
213230
const level = normalizeLevelFilter(firstQueryValue(query.level));
214231
const location = normalizeComparable(
215232
firstQueryValue(query.country) || firstQueryValue(query.location),
216233
);
217-
const type = normalizeComparable(
218-
firstQueryValue(query.model) || firstQueryValue(query.type),
219-
);
234+
const types = getTypeFilters(query);
220235

221-
if (!level && !location && !type) return jobs;
236+
if (!level && !location && types.length === 0) return jobs;
222237

223238
return jobs.filter((job) => {
224239
const candidate = job as SearchJob;
@@ -227,7 +242,8 @@ function filterJobs(jobs: unknown[], query: Request["query"]): unknown[] {
227242

228243
const matchesLevel = !level || inferJobLevel(title) === level;
229244
const matchesLocation = matchesLocationFilter(jobLocation, location);
230-
const matchesType = !type || inferJobType(candidate) === type;
245+
const matchesType =
246+
types.length === 0 || types.includes(inferJobType(candidate));
231247

232248
return matchesLevel && matchesLocation && matchesType;
233249
});
@@ -387,8 +403,8 @@ jobsRoutes.get("/search", async (req: Request, res: Response) => {
387403
country: firstQueryValue(req.query.country),
388404
state: firstQueryValue(req.query.state),
389405
city: firstQueryValue(req.query.city),
390-
type: firstQueryValue(req.query.type),
391-
model: firstQueryValue(req.query.model),
406+
type: queryValues(req.query.type),
407+
model: queryValues(req.query.model),
392408
contract:
393409
firstQueryValue(req.query.contract) ||
394410
firstQueryValue(req.query.contractType) ||

backend/tests/unit/app.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ describe("jobsApiApp", () => {
321321
country: "",
322322
state: "",
323323
city: "",
324-
type: "Remoto",
325-
model: "",
324+
type: ["Remoto"],
325+
model: [],
326326
contract: "",
327327
});
328328
expect(mocks.cacheSearchKeywords).not.toHaveBeenCalled();

backend/tests/unit/libs/cache.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,33 @@ describe("Valkey Cache Lib", () => {
286286
);
287287
expect(result).toEqual(["job_1", "job_2"]);
288288
});
289+
290+
it("deve unir múltiplos modelos antes de intersectar com outros filtros", async () => {
291+
mockClientInstance.sendCommand
292+
.mockResolvedValueOnce(2)
293+
.mockResolvedValueOnce(["job_1", "job_2"]);
294+
295+
const result = await cacheSearchJobIds({
296+
model: ["Remoto", "Híbrido"],
297+
level: "Sênior",
298+
});
299+
300+
expect(mockClientInstance.sendCommand).toHaveBeenNthCalledWith(1, [
301+
"SUNIONSTORE",
302+
expect.stringMatching(/^scraper:jobs:filter:/),
303+
"scraper:jobs:model:remoto",
304+
"scraper:jobs:model:hibrido",
305+
]);
306+
expect(mockClientInstance.sendCommand).toHaveBeenNthCalledWith(2, [
307+
"SINTER",
308+
"scraper:jobs:level:senior",
309+
expect.stringMatching(/^scraper:jobs:filter:/),
310+
]);
311+
expect(mockClientInstance.del).toHaveBeenCalledWith(
312+
expect.stringMatching(/^scraper:jobs:filter:/),
313+
);
314+
expect(result).toEqual(["job_1", "job_2"]);
315+
});
289316
});
290317

291318
describe("cacheGetJobsByIds", () => {

0 commit comments

Comments
 (0)