openvibers/openvibe.watch Commits
A fired watch's notice reads a named value as words, not JSON (#255)
Changes
@@@ -88,11 +88,22 @@ const REFUSED_URL = new Set(['bad_url', 'port_refused', 'address_refused']);const clip = (s, n) => { const t = String(s == null ? '' : s).replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };--/** A value as a person reads it in one line. */++/**++ * A value as a person reads it in one line. A feed entry or any record with a title or name reads as that title and++ * a short summary ("(Senior) Product Engineer — Bielefeld · Remote"), never as its JSON; a list says how many and++ * names the first. Other shapes stay JSON (a price object, a status map).++ */function valueText(v) {if (v == null) return 'no value';if (typeof v === 'string') return v;if (typeof v === 'number' || typeof v === 'boolean') return String(v);++ const named = (o) => (o && typeof o === 'object' && !Array.isArray(o) && [o.title, o.name].find((x) => typeof x === 'string' && x.trim())) || null;++ const head = named(v);++ if (head) {++ const more = [v.summary, v.description].find((x) => typeof x === 'string' && x.trim());++ return more ? `${clip(head, 160)} — ${clip(more, 120)}` : clip(head, 160);++ }++ if (Array.isArray(v) && v.length && named(v[0])) return `${v.length} item${v.length === 1 ? '' : 's'}, first: ${clip(named(v[0]), 160)}`;try { return JSON.stringify(v); } catch { return String(v); }}@@@ -513,6 +513,21 @@ t('an alert leaves out the source and fetched lines when the watch stored neitheassert.deepStrictEqual(p.rich_content.evidence, { before: null, after: 'v1', source_url: null, fetched_at: null });});++t('a feed entry or a named record reads as its title and summary, never as JSON', () => {++ const { notificationFor } = require('../server/actions');++ const at = (value, previousValue) => notificationFor({++ config: { baseUrl: 'https://openvibe.watch' },++ watch: { id: 'wch_01JAB2C3D4E5F6G7H8J9K0MNPQ', name: 'Jobs', source: { kind: 'event', pattern: 'work.job.posted' } },++ run: { id: 'ckr_01JAB2C3D4E5F6G7H8J9K0MNPQ' }, action: {}, value, previousValue, observedAt: null,++ }).message;++ const entry = { identity: 'https://openvibe.work/jobs/job_1', kind: 'entry', title: '(Senior) Product Engineer at sustaind GmbH', link: 'https://openvibe.work/jobs/job_1', summary: 'Bielefeld · Remote' };++ assert.strictEqual(at(entry), '(Senior) Product Engineer at sustaind GmbH — Bielefeld · Remote');++ assert.strictEqual(at({ name: 'Front door', state: 'open' }), 'Front door');++ assert.strictEqual(at([entry, entry]), '2 items, first: (Senior) Product Engineer at sustaind GmbH');++ assert.strictEqual(at({ price: 12 }), '{"price":12}', 'a shape with no name stays as it is');++ assert.strictEqual(at({ title: 'B' }, { title: 'A' }), 'B (was A)');++});++t('done', async () => { await web.close(); await svc.stop(); });t.run();
@@ -88,11 +88,22 @@ const REFUSED_URL = new Set(['bad_url', 'port_refused', 'address_refused']);
@@ -88,11 +88,22 @@ const REFUSED_URL = new Set(['bad_url', 'port_refused', 'address_refused']);
const clip = (s, n) => { const t = String(s == null ? '' : s).replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
const clip = (s, n) => { const t = String(s == null ? '' : s).replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
-/** A value as a person reads it in one line. */
+/**
+ * A value as a person reads it in one line. A feed entry or any record with a title or name reads as that title and
+ * a short summary ("(Senior) Product Engineer — Bielefeld · Remote"), never as its JSON; a list says how many and
+ * names the first. Other shapes stay JSON (a price object, a status map).
+ */
function valueText(v) {
function valueText(v) {
if (v == null) return 'no value';
if (v == null) return 'no value';
if (typeof v === 'string') return v;
if (typeof v === 'string') return v;
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
+ const named = (o) => (o && typeof o === 'object' && !Array.isArray(o) && [o.title, o.name].find((x) => typeof x === 'string' && x.trim())) || null;
+ const head = named(v);
+ if (head) {
+ const more = [v.summary, v.description].find((x) => typeof x === 'string' && x.trim());
+ return more ? `${clip(head, 160)} — ${clip(more, 120)}` : clip(head, 160);
+ }
+ if (Array.isArray(v) && v.length && named(v[0])) return `${v.length} item${v.length === 1 ? '' : 's'}, first: ${clip(named(v[0]), 160)}`;
try { return JSON.stringify(v); } catch { return String(v); }
try { return JSON.stringify(v); } catch { return String(v); }
}
}
@@ -513,6 +513,21 @@ t('an alert leaves out the source and fetched lines when the watch stored neithe
@@ -513,6 +513,21 @@ t('an alert leaves out the source and fetched lines when the watch stored neithe
assert.deepStrictEqual(p.rich_content.evidence, { before: null, after: 'v1', source_url: null, fetched_at: null });
assert.deepStrictEqual(p.rich_content.evidence, { before: null, after: 'v1', source_url: null, fetched_at: null });
});
});
+t('a feed entry or a named record reads as its title and summary, never as JSON', () => {
+ const { notificationFor } = require('../server/actions');
+ const at = (value, previousValue) => notificationFor({
+ config: { baseUrl: 'https://openvibe.watch' },
+ watch: { id: 'wch_01JAB2C3D4E5F6G7H8J9K0MNPQ', name: 'Jobs', source: { kind: 'event', pattern: 'work.job.posted' } },
+ run: { id: 'ckr_01JAB2C3D4E5F6G7H8J9K0MNPQ' }, action: {}, value, previousValue, observedAt: null,
+ }).message;
+ const entry = { identity: 'https://openvibe.work/jobs/job_1', kind: 'entry', title: '(Senior) Product Engineer at sustaind GmbH', link: 'https://openvibe.work/jobs/job_1', summary: 'Bielefeld · Remote' };
+ assert.strictEqual(at(entry), '(Senior) Product Engineer at sustaind GmbH — Bielefeld · Remote');
+ assert.strictEqual(at({ name: 'Front door', state: 'open' }), 'Front door');
+ assert.strictEqual(at([entry, entry]), '2 items, first: (Senior) Product Engineer at sustaind GmbH');
+ assert.strictEqual(at({ price: 12 }), '{"price":12}', 'a shape with no name stays as it is');
+ assert.strictEqual(at({ title: 'B' }, { title: 'A' }), 'B (was A)');
+});
+
t('done', async () => { await web.close(); await svc.stop(); });
t('done', async () => { await web.close(); await svc.stop(); });
t.run();
t.run();