Skip to content

Commit e474fe7

Browse files
committed
fix(project-settings): disable Save button correctly
1 parent ed8ffa7 commit e474fe7

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

testgen/ui/components/frontend/standalone/project_settings/index.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ const ProjectSettings = (props) => {
4646
// newly-stored values and these derives recompute, letting
4747
// `showRetentionConfirmation` settle back to a clean state.
4848
const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
49+
const persistedName = van.derive(() => props.name.val ?? '');
50+
const persistedUseWeights = van.derive(() => props.use_dq_score_weights.val ?? true);
51+
const persistedObsUrl = van.derive(() => props.observability_api_url.val ?? '');
52+
const persistedObsKey = van.derive(() => props.observability_api_key.val ?? '');
4953
const persistedRetentionEnabled = van.derive(() => props.data_retention_enabled.val ?? false);
5054
const persistedRetentionDays = van.derive(() => props.data_retention_days.val ?? 180);
5155
const persistedRetentionCron = van.derive(() => props.retention_cron_expr.val ?? '0 1 * * *');
@@ -66,10 +70,30 @@ const ProjectSettings = (props) => {
6670
observability_api_url: van.state(true),
6771
data_retention_days: van.state(Number.isFinite(form.data_retention_days.rawVal)),
6872
};
73+
// Retention is unchanged when the enabled flag matches the persisted value and,
74+
// while enabled, the days/cron/tz also match. When retention is off, days/cron/tz
75+
// are hidden and the backend clears them, so they don't count as unsaved changes —
76+
// only the enabled flag matters.
77+
const retentionUnchanged = van.derive(() => {
78+
if (form.data_retention_enabled.val !== persistedRetentionEnabled.val) return false;
79+
if (!form.data_retention_enabled.val) return true;
80+
return form.data_retention_days.val === persistedRetentionDays.val
81+
&& form.retention_cron_expr.val === persistedRetentionCron.val
82+
&& form.retention_cron_tz.val === persistedRetentionTz.val;
83+
});
84+
// No unsaved changes when every field matches its persisted value. Because the
85+
// persisted derives are reactive, this settles back to `true` after a Save once
86+
// the props update with the stored values, disabling the button again.
87+
const noChanges = van.derive(() => form.name.val === persistedName.val
88+
&& form.use_dq_score_weights.val === persistedUseWeights.val
89+
&& form.observability_api_url.val === persistedObsUrl.val
90+
&& form.observability_api_key.val === persistedObsKey.val
91+
&& retentionUnchanged.val);
6992
const saveDisabled = van.derive(() => !formValidity.name.val
7093
|| !formValidity.observability_api_url.val
7194
|| !formValidity.observability_api_key.val
72-
|| (form.data_retention_enabled.val && !formValidity.data_retention_days.val));
95+
|| (form.data_retention_enabled.val && !formValidity.data_retention_days.val)
96+
|| noChanges.val);
7397
const testObservabilityDisabled = van.derive(() => form.observability_api_url.val.length <= 0 || form.observability_api_key.val.length <= 0);
7498
const retentionCronEditorValue = van.derive(() => {
7599
if (form.retention_cron_expr.val && form.retention_cron_tz.val && form.data_retention_enabled.val) {
@@ -284,7 +308,7 @@ const ProjectSettings = (props) => {
284308
}),
285309
),
286310
div(
287-
{ class: 'flex-row fx-justify-content-flex-end' },
311+
{ class: 'flex-row fx-justify-content-flex-end', style: 'max-width: 700px;' },
288312
Button({
289313
type: 'stroked',
290314
color: 'primary',

testgen/ui/views/project_settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ def update_project(self, project_code: str, edited_project: dict) -> None:
153153
self.project.data_retention_enabled = retention_enabled
154154
self.project.data_retention_days = retention_days if retention_enabled else None
155155
self.project.save()
156+
get_project.clear()
157+
select_projects_where.clear()
156158

157159
if retention_enabled:
158160
JobSchedule.upsert_for_retention(
@@ -173,6 +175,8 @@ def update_project(self, project_code: str, edited_project: dict) -> None:
173175
)
174176
st.toast("Scores will be recalculated in the background.")
175177

178+
st.toast("Project settings saved", icon=":material/task_alt:")
179+
176180
def test_observability_connection(self, project_code: str, edited_project: dict) -> "ObservabilityConnectionStatus":
177181
try:
178182
test_observability_exporter(

0 commit comments

Comments
 (0)