feat(connection): unify Ignite version selection
This commit is contained in:
1 parent
8031a9e651
commit
e8e980c25c
11 files changed
+145
-8
No files matched your search
@@ -194,6 +194,18 @@ const NACOS_CONNECTION_PROFILES: ReadonlyArray<{ value: NacosConnectionProfile;
|
||||
{ value: "rnacos", title: "r-nacos" },
|
||||
];
|
||||
|
||||
type IgniteConnectionProfile = "ignite" | "ignite3";
|
||||
|
||||
const IGNITE_CONNECTION_PROFILES: ReadonlyArray<{ value: IgniteConnectionProfile; title: string }> = [
|
||||
{ value: "ignite", title: "Ignite 2.x" },
|
||||
{ value: "ignite3", title: "Ignite 3.x" },
|
||||
];
|
||||
|
||||
// The picker merges the Ignite 2.x/3.x cards into a single "Apache Ignite"
|
||||
// entry; the version is picked inside the connection form instead.
|
||||
const MERGED_PICKER_OPTION_FOR_TYPE: Record<string, string> = { ignite3: "ignite" };
|
||||
const PICKER_SEARCH_ALIASES: Record<string, string[]> = { ignite: ["ignite3", "ignite 3"] };
|
||||
|
||||
type LegacyTransportFields = {
|
||||
ssh_enabled?: boolean;
|
||||
ssh_host?: string;
|
||||
@@ -2883,6 +2895,16 @@ function onDbTypeChange(val: string) {
|
||||
resetVisibleSchemasState();
|
||||
}
|
||||
|
||||
function selectIgniteConnectionProfile(profile: IgniteConnectionProfile) {
|
||||
if (form.value.db_type === profile) return;
|
||||
const category = dbCategoryForOption(profile);
|
||||
if (category) selectedDbCategory.value = category;
|
||||
customDriverName.value = "";
|
||||
applyProfile(profile, true);
|
||||
resetTestState();
|
||||
resetVisibleSchemasState();
|
||||
}
|
||||
|
||||
function switchH2ConnectionMode(mode: H2ConnectionMode) {
|
||||
h2ConnectionMode.value = mode;
|
||||
if (mode === "file") {
|
||||
@@ -3177,17 +3199,19 @@ assertCompleteDatabaseCategories(
|
||||
dbCategoryDefinitions.map((category) => category.optionValues),
|
||||
);
|
||||
|
||||
const hiddenPickerOptionTypes = new Set(Object.keys(MERGED_PICKER_OPTION_FOR_TYPE));
|
||||
|
||||
const dbCategories = computed<DbCategory[]>(() => {
|
||||
return dbCategoryDefinitions.map((category) => ({
|
||||
key: category.key,
|
||||
title: t(category.titleKey),
|
||||
options: dbOptions.filter((option) => category.optionValues.includes(option.value)),
|
||||
options: dbOptions.filter((option) => category.optionValues.includes(option.value) && !hiddenPickerOptionTypes.has(option.value)),
|
||||
}));
|
||||
});
|
||||
|
||||
function matchesDbOption(option: DbOption, keyword: string, categoryTitle = "") {
|
||||
const profile = driverProfiles[option.value];
|
||||
return [option.label, option.value, profile?.label, profile?.type, categoryTitle].some((value) =>
|
||||
return [option.label, option.value, profile?.label, profile?.type, categoryTitle, ...(PICKER_SEARCH_ALIASES[option.value] ?? [])].some((value) =>
|
||||
String(value || "")
|
||||
.toLowerCase()
|
||||
.includes(keyword),
|
||||
@@ -3213,7 +3237,11 @@ const visibleDbCategories = computed<DbCategory[]>(() => {
|
||||
return filteredDbCategories.value.filter((category) => category.key === selectedDbCategory.value);
|
||||
});
|
||||
const hasDbPickerResults = computed(() => visibleDbCategories.value.some((category) => category.options.length > 0));
|
||||
const selectedDbOptionIsVisible = computed(() => visibleDbCategories.value.some((category) => category.options.some((option) => option.value === selectedType.value)));
|
||||
function isPickerOptionSelected(optionValue: string): boolean {
|
||||
return selectedType.value === optionValue || MERGED_PICKER_OPTION_FOR_TYPE[selectedType.value] === optionValue;
|
||||
}
|
||||
|
||||
const selectedDbOptionIsVisible = computed(() => visibleDbCategories.value.some((category) => category.options.some((option) => isPickerOptionSelected(option.value))));
|
||||
|
||||
function selectDbCategory(category: DbCategoryKey) {
|
||||
selectedDbCategory.value = category;
|
||||
@@ -3229,7 +3257,8 @@ function selectDbPickerView(view: DbPickerView) {
|
||||
}
|
||||
|
||||
function dbCategoryForOption(value: string): DbCategoryKey | undefined {
|
||||
return dbCategories.value.find((category) => category.options.some((option) => option.value === value))?.key;
|
||||
const pickerValue = MERGED_PICKER_OPTION_FOR_TYPE[value] ?? value;
|
||||
return dbCategories.value.find((category) => category.options.some((option) => option.value === pickerValue))?.key;
|
||||
}
|
||||
|
||||
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
|
||||
@@ -5881,8 +5910,8 @@ function openExternalUrl(url: string) {
|
||||
type="button"
|
||||
:title="opt.label"
|
||||
class="connection-db-picker-option group flex min-h-24 flex-col items-center justify-center gap-2 rounded-[4px] border bg-background/70 p-3 text-center transition hover:border-primary/40 hover:bg-muted/40 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
:class="selectedType === opt.value ? 'dbx-tile-selected shadow-sm' : 'border-border'"
|
||||
:aria-pressed="selectedType === opt.value"
|
||||
:class="isPickerOptionSelected(opt.value) ? 'dbx-tile-selected shadow-sm' : 'border-border'"
|
||||
:aria-pressed="isPickerOptionSelected(opt.value)"
|
||||
@click="onDbTypeChange(opt.value)"
|
||||
@dblclick="goToConnectionStep(opt.value)"
|
||||
>
|
||||
@@ -5901,8 +5930,8 @@ function openExternalUrl(url: string) {
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="connection-db-picker-option flex items-center gap-3 rounded-[4px] border bg-background px-3 py-2 text-left transition hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
:class="selectedType === opt.value ? 'dbx-tile-selected' : 'border-border'"
|
||||
:aria-pressed="selectedType === opt.value"
|
||||
:class="isPickerOptionSelected(opt.value) ? 'dbx-tile-selected' : 'border-border'"
|
||||
:aria-pressed="isPickerOptionSelected(opt.value)"
|
||||
@click="onDbTypeChange(opt.value)"
|
||||
@dblclick="goToConnectionStep(opt.value)"
|
||||
>
|
||||
@@ -7270,6 +7299,23 @@ function openExternalUrl(url: string) {
|
||||
|
||||
<!-- MySQL / PostgreSQL: host, port, user, password, database -->
|
||||
<template v-else>
|
||||
<div v-if="form.db_type === 'ignite' || form.db_type === 'ignite3'" class="grid grid-cols-4 items-start gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.igniteVersion") }}</Label>
|
||||
<div class="col-span-3 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
v-for="profile in IGNITE_CONNECTION_PROFILES"
|
||||
:key="profile.value"
|
||||
type="button"
|
||||
class="min-w-0 rounded-md border px-3 py-2.5 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
:class="form.db_type === profile.value ? 'border-primary bg-primary/5 shadow-sm' : 'border-border bg-background'"
|
||||
:aria-pressed="form.db_type === profile.value"
|
||||
@click="selectIgniteConnectionProfile(profile.value)"
|
||||
>
|
||||
<span class="block truncate text-sm font-medium">{{ profile.title }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="form.db_type === 'elasticsearch'" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.mode") }}</Label>
|
||||
<div class="col-span-3 grid h-8 grid-cols-2 overflow-hidden rounded-md border border-input bg-muted/30 p-0.5">
|
||||
|
||||
@@ -489,6 +489,7 @@ export default {
|
||||
oracleTnsAliasRequired: "Oracle TNS mode requires a network alias.",
|
||||
oracleTnsAdminRequired: "Oracle TNS mode requires the TNS_ADMIN directory containing tnsnames.ora.",
|
||||
oracleTnsTransportUnsupported: "Oracle TNS connections cannot use SSH, proxy, or HTTP tunnel layers. Remove the transport layer or use Service Name/SID mode.",
|
||||
igniteVersion: "Ignite version",
|
||||
elasticsearchDirectMode: "Direct",
|
||||
elasticsearchKibanaProxyMode: "Kibana Proxy",
|
||||
elasticsearchKibanaHost: "Kibana Host",
|
||||
|
||||
@@ -1016,6 +1016,7 @@ export default withEnglishFallback({
|
||||
mixed: "Mixto",
|
||||
},
|
||||
},
|
||||
igniteVersion: "Versión de Ignite",
|
||||
elasticsearchDirectMode: "Conexión directa",
|
||||
elasticsearchKibanaProxyMode: "proxy de Kibana",
|
||||
elasticsearchKibanaHost: "host de Kibana",
|
||||
|
||||
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
|
||||
mixed: "Misto",
|
||||
},
|
||||
},
|
||||
igniteVersion: "Versione di Ignite",
|
||||
elasticsearchDirectMode: "Connessione diretta",
|
||||
elasticsearchKibanaProxyMode: "Proxy Kibana",
|
||||
elasticsearchKibanaHost: "Host Kibana",
|
||||
|
||||
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
|
||||
mixed: "大文字小文字混在",
|
||||
},
|
||||
},
|
||||
igniteVersion: "Ignite バージョン",
|
||||
elasticsearchDirectMode: "直接接続",
|
||||
elasticsearchKibanaProxyMode: "Kibana プロキシ",
|
||||
elasticsearchKibanaHost: "Kibana ホスト",
|
||||
|
||||
@@ -483,6 +483,7 @@ export default withEnglishFallback({
|
||||
oracleTnsAliasRequired: "Oracle TNS 모드에는 네트워크 별칭이 필요합니다.",
|
||||
oracleTnsAdminRequired: "Oracle TNS 모드에는 tnsnames.ora가 포함된 TNS_ADMIN 디렉터리가 필요합니다.",
|
||||
oracleTnsTransportUnsupported: "Oracle TNS 연결은 SSH, 프록시 또는 HTTP 터널 계층을 사용할 수 없습니다. 전송 계층을 제거하거나 서비스 이름/SID 모드를 사용하세요.",
|
||||
igniteVersion: "Ignite 버전",
|
||||
elasticsearchDirectMode: "직접",
|
||||
elasticsearchKibanaProxyMode: "Kibana 프록시",
|
||||
elasticsearchKibanaHost: "Kibana 호스트",
|
||||
|
||||
@@ -1015,6 +1015,7 @@ export default withEnglishFallback({
|
||||
mixed: "Maiúsculas e minúsculas",
|
||||
},
|
||||
},
|
||||
igniteVersion: "Versão do Ignite",
|
||||
elasticsearchDirectMode: "Conexão Direta",
|
||||
elasticsearchKibanaProxyMode: "Proxy do Kibana",
|
||||
elasticsearchKibanaHost: "Host do Kibana",
|
||||
|
||||
@@ -414,6 +414,7 @@ export default withEnglishFallback({
|
||||
oracleTnsAliasRequired: "Oracle TNS 模式需要填写网络别名。",
|
||||
oracleTnsAdminRequired: "Oracle TNS 模式需要填写包含 tnsnames.ora 的 TNS_ADMIN 目录。",
|
||||
oracleTnsTransportUnsupported: "Oracle TNS 连接不能与 SSH、代理或 HTTP 隧道组合使用。请移除传输层,或改用服务名/SID 模式。",
|
||||
igniteVersion: "Ignite 版本",
|
||||
elasticsearchDirectMode: "直连",
|
||||
elasticsearchKibanaProxyMode: "Kibana 代理",
|
||||
elasticsearchKibanaHost: "Kibana 主机",
|
||||
|
||||
@@ -1014,6 +1014,7 @@ export default withEnglishFallback({
|
||||
mixed: "混合大小寫",
|
||||
},
|
||||
},
|
||||
igniteVersion: "Ignite 版本",
|
||||
elasticsearchDirectMode: "直接連線",
|
||||
elasticsearchKibanaProxyMode: "Kibana 代理",
|
||||
elasticsearchKibanaHost: "Kibana 主機",
|
||||
|
||||
+83
@@ -75,6 +75,63 @@ function profileSwitchHarness(selectedProfile: string, editing = false) {
|
||||
return { customDriverName, events, form, selectProfile, selectedType };
|
||||
}
|
||||
|
||||
function igniteProfileSwitchHarness(selectedProfile: "ignite" | "ignite3") {
|
||||
const form = {
|
||||
value: {
|
||||
db_type: selectedProfile,
|
||||
driver_profile: selectedProfile,
|
||||
driver_label: selectedProfile === "ignite" ? "Apache Ignite 2.x" : "Apache Ignite 3.x",
|
||||
host: "ignite.example.com",
|
||||
port: 10800,
|
||||
username: "draft-user",
|
||||
password: "draft-password",
|
||||
database: "draft-database",
|
||||
ssl: true,
|
||||
url_params: "sslMode=require",
|
||||
connection_string: "jdbc:ignite:thin://ignite.example.com:10800/draft-database",
|
||||
agent_java_options: ["-Xms256m", "-Xmx1g"],
|
||||
},
|
||||
};
|
||||
const selectedType = { value: selectedProfile };
|
||||
const selectedDbCategory = { value: "sql" };
|
||||
const customDriverName = { value: "" };
|
||||
const events: string[] = [];
|
||||
|
||||
function applyProfile(profile: "ignite" | "ignite3", preserveConnectionFields: boolean) {
|
||||
events.push(`apply:${profile}:${preserveConnectionFields}`);
|
||||
selectedType.value = profile;
|
||||
form.value.db_type = profile;
|
||||
form.value.driver_profile = profile;
|
||||
form.value.driver_label = profile === "ignite" ? "Apache Ignite 2.x" : "Apache Ignite 3.x";
|
||||
if (!preserveConnectionFields) {
|
||||
form.value.host = "127.0.0.1";
|
||||
form.value.username = "";
|
||||
form.value.password = "";
|
||||
form.value.database = "";
|
||||
form.value.ssl = false;
|
||||
form.value.url_params = "";
|
||||
form.value.connection_string = "";
|
||||
form.value.agent_java_options = [];
|
||||
}
|
||||
}
|
||||
|
||||
const javascript = ts.transpileModule(functionSource("selectIgniteConnectionProfile"), {
|
||||
compilerOptions: { module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2022 },
|
||||
}).outputText;
|
||||
const selectProfile = new Function("form", "selectedType", "dbCategoryForOption", "selectedDbCategory", "customDriverName", "applyProfile", "resetTestState", "resetVisibleSchemasState", `${javascript}\nreturn selectIgniteConnectionProfile;`)(
|
||||
form,
|
||||
selectedType,
|
||||
() => "sql",
|
||||
selectedDbCategory,
|
||||
customDriverName,
|
||||
applyProfile,
|
||||
() => events.push("reset-test"),
|
||||
() => events.push("reset-schemas"),
|
||||
) as (profile: "ignite" | "ignite3") => void;
|
||||
|
||||
return { events, form, selectProfile, selectedType };
|
||||
}
|
||||
|
||||
describe("ConnectionDialog database profile switching", () => {
|
||||
it.each([
|
||||
["mysql", ""],
|
||||
@@ -116,4 +173,30 @@ describe("ConnectionDialog database profile switching", () => {
|
||||
expect(harness.customDriverName.value).toBe("");
|
||||
expect(harness.events).toEqual(["apply:custom_mysql:true", "reset-test", "reset-schemas"]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["ignite", "ignite3"],
|
||||
["ignite3", "ignite"],
|
||||
] as const)("preserves an Ignite %s draft when switching to %s", (from, to) => {
|
||||
const harness = igniteProfileSwitchHarness(from);
|
||||
const connectionFields = {
|
||||
host: harness.form.value.host,
|
||||
port: harness.form.value.port,
|
||||
username: harness.form.value.username,
|
||||
password: harness.form.value.password,
|
||||
database: harness.form.value.database,
|
||||
ssl: harness.form.value.ssl,
|
||||
url_params: harness.form.value.url_params,
|
||||
connection_string: harness.form.value.connection_string,
|
||||
agent_java_options: [...harness.form.value.agent_java_options],
|
||||
};
|
||||
|
||||
harness.selectProfile(to);
|
||||
|
||||
expect(harness.form.value).toMatchObject(connectionFields);
|
||||
expect(harness.form.value.db_type).toBe(to);
|
||||
expect(harness.form.value.driver_profile).toBe(to);
|
||||
expect(harness.selectedType.value).toBe(to);
|
||||
expect(harness.events).toEqual([`apply:${to}:true`, "reset-test", "reset-schemas"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user