feat: add slack connection fields to workspace settings export/import (#8287)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Petric
2026-03-09 17:39:03 -04:00
committed by GitHub
parent 6c5533bc60
commit 39e77ecd00
3 changed files with 67 additions and 2 deletions
+25 -1
View File
@@ -282,6 +282,12 @@ struct SimplifiedSettings {
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
operator_settings: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_team_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_command_script: Option<String>,
}
// V1 format: Legacy flat format for backward compatibility (matches main branch exactly)
@@ -316,6 +322,12 @@ struct SimplifiedSettingsLegacy {
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
operator_settings: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_team_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
slack_command_script: Option<String>,
}
// Internal struct for querying database
@@ -335,6 +347,9 @@ struct SettingsRow {
mute_critical_alerts: Option<bool>,
color: Option<String>,
operator_settings: Option<serde_json::Value>,
slack_team_id: Option<String>,
slack_name: Option<String>,
slack_command_script: Option<String>,
}
pub(crate) async fn tarball_workspace(
@@ -939,7 +954,10 @@ pub(crate) async fn tarball_workspace(
workspace.name as name,
mute_critical_alerts,
color,
operator_settings
operator_settings,
slack_team_id,
slack_name,
slack_command_script
FROM workspace_settings
LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id
WHERE workspace_id = $1"#,
@@ -965,6 +983,9 @@ pub(crate) async fn tarball_workspace(
mute_critical_alerts: row.mute_critical_alerts,
color: row.color.clone(),
operator_settings: row.operator_settings.clone(),
slack_team_id: row.slack_team_id.clone(),
slack_name: row.slack_name.clone(),
slack_command_script: row.slack_command_script.clone(),
};
serde_json::to_value(settings)
.map(|v| serde_json::to_string_pretty(&v).ok())
@@ -1024,6 +1045,9 @@ pub(crate) async fn tarball_workspace(
mute_critical_alerts: row.mute_critical_alerts,
color: row.color,
operator_settings: row.operator_settings,
slack_team_id: row.slack_team_id,
slack_name: row.slack_name,
slack_command_script: row.slack_command_script,
};
serde_json::to_value(settings)
.map(|v| serde_json::to_string_pretty(&v).ok())
+26 -1
View File
@@ -53,6 +53,9 @@ export interface SimplifiedSettings {
mute_critical_alerts?: boolean;
color?: string;
operator_settings?: any;
slack_team_id?: string;
slack_name?: string;
slack_command_script?: string;
}
// Legacy settings interface for reading old settings.yaml files
@@ -77,6 +80,9 @@ interface LegacySimplifiedSettings {
mute_critical_alerts?: boolean;
color?: string;
operator_settings?: any;
slack_team_id?: string;
slack_name?: string;
slack_command_script?: string;
}
// Helper to convert legacy flat settings to new grouped format
@@ -94,6 +100,9 @@ export function migrateToGroupedFormat(settings: any): SimplifiedSettings {
if (settings.mute_critical_alerts !== undefined) result.mute_critical_alerts = settings.mute_critical_alerts;
if (settings.color !== undefined) result.color = settings.color;
if (settings.operator_settings !== undefined) result.operator_settings = settings.operator_settings;
if (settings.slack_team_id !== undefined) result.slack_team_id = settings.slack_team_id;
if (settings.slack_name !== undefined) result.slack_name = settings.slack_name;
if (settings.slack_command_script !== undefined) result.slack_command_script = settings.slack_command_script;
// Handle auto_invite: check if already grouped or needs migration
if (settings.auto_invite && typeof settings.auto_invite === "object") {
@@ -183,12 +192,18 @@ export async function pushWorkspaceSettings(
mute_critical_alerts: remoteSettings.mute_critical_alerts,
color: remoteSettings.color,
operator_settings: remoteSettings.operator_settings,
slack_team_id: remoteSettings.slack_team_id,
slack_name: remoteSettings.slack_name,
slack_command_script: remoteSettings.slack_command_script,
};
} catch (err) {
throw new Error(`Failed to get workspace settings: ${err}`);
}
if (isSuperset(localSettings, settings)) {
// Exclude read-only fields from comparison (slack_team_id and slack_name are set via OAuth only)
const { slack_team_id: _lst, slack_name: _lsn, ...comparableLocal } = localSettings;
const { slack_team_id: _rst, slack_name: _rsn, ...comparableRemote } = settings;
if (isSuperset(comparableLocal, comparableRemote)) {
log.debug(`Workspace settings are up to date`);
return;
}
@@ -366,6 +381,16 @@ export async function pushWorkspaceSettings(
requestBody: localSettings.operator_settings,
});
}
if (localSettings.slack_command_script != settings.slack_command_script) {
log.debug(`Updating slack command script...`);
await wmill.editSlackCommand({
workspace,
requestBody: {
slack_command_script: localSettings.slack_command_script,
},
});
}
}
export async function pushWorkspaceKey(
+16
View File
@@ -193,5 +193,21 @@ describe("migrateToGroupedFormat", () => {
expect("webhook" in result).toBe(false);
expect("deploy_to" in result).toBe(false);
expect("color" in result).toBe(false);
expect("slack_team_id" in result).toBe(false);
expect("slack_name" in result).toBe(false);
expect("slack_command_script" in result).toBe(false);
});
test("copies slack fields through", () => {
const settings = {
name: "ws",
slack_team_id: "T12345",
slack_name: "my-team",
slack_command_script: "u/admin/slack_handler",
};
const result = migrateToGroupedFormat(settings);
expect(result.slack_team_id).toBe("T12345");
expect(result.slack_name).toBe("my-team");
expect(result.slack_command_script).toBe("u/admin/slack_handler");
});
});