Complete unread discovery before mobile Updates triage #750
|
|
@ -992,9 +992,19 @@
|
|||
},
|
||||
});
|
||||
|
||||
const updateTriageLauncher = createUpdateTriageLauncher({
|
||||
selectUpdates: () => selectMobileQueue('update'),
|
||||
discover: () => notificationPager.loadAll(() => lastNotifications),
|
||||
isUpdatesSelected: () => selectedWorkFilter === 'update',
|
||||
hasMore: () => Boolean(notificationPagination.has_more),
|
||||
hasCheckpoint: () => updateTriage.resumable(),
|
||||
resume: () => updateTriage.resume(),
|
||||
start: () => updateTriage.start(),
|
||||
announce: message => { qs('#my-work-action-status').textContent = message; },
|
||||
});
|
||||
|
||||
function openUpdateTriage() {
|
||||
selectMobileQueue('update');
|
||||
return updateTriage.resumable() ? updateTriage.resume() : updateTriage.start();
|
||||
return updateTriageLauncher.open();
|
||||
}
|
||||
|
||||
function routedWorkItem(item) {
|
||||
|
|
|
|||
|
|
@ -1073,6 +1073,7 @@
|
|||
<script src="static/mobile-work-entry.js"></script>
|
||||
<script src="static/mobile-queue-launcher.js"></script>
|
||||
<script src="static/update-triage-session.js"></script>
|
||||
<script src="static/update-triage-launcher.js"></script>
|
||||
<script src="static/agenda-session-launcher.js"></script>
|
||||
<script src="static/mobile-launch.js"></script>
|
||||
<script src="static/mobile-app-shortcuts.js"></script>
|
||||
|
|
|
|||
|
|
@ -248,7 +248,8 @@ function createNotificationSelection({ limit = 50, onChange = () => {} } = {}) {
|
|||
function createNotificationPager({ load, onNotifications, onPagination, onStatus }) {
|
||||
let pagination = { page: 1, total: 0, has_more: false };
|
||||
let pending = false;
|
||||
return {
|
||||
let completing = null;
|
||||
const pager = {
|
||||
reset(next) {
|
||||
pagination = { ...pagination, ...(next || {}) };
|
||||
onPagination(pagination);
|
||||
|
|
@ -282,7 +283,19 @@ function createNotificationPager({ load, onNotifications, onPagination, onStatus
|
|||
pending = false;
|
||||
}
|
||||
},
|
||||
loadAll(getExisting) {
|
||||
if (completing) return completing;
|
||||
completing = (async () => {
|
||||
while (pagination.has_more) {
|
||||
const loaded = await pager.loadMore(getExisting());
|
||||
if (!loaded) return false;
|
||||
}
|
||||
return true;
|
||||
})().finally(() => { completing = null; });
|
||||
return completing;
|
||||
},
|
||||
};
|
||||
return pager;
|
||||
}
|
||||
|
||||
function createWorkPager({ load, onItems, onPagination, onStatus }) {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ const SHELL = [
|
|||
BASE + 'static/mobile-work-entry.js',
|
||||
BASE + 'static/mobile-queue-launcher.js',
|
||||
BASE + 'static/update-triage-session.js',
|
||||
BASE + 'static/update-triage-launcher.js',
|
||||
BASE + 'static/agenda-session-launcher.js',
|
||||
BASE + 'static/mobile-launch.js',
|
||||
BASE + 'static/mobile-app-shortcuts.js',
|
||||
|
|
|
|||
34
frontend/update-triage-launcher.js
Normal file
34
frontend/update-triage-launcher.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) module.exports = factory;
|
||||
else root.createUpdateTriageLauncher = factory;
|
||||
})(typeof self !== 'undefined' ? self : this, function createUpdateTriageLauncher(options) {
|
||||
let pending = null;
|
||||
|
||||
function open() {
|
||||
if (pending) return pending;
|
||||
options.selectUpdates();
|
||||
options.announce('Checking all unread updates…');
|
||||
pending = Promise.resolve()
|
||||
.then(() => options.discover())
|
||||
.then(complete => {
|
||||
if (!options.isUpdatesSelected()) return 'cancelled';
|
||||
if (complete === false || options.hasMore()) {
|
||||
options.announce('Updates check paused. Retry to check older unread updates.');
|
||||
return 'incomplete';
|
||||
}
|
||||
const opened = options.hasCheckpoint() ? options.resume() : options.start();
|
||||
if (!opened) options.announce('No unread updates are ready.');
|
||||
return opened ? 'opened' : 'empty';
|
||||
})
|
||||
.catch(() => {
|
||||
if (options.isUpdatesSelected()) {
|
||||
options.announce('Updates check paused. Retry to check older unread updates.');
|
||||
}
|
||||
return 'incomplete';
|
||||
})
|
||||
.finally(() => { pending = null; });
|
||||
return pending;
|
||||
}
|
||||
|
||||
return {open};
|
||||
});
|
||||
|
|
@ -28,7 +28,7 @@ FEATURE_SOURCES = {
|
|||
"device-setup": ("static/install-app.js", "static/mobile-device-setup.js"),
|
||||
"security-center": ("static/security-center.js",),
|
||||
"today-timer": (
|
||||
"static/my-work.js", "static/protect-today.js", "static/mobile-task-dock.js", "static/mobile-queue-launcher.js", "static/update-triage-session.js", "static/notification-undo.js", "static/today-timer.js", "static/today-recap.js",
|
||||
"static/my-work.js", "static/protect-today.js", "static/mobile-task-dock.js", "static/mobile-queue-launcher.js", "static/update-triage-session.js", "static/update-triage-launcher.js", "static/notification-undo.js", "static/today-timer.js", "static/today-recap.js",
|
||||
"static/today-rollover.js", "static/later-work.js", "static/drafts.js", "static/unfiled-captures.js",
|
||||
"static/draft-filing-session.js", "static/draft-capacity-dialog.js", "static/work-selection.js",
|
||||
"static/today-work.js", "static/pick-work.js", "static/batch-find-work.js",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,109 @@ PICK_WORK = Path(__file__).parents[1] / "frontend" / "pick-work.js"
|
|||
WORK_ROUTE = Path(__file__).parents[1] / "frontend" / "work-route.js"
|
||||
UPDATE_OWNERSHIP = Path(__file__).parents[1] / "frontend" / "update-ownership.js"
|
||||
AGENDA_SESSION_LAUNCHER = Path(__file__).parents[1] / "frontend" / "agenda-session-launcher.js"
|
||||
UPDATE_TRIAGE_LAUNCHER = Path(__file__).parents[1] / "frontend" / "update-triage-launcher.js"
|
||||
|
||||
|
||||
def test_update_triage_launch_waits_for_complete_single_flight_discovery():
|
||||
script = f"""
|
||||
const createLauncher = require({json.dumps(str(UPDATE_TRIAGE_LAUNCHER))});
|
||||
let release;
|
||||
let discoveries = 0;
|
||||
let opens = 0;
|
||||
const messages = [];
|
||||
const launcher = createLauncher({{
|
||||
selectUpdates: () => {{}},
|
||||
discover: () => {{ discoveries += 1; return new Promise(resolve => {{ release = resolve; }}); }},
|
||||
isUpdatesSelected: () => true,
|
||||
hasMore: () => false,
|
||||
hasCheckpoint: () => false,
|
||||
resume: () => {{ throw new Error('must start'); }},
|
||||
start: () => {{ opens += 1; return true; }},
|
||||
announce: message => messages.push(message),
|
||||
}});
|
||||
const first = launcher.open();
|
||||
const second = launcher.open();
|
||||
Promise.resolve().then(() => {{
|
||||
const before = {{discoveries, opens, same:first === second}};
|
||||
release(true);
|
||||
return Promise.all([first, second]).then(results =>
|
||||
process.stdout.write(JSON.stringify({{before, discoveries, opens, results, messages}}))
|
||||
);
|
||||
}});
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"before": {"discoveries": 1, "opens": 0, "same": True},
|
||||
"discoveries": 1,
|
||||
"opens": 1,
|
||||
"results": ["opened", "opened"],
|
||||
"messages": ["Checking all unread updates…"],
|
||||
}
|
||||
|
||||
|
||||
def test_update_triage_launch_preserves_checkpoint_when_discovery_fails():
|
||||
script = f"""
|
||||
const createLauncher = require({json.dumps(str(UPDATE_TRIAGE_LAUNCHER))});
|
||||
const messages = [];
|
||||
let resumed = 0;
|
||||
const launcher = createLauncher({{
|
||||
selectUpdates: () => {{}},
|
||||
discover: async () => false,
|
||||
isUpdatesSelected: () => true,
|
||||
hasMore: () => true,
|
||||
hasCheckpoint: () => true,
|
||||
resume: () => {{ resumed += 1; return true; }},
|
||||
start: () => true,
|
||||
announce: message => messages.push(message),
|
||||
}});
|
||||
launcher.open().then(result => process.stdout.write(JSON.stringify({{result, resumed, messages}})));
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"result": "incomplete",
|
||||
"resumed": 0,
|
||||
"messages": [
|
||||
"Checking all unread updates…",
|
||||
"Updates check paused. Retry to check older unread updates.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_notification_pager_load_all_retries_from_failed_page_without_duplicates():
|
||||
script = f"""
|
||||
const buildMyWork = require({json.dumps(str(MY_WORK))});
|
||||
let items = [{{id:1}}];
|
||||
let fail = true;
|
||||
const requested = [];
|
||||
const pager = buildMyWork.createNotificationPager({{
|
||||
load: async page => {{
|
||||
requested.push(page);
|
||||
if (page === 2 && fail) {{ fail = false; throw new Error('offline'); }}
|
||||
return page === 2 ? {{page:2,total:101,has_more:true,items:[{{id:1}},{{id:51}}]}} :
|
||||
{{page:3,total:101,has_more:false,items:[{{id:101}}]}};
|
||||
}},
|
||||
onNotifications: value => {{ items = value; }},
|
||||
onPagination: () => {{}},
|
||||
onStatus: () => {{}},
|
||||
}});
|
||||
pager.reset({{page:1,total:101,has_more:true}});
|
||||
pager.loadAll(() => items).then(firstResult => pager.loadAll(() => items).then(secondResult =>
|
||||
process.stdout.write(JSON.stringify({{firstResult, secondResult, requested, ids:items.map(item => item.id)}}))
|
||||
));
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"firstResult": False,
|
||||
"secondResult": True,
|
||||
"requested": [2, 2, 3],
|
||||
"ids": [1, 51, 101],
|
||||
}
|
||||
|
||||
|
||||
def test_agenda_session_launch_waits_for_complete_single_flight_discovery():
|
||||
|
|
|
|||
|
|
@ -847,6 +847,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
|||
"/dashboard/static/mobile-work-entry.js",
|
||||
"/dashboard/static/mobile-queue-launcher.js",
|
||||
"/dashboard/static/update-triage-session.js",
|
||||
"/dashboard/static/update-triage-launcher.js",
|
||||
"/dashboard/static/agenda-session-launcher.js",
|
||||
"/dashboard/static/mobile-launch.js",
|
||||
"/dashboard/static/mobile-app-shortcuts.js",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user