fix(usage): keep the settings tab usable on a bad collector URL, and report layouts and CSS accurately
Three items, one of which explains an error seen in the app. "The operation could not be completed" could come from a config typo. status() called collectorUrl() bare, and that throws on a bare hostname, a path, a query, or http in production. The settings page renders one generic failure when its status query errors, so a misconfigured USAGE_COLLECTOR_URL replaced the whole tab with that sentence — no cause, and no way to read the status or withdraw, because every control there sits behind that call. The URL is now reported as collector_error: 'INVALID_COLLECTOR_URL' beside the real state, the tab says what is wrong and how to fix it, and the links are only rendered when there is somewhere to point them. gallery_layouts reported grid for every preset-themed install. color_theme holds either a theme object or the NAME of a preset — the theme picker stores names, and eventTypeService seeds them (`theme_preset: 'corporateTimeline'`). Only reading value.galleryLayout made masonry, timeline, mosaic and the two gallery presets invisible. Names now resolve, and an event with no theme of its own resolves through the global one instead of being counted as grid. Only the name -> layout mapping is duplicated, not the presets; frontend/src/types/theme.types.ts stays the source of truth, and an unknown name reports `other` so a preset added later degrades to "something else" rather than quietly inflating the grid count. custom_css missed CSS applied through a template. An enabled css_templates row applied via events.css_template_id is gallery styling by the same definition as the settings fields — the Custom CSS tab is where both are authored — but neither the snapshot nor the middleware saw it, so those installs reported custom_css entirely false. Existence only; template contents are never read. Eleven tests. Reverting each fix in turn fails 3, 1 and 3 of them. Refs #1110
This commit is contained in:
@@ -56,6 +56,41 @@ const SETTING_KEYS = [
|
||||
'general_public_site_custom_css'
|
||||
];
|
||||
const truth = (value) => value === true || value === 1 || value === '1';
|
||||
|
||||
// `events.color_theme` holds either a theme object or the NAME of a preset —
|
||||
// the admin theme picker stores names, and eventTypeService seeds them too
|
||||
// (`theme_preset: 'corporateTimeline'`). Reading only `value.galleryLayout`
|
||||
// therefore reported `grid` for every preset-themed install.
|
||||
//
|
||||
// Only the layout each name maps to is duplicated here, not the presets
|
||||
// themselves; frontend/src/types/theme.types.ts stays the source of truth. A
|
||||
// name this map does not know reports `other` rather than a confident `grid`,
|
||||
// so a preset added on the frontend degrades to "something else" instead of
|
||||
// quietly inflating the grid count.
|
||||
const PRESET_LAYOUTS = {
|
||||
default: 'grid',
|
||||
elegantWedding: 'grid',
|
||||
modernMasonry: 'masonry',
|
||||
birthdayFun: 'carousel',
|
||||
corporateTimeline: 'timeline',
|
||||
artisticMosaic: 'mosaic',
|
||||
darkClassic: 'grid',
|
||||
darkElegant: 'grid',
|
||||
darkModern: 'masonry',
|
||||
galleryPremium: 'gallery-premium',
|
||||
galleryStory: 'gallery-story'
|
||||
};
|
||||
|
||||
function resolveLayout(value) {
|
||||
const named =
|
||||
typeof value === 'string'
|
||||
? PRESET_LAYOUTS[value]
|
||||
: value && typeof value === 'object'
|
||||
? value.galleryLayout
|
||||
: null;
|
||||
if (!named) return typeof value === 'string' ? 'other' : 'grid';
|
||||
return LAYOUTS.includes(named) ? named : 'other';
|
||||
}
|
||||
const parse = (value) => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
@@ -170,11 +205,26 @@ class UsageService {
|
||||
}
|
||||
async status() {
|
||||
const state = await this.state();
|
||||
// Reported, not thrown. status() used to call collectorUrl() bare, so a
|
||||
// misconfigured USAGE_COLLECTOR_URL — a bare hostname, a path, a query, or
|
||||
// http in production — failed this request outright. The settings page
|
||||
// renders one generic failure when its status query errors, so the
|
||||
// operator saw "the operation could not be completed" with no cause AND
|
||||
// no way to reach their own state: they could not read the status or even
|
||||
// withdraw, because every control on that tab is behind this call.
|
||||
let collectorUrl = null;
|
||||
let collectorError = null;
|
||||
try {
|
||||
collectorUrl = this.collectorUrl();
|
||||
} catch {
|
||||
collectorError = 'INVALID_COLLECTOR_URL';
|
||||
}
|
||||
return {
|
||||
status: state.status,
|
||||
notice_dismissed: Boolean(state.notice_dismissed),
|
||||
installation_id: state.installation_id,
|
||||
collector_url: this.collectorUrl(),
|
||||
collector_url: collectorUrl,
|
||||
collector_error: collectorError,
|
||||
schema_version: 'usage.v1',
|
||||
last_report_date: state.last_report_date,
|
||||
last_error: state.last_error,
|
||||
@@ -613,21 +663,35 @@ class UsageService {
|
||||
.first()
|
||||
);
|
||||
const theme = settings.theme_config || {};
|
||||
// An enabled template applied to an event is gallery styling by the same
|
||||
// definition as the settings fields — the Custom CSS tab is where both are
|
||||
// authored. Existence only; template contents are never read.
|
||||
const appliedTemplate = await this.db('css_templates')
|
||||
.where('is_enabled', formatBoolean(true))
|
||||
.whereIn(
|
||||
'id',
|
||||
this.db('events').whereNotNull('css_template_id').select('css_template_id')
|
||||
)
|
||||
.select('id')
|
||||
.first();
|
||||
features.custom_css.configured = Boolean(
|
||||
settings.general_custom_css ||
|
||||
settings.general_public_site_custom_css ||
|
||||
theme.customCss
|
||||
theme.customCss ||
|
||||
appliedTemplate
|
||||
);
|
||||
// Read only the theme field, never event names, IDs, sizes, counts, or photos.
|
||||
const themes = await this.db('events').distinct('color_theme');
|
||||
const layouts = new Set();
|
||||
// An event with no theme of its own renders with the global one.
|
||||
const inheritedLayout = resolveLayout(theme);
|
||||
for (const row of themes) {
|
||||
const value = parse(row.color_theme);
|
||||
const layout =
|
||||
value && typeof value === 'object'
|
||||
? value.galleryLayout || 'grid'
|
||||
: 'grid';
|
||||
layouts.add(LAYOUTS.includes(layout) ? layout : 'other');
|
||||
if (row.color_theme === null || row.color_theme === '') {
|
||||
layouts.add(inheritedLayout);
|
||||
} else {
|
||||
layouts.add(resolveLayout(value));
|
||||
}
|
||||
if (value && typeof value === 'object' && value.customCss)
|
||||
features.custom_css.configured = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user