-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
β¨ππ Define schedule formats #39
Comments
I think rather than a freeform JSON Object as a single |
Can anyone think of what the advantages would be of having a freeform json object as a schedule parameter? |
I can start prototyping an implementation that uses the Veutify v2 calendar component instead of dayspan-vuetify BUT I would simply be attempting as close to a one-for-one replacement as possible. I'm not in a position to make strategic schema or architectural decisions since I only starting looking at this a couple of days ago. It is worth mentioning that scheduling seems to be the most complex and fragile aspect of the ML Admin Panel web app. Rewriting it a week or two before launch is not ideal. I'm trying to take a measure twice cut once approach. |
@curtpw, in standup yesterday, @henryrossiter suggested we better define what we're trying to do, I added on that we should do so in writing, and @odziem added that our back-and-forth on ChildMindInstitute/mindlogger-backend#204 was a useful iterative design-and-documentation thread. This issue is intended to be the measuring twice you mentioned. Updating the calendar component is a separate, related issue. |
@binarybottle β advantages to whom? The question is a classic balancing act between structure and flexibility. In this case, as far as I can tell, the advantages to a freeform JSON Object are:
To be clear, I think a freeform JSON Object (what we have now) is better than nothing (what we had before) but worse than a well-defined structure. |
related: ReproNim/reproschema#46 |
In this morning's standup, @odziem, @binarybottle, @henryrossiter, @hotavocado, and I discussed how to handle timezones.
|
Here are some in-use-at-the-moment snippets to help until we document what we want: const setSchedule = ({ apiHost, token, id, data }) => axios({
method: 'put',
url: `${apiHost}/${id}/constraints`,
headers: {
'Girder-Token': token,
},
data,
}); saveSchedule() {
const scheduleForm = new FormData();
if (this.currentApplet && this.currentApplet.applet && this.currentApplet.applet.schedule) {
this.dialog = true;
this.saveSuccess = false;
this.saveError = false;
this.loading = true;
const schedule = this.currentApplet.applet.schedule;
scheduleForm.set('schedule', JSON.stringify(schedule || {}));
api.setSchedule({
apiHost: this.$store.state.backend,
id: this.currentApplet.applet._id,
token: this.$store.state.auth.authToken.token,
data: scheduleForm,
}).then(() => {
console.log('success');
this.loading = false;
this.saveSuccess = true;
}).catch((e) => {
this.errorMessage = `Save Unsuccessful. ${e}`;
console.log('fail');
this.loading = false;
this.saveError = true;
});
}
}, setSchedule(state, schedule) {
if (!_.isEmpty(state.currentApplet)) {
// TODO: this sucks.
const idx = _.findIndex(state.allApplets,
a => a.applet['skos:prefLabel'] == state.currentApplet.applet['skos:prefLabel']);
if (idx > -1) {
state.allApplets[idx].applet.schedule = schedule;
state.currentApplet = state.allApplets[idx];
}
// update this in the copy too.
//state.currentApplet = {...state.currentApplet, schedule };
}
}, export const dateParser = (schedule) => {
const output = {};
schedule.events.forEach((e) => {
const uri = e.data.URI;
if (!output[uri]) {
output[uri] = {
notificationDateTimes: [],
};
}
const eventSchedule = Parse.schedule(e.schedule);
const now = Day.fromDate(new Date());
const lastScheduled = getLastScheduled(eventSchedule, now);
const nextScheduled = getNextScheduled(eventSchedule, now);
const notifications = R.pathOr([], ['data', 'notifications'], e);
const dateTimes = getScheduledNotifications(eventSchedule, now, notifications);
let lastScheduledResponse = lastScheduled;
if (output[uri].lastScheduledResponse && lastScheduled) {
lastScheduledResponse = moment.max(
moment(output[uri].lastScheduledResponse),
moment(lastScheduled),
);
}
let nextScheduledResponse = nextScheduled;
if (output[uri].nextScheduledResponse && nextScheduled) {
nextScheduledResponse = moment.min(
moment(output[uri].nextScheduledResponse),
moment(nextScheduled),
);
}
output[uri] = {
lastScheduledResponse,
nextScheduledResponse,
// TODO: only append unique datetimes when multiple events scheduled for same activity/URI
notificationDateTimes: output[uri].notificationDateTimes.concat(dateTimes),
};
});
return output;
};
// Attach some info to each activity
export const appletsSelector = createSelector(
R.path(['applets', 'applets']),
responseScheduleSelector,
(applets, responseSchedule) => applets.map((applet) => {
let scheduledDateTimesByActivity = {};
// applet.schedule, if defined, has an events key.
// events is a list of objects.
// the events[idx].data.URI points to the specific activity's schema.
if (applet.schedule) {
scheduledDateTimesByActivity = dateParser(applet.schedule);
}
const extraInfoActivities = applet.activities.map((act) => {
const scheduledDateTimes = scheduledDateTimesByActivity[act.schema];
const nextScheduled = R.pathOr(null, ['nextScheduledResponse'], scheduledDateTimes);
const lastScheduled = R.pathOr(null, ['lastScheduledResponse'], scheduledDateTimes);
const lastResponse = R.path([applet.id, act.id, 'lastResponse'], responseSchedule);
return {
...act,
appletId: applet.id,
appletShortName: applet.name,
appletName: applet.name,
appletSchema: applet.schema,
appletSchemaVersion: applet.schemaVersion,
lastScheduledTimestamp: lastScheduled,
lastResponseTimestamp: lastResponse,
nextScheduledTimestamp: nextScheduled,
isOverdue: lastScheduled && moment(lastResponse) < moment(lastScheduled),
// also add in our parsed notifications...
notification: R.prop('notificationDateTimes', scheduledDateTimes),
};
});
return {
...applet,
activities: extraInfoActivities,
};
}),
); export const scheduleNotifications = (activities) => {
PushNotificationIOS.setApplicationIconBadgeNumber(1);
PushNotification.cancelAllLocalNotifications();
// const now = moment();
// const lookaheadDate = moment().add(1, 'month');
const notifications = [];
for (let i = 0; i < activities.length; i += 1) {
const activity = activities[i];
const scheduleDateTimes = activity.notification || [];
// /* below is for easy debugging.
// every 30 seconds a notification will appear for an applet.
// */
// const scheduleDateTimes = [];
// for (i = 0; i < 50; i += 1) {
// const foo = new Date();
// foo.setSeconds(foo.getSeconds() + i * 30);
// scheduleDateTimes.push(moment(foo));
// }
// console.log('activity', activity);
// /* end easy debugging section */
scheduleDateTimes.forEach((dateTime) => {
const ugctime = new Date(dateTime.valueOf());
notifications.push({
timestamp: ugctime,
niceTime: dateTime.format(),
activityId: activity.id,
activityName: activity.name.en,
appletName: activity.appletName,
activity: JSON.stringify(activity),
});
});
}
// Sort notifications by timestamp
notifications.sort((a, b) => a.timestamp - b.timestamp);
// Schedule the notifications
notifications.forEach((notification) => {
PushNotification.localNotificationSchedule({
message: `Please perform activity: ${notification.activityName}`,
date: new Date(notification.timestamp),
group: notification.activityName,
vibrate: true,
userInfo: {
id: notification.activityId,
activity: notification.activity,
},
// android only (notification.activity is already stringified)
data: notification.activity,
});
});
return notifications;
}; def setConstraints(self, folder, activity, schedule, **kwargs):
thisUser = self.getCurrentUser()
applet = jsonld_expander.formatLdObject(
_setConstraints(folder, activity, schedule, thisUser),
'applet',
thisUser,
refreshCache=True
)
thread = threading.Thread(
target=AppletModel().updateUserCacheAllUsersAllRoles,
args=(applet, thisUser)
)
thread.start()
return(applet) |
For applet
{
"around": 1578805200000,
"events": [{
"data": {
"URI": "https://raw.githubusercontent.com/hotavocado/HBN_EMA_NIMH2/master/activities/morning_set/morning_set_schema",
"busy": true,
"calendar": "",
"color": "#F44336",
"description": "",
"forecolor": "#ffffff",
"icon": "",
"location": "",
"notifications": [{
"end": null,
"notifyIfIncomplete": true,
"random": false,
"start": "09:00"
}],
"title": "EMA Assessment (Morning)",
"useNotifications": true
},
"schedule": {
"times": [
"08"
]
}
},
{
"data": {
"URI": "https://raw.githubusercontent.com/hotavocado/HBN_EMA_NIMH2/master/activities/day_set/day_set_schema",
"busy": true,
"calendar": "",
"color": "#1976d2",
"description": "",
"forecolor": "#ffffff",
"icon": "",
"location": "",
"notifications": [{
"end": null,
"notifyIfIncomplete": true,
"random": false,
"start": "15:00"
}],
"title": "EMA Assessment (Mid Day)",
"useNotifications": true
},
"schedule": {
"times": [
"14"
]
}
},
{
"data": {
"URI": "https://raw.githubusercontent.com/hotavocado/HBN_EMA_NIMH2/master/activities/evening_set/evening_set_schema",
"busy": true,
"calendar": "",
"color": "#1976d2",
"description": "",
"forecolor": "#ffffff",
"icon": "",
"location": "",
"notifications": [{
"end": null,
"notifyIfIncomplete": true,
"random": false,
"start": "20:00"
}],
"title": "EMA Assessment (Night)",
"useNotifications": true
},
"schedule": {
"times": [
"19"
]
}
}
],
"eventsOutside": true,
"fill": false,
"listTimes": true,
"minimumSize": 0,
"repeatCovers": true,
"size": 1,
"type": 1,
"updateColumns": true,
"updateRows": true
} |
Moved to #737 |
Is your feature request related to a problem? Please describe.
We don't
β ChildMindInstitute/mindlogger-backend#204 (comment)
β ChildMindInstitute/mindlogger-admin#19
Describe the solution you'd like
As @henryrossiter suggested, we should define our requirements and choose and design components based on those requirements.
Describe alternatives you've considered
To date, we've chosen components (here specifically [email protected]) and worked around their formats.
Additional context
Current relevant endpoints include:
GET /schedule
Response Body
PUT /applet/{id}/constraints
Related
GET schedule
endpointΒ mindlogger-backend#204The text was updated successfully, but these errors were encountered: