mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(app): find starting refs by change request descriptions (#6558)
Preserve returned change request bodies as search-only option text so client filtering keeps description matches. Keep title and ID matches ranked ahead of secondary text. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -160,3 +160,16 @@ describe("combobox above-search ordering", () => {
|
||||
expect(getComboboxFallbackIndex(ordered.length, "below-search")).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("search-only option text", () => {
|
||||
it("finds hidden search text while keeping title matches first", () => {
|
||||
const bodyMatch = { id: "pr:7", label: "Fix parsing", searchText: "Release notes for 2024" };
|
||||
const titleMatch = { id: "pr:8", label: "2024 release" };
|
||||
expect(filterAndRankComboboxOptions([bodyMatch, titleMatch], "2024")).toEqual([
|
||||
titleMatch,
|
||||
bodyMatch,
|
||||
]);
|
||||
expect(filterAndRankComboboxOptions([bodyMatch], "unrelated")).toEqual([]);
|
||||
expect(filterAndRankComboboxOptions([bodyMatch], "parsing 2024")).toEqual([bodyMatch]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,18 +10,22 @@ export interface ComboboxOptionModel {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
/** Additional text to match without displaying it in the option row. */
|
||||
searchText?: string;
|
||||
kind?: ComboboxOptionKind;
|
||||
}
|
||||
|
||||
const DESCRIPTION_FALLBACK_TIER = 99;
|
||||
const SECONDARY_TEXT_FALLBACK_TIER = 99;
|
||||
|
||||
function scoreOption(opt: ComboboxOptionModel, search: string): MatchScore | null {
|
||||
const best = scoreTextFields(search, [opt.label, opt.id]);
|
||||
if (best) return best;
|
||||
if (!opt.description) return null;
|
||||
const descriptionScore = scoreTextFields(search, [opt.description]);
|
||||
if (!descriptionScore) return null;
|
||||
return { ...descriptionScore, tier: descriptionScore.tier + DESCRIPTION_FALLBACK_TIER };
|
||||
const secondaryFields = opt.searchText
|
||||
? [opt.label, opt.id, opt.description ?? "", opt.searchText]
|
||||
: [opt.description ?? ""];
|
||||
const secondaryScore = scoreTextFields(search, secondaryFields);
|
||||
if (!secondaryScore) return null;
|
||||
return { ...secondaryScore, tier: secondaryScore.tier + SECONDARY_TEXT_FALLBACK_TIER };
|
||||
}
|
||||
|
||||
export interface BuildVisibleComboboxOptionsInput {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildVisibleComboboxOptions } from "@/components/ui/combobox-options";
|
||||
import type { ForgeSearchItem } from "@getpaseo/protocol/messages";
|
||||
import {
|
||||
type BranchPickerDetail,
|
||||
@@ -340,3 +341,48 @@ describe("defaultBasePickerItem", () => {
|
||||
expect(defaultBasePickerItem({ currentBranch: null })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("starting-ref search", () => {
|
||||
it.each(["gitlab", "github"] as const)(
|
||||
"keeps a %s change request returned for a body-only match",
|
||||
(forge) => {
|
||||
const item: ForgeSearchItem = {
|
||||
...prItem,
|
||||
forge,
|
||||
number: 3896,
|
||||
title: "fix(config): send environment credentials only to trusted hosts",
|
||||
body: "This mirrors the fix shipped for CVE-2024-53858.",
|
||||
headRefName: "8545-duo-test",
|
||||
};
|
||||
const { options } = buildPickerOptionData({
|
||||
branchDetails: [],
|
||||
prItems: [item],
|
||||
baseItem: null,
|
||||
});
|
||||
const visible = buildVisibleComboboxOptions({
|
||||
options,
|
||||
searchQuery: "2024",
|
||||
searchable: true,
|
||||
allowCustomValue: false,
|
||||
customValuePrefix: "Use",
|
||||
});
|
||||
expect(visible.map((option) => option.label)).toEqual([
|
||||
`${forge === "gitlab" ? "!" : "#"}3896 fix(config): send environment credentials only to trusted hosts`,
|
||||
]);
|
||||
const selected = buildPickerOptionData({
|
||||
branchDetails: [],
|
||||
prItems: [],
|
||||
baseItem: { kind: "github-pr", item },
|
||||
});
|
||||
expect(
|
||||
buildVisibleComboboxOptions({
|
||||
options: selected.options,
|
||||
searchQuery: "2024",
|
||||
searchable: true,
|
||||
allowCustomValue: false,
|
||||
customValuePrefix: "Use",
|
||||
}),
|
||||
).toEqual(visible);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -264,7 +264,10 @@ export function buildPickerOptionData(input: {
|
||||
itemById.set(id, { kind: "github-pr", item: pr });
|
||||
const updatedAtMs = pr.updatedAt ? Date.parse(pr.updatedAt) : 0;
|
||||
const timestamp = Number.isNaN(updatedAtMs) ? 0 : Math.floor(updatedAtMs / 1000);
|
||||
timedOptions.push({ option: { id, label: formatPrLabel(pr) }, timestamp });
|
||||
timedOptions.push({
|
||||
option: { id, label: formatPrLabel(pr), searchText: pr.body ?? undefined },
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
const baseItem = input.baseItem;
|
||||
@@ -279,7 +282,13 @@ export function buildPickerOptionData(input: {
|
||||
const labeledItem = disambiguate(baseItem, timedOptions);
|
||||
itemById.set(selectedOptionId, labeledItem);
|
||||
timedOptions.push({
|
||||
option: { id: selectedOptionId, label: pickerItemLabel(labeledItem) },
|
||||
option: {
|
||||
id: selectedOptionId,
|
||||
label: pickerItemLabel(labeledItem),
|
||||
...(labeledItem.kind === "github-pr"
|
||||
? { searchText: labeledItem.item.body ?? undefined }
|
||||
: {}),
|
||||
},
|
||||
// The base sorts first: it is what a workspace is created from unless you pick something.
|
||||
timestamp: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user