From 152d129204f620a713f990aa7e61369845f6d70b Mon Sep 17 00:00:00 2001 From: Irv Katz <60225276+exidy80@users.noreply.github.com> Date: Sat, 7 Dec 2024 18:59:34 -0500 Subject: [PATCH 01/35] upgrade user-list and user-info --- README.md | 9 +- UPGRADE_NOTES.md | 23 +- app/components/ui/form-field.hbs | 44 ++- app/components/ui/form-field.js | 12 +- ...ble-list.hbs => user-collapsible-list.hbs} | 0 ...sible-list.js => user-collapsible-list.js} | 0 app/components/user-info.hbs | 299 +++++++++--------- app/components/user-info.js | 236 +++++++------- app/components/user-list.hbs | 24 +- app/components/user-list.js | 182 +++++------ app/routes/users/user.js | 16 +- app/services/error-handling.js | 5 +- 12 files changed, 452 insertions(+), 398 deletions(-) rename app/components/{collapsible-list.hbs => user-collapsible-list.hbs} (100%) rename app/components/{collapsible-list.js => user-collapsible-list.js} (100%) diff --git a/README.md b/README.md index 9292f9a1b..45ecc9700 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ If you run into the following error while running tests: ## Client `/app` -EnCOMPASS uses Ember for the client and was recently migrated to Ember Octane from v2.14. Portions are not fully migrated. +EnCOMPASS uses Ember for the client and was recently migrated to Ember Octane from v2.14. Portions are not fully migrated. See [UPGRADE_NOTES](./UPGRADE_NOTES.md) for more information. ### workflow @@ -90,9 +90,6 @@ EnCOMPASS uses Ember for the client and was recently migrated to Ember Octane fr Ember is switching to Glimmer for its component engine. Components that have their templates (.hbs files) in `app/components` have been migrated. Their classes (.js files) will look like native JS classes. Components with templates in `app/templates/components` have not been migrated and still use Classic Ember component classes. I tried to combine similar components when possible. -- `admin-problem-filter` and `admin-workspace-filter` could be comined -- `problem-filter` and `workspace-filter` could be combined - ### mixins usage of mixins (found in `app/mixins`) are deprecated - they still work for classic components but should be refactored away @@ -122,9 +119,7 @@ Each route has a corresponding template that gets rendered. It should be in the Libraries that are not managed by npm are added in the `/vendor` directory and configured into the bundle in `/ember-cli-build.js` including: -1. selectize input library (see `app/components/selectize-input.js`) -2. typeahead library (see `app/components/twitter-typeahead.js`) -3. selection libraries (`vendor/image-tagging.js` and `vendor/selection-highlighting.js`) that are used in `app/components/workspace-submission.js` +- selection libraries (`vendor/image-tagging.js` and `vendor/selection-highlighting.js`) that are used in `app/components/workspace-submission.js` ### `/helpers` diff --git a/UPGRADE_NOTES.md b/UPGRADE_NOTES.md index 989b7a25b..50f7261e4 100644 --- a/UPGRADE_NOTES.md +++ b/UPGRADE_NOTES.md @@ -6,6 +6,19 @@ There has been various attempts to upgrade this app to modern Ember (Octane, Emb This file is an attempt to document what has and has not been done, as well as suggestions for future developers if I (like all others) leave an incomplete upgrade process. +# Notes about the current state + +Enc-test has been updated with the latest version of the work that I've done over the last couple of months, as represented in this file. Of course, there is plenty that does not work; mostly parts of the system that have not yet been upgraded, upgrades that have not been adequately tested, and a few items that I document below that represent my current work when this contracted ended. To help the next developer, there are two files beyond this one: + +1. component audit.xls -- contains notes about all components in the Encompass system, including which have been upgraded or deleted. +2. componentFinder.js -- script (run with "node componentFinder") that produces a report of all the components used in the different routes, all the components used by other components, etc. This should help the next developer in understanding how the Encompass app is organized. + +## Items in progress + +1. user-info component -- mostly works except for a few of the updates: seen tour, authorized, etc. +2. problem-list-container and related components -- trashed problems might not be showing up correctly. Deleting and some actions might not be working. +3. workspace-list-container and related components -- trashed and hidden workspaces might not be showing up correctly. Many of the actions in the three-dot (more) menu are not working. + # Upgrades needed globally ## Removal of Mixins @@ -60,6 +73,10 @@ In Ember 4.5, helpers can now be regular functions rather than wrapped in a mana 11/14/2024: Upgraded to 4.5 and simplified all helpers. +## Controllers + +Controllers are slated to be deprecated. Best practices are to replace them with the use of components -- the idea is that route templates reference components that contain work that had been done by controllers. + ## EmberTable There are other packages that are more aligned with Glimmer and Octane approaches to Ember. However, depending on the needs, perhaps the 5 uses of EmberTable could be replaced with vanilla JS. @@ -87,6 +104,10 @@ Instead, we could leverage the {timestamps: true} option when defining all the M There is now the folder app/components/ui that contains the form-field and expandable-cell components. The purpose of this folder is a place for generic UI components. Other generic UI components include: my-select, selectize-input, twitter-typeahead, radio-group (and radio-group-item), toggle-control, checkbox-list (and checkbox-list-item), collapsible-list, and quill-container. Once these get moved into that folder, every usage must reference the "Ui" namespace, such as or . +# Other components + +Similar to the Ui example above, usage of Namespaces is encouraged in Ember moving forward. Thus, we should reorganize the app/components folder with subfolders representing the distinct subsystems of Encompass. The components in the folders would then be referenced in templates via namespaces, such as which refers to app/components/users/user-list.js and user-list.hbs. + ## New Workspaces The components workspace-new, workspace-new-enc, and workspace-new-pows are currently not used. They seem to reflect some type of new functionality (rather than template/workspaces/new.hbs and workspace-new-container, which are used) that Pedro was working on but never finished. I'm leaving these files in the codebase with the hope that someday someone will use them to figure out what was being done and to finish the work. Likely the intent was to have tempalte/workspaces/new.hbs use the workspace-new component. @@ -184,7 +205,7 @@ If this.removeMessages is undefined, Ember might **not** show an error in the co - **Components** - most of the discussion in this file focuses on the upgrading of components from classic to modern (Glimmer, Octane, Ember 4.5). - **Adapters** - Upgraded -- **Controllers** - Seem to have already been upgraded. I've not tested these. +- **Controllers** - Controllers are slated to be deprecated in favor of using components. That is, the template for a route would simply contain invocations of one or ore components. Thus, working through the controllers and refactoring to remove them all is another goal of the upgrade. - **Helpers** - as document elsewhere in this document, all upgraded. - **Initializers** - only one and I believe it's not needed for production. I upgraded it, however. - **Mixins** - as documented elsewhere, I'm in the process of eliminating these and double-checking others' work on removing these from components, etc. diff --git a/app/components/ui/form-field.hbs b/app/components/ui/form-field.hbs index 64f96545e..978cdf05a 100644 --- a/app/components/ui/form-field.hbs +++ b/app/components/ui/form-field.hbs @@ -1,21 +1,45 @@ -
+
{{! If isEditing is true, render an input/checkbox/other editable UI }} {{#if @isEditing}} {{#if (is-equal @type 'checkbox')}} + {{#if @label}} + + {{/if}} {{else if (is-equal @type 'textarea')}} + {{#if @label}} + + {{/if}} + {{else if (is-equal @type 'button')}} + {{else}} + {{#if @label}} + + {{/if}} {{/if}} - {{! Render a button if provided }} + {{! Render a post-field button if provided }} {{#if @buttonLabel}} - + {{#if @isEditing}} + + {{/if}} {{/if}}
\ No newline at end of file diff --git a/app/components/ui/form-field.js b/app/components/ui/form-field.js index e4ed262eb..62ccc7a38 100644 --- a/app/components/ui/form-field.js +++ b/app/components/ui/form-field.js @@ -3,7 +3,12 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; export default class UiFormFieldComponent extends Component { - @tracked currentValue = this.args.value; // Track the value for inline editing + // @tracked currentValue = this.args.value; // Track the value for inline editing + @tracked currentValue = this.args.value; + + get id() { + return this.args.id || this.args.name || 'xyzzy'; + } @action handleInput(event) { @@ -27,4 +32,9 @@ export default class UiFormFieldComponent extends Component { this.args.onClick(); } } + + @action + resetEditingValue() { + this.currentValue = this.args.value; + } } diff --git a/app/components/collapsible-list.hbs b/app/components/user-collapsible-list.hbs similarity index 100% rename from app/components/collapsible-list.hbs rename to app/components/user-collapsible-list.hbs diff --git a/app/components/collapsible-list.js b/app/components/user-collapsible-list.js similarity index 100% rename from app/components/collapsible-list.js rename to app/components/user-collapsible-list.js diff --git a/app/components/user-info.hbs b/app/components/user-info.hbs index 830c615b4..ce684e3ee 100644 --- a/app/components/user-info.hbs +++ b/app/components/user-info.hbs @@ -1,5 +1,7 @@ -

{{@user.username}}'s Account Details

-{{#each this.errorHandling.loadOrgsErrors as |error|}} +

+ {{@user.username}}'s Account Details +

+{{#each this.loadOrgsErrors as |error|}}

{{error}}

{{/each}}
@@ -11,36 +13,38 @@ Username - {{#if this.isEditing}} - - {{else}} - {{@user.username}} - {{/if}} + First Name - {{#if this.isEditing}} - - {{else}} - {{@user.firstName}} - {{/if}} + Last Name - {{#if this.isEditing}} - - {{else}} - {{@user.lastName}} - {{/if}} + @@ -52,11 +56,13 @@ Email - {{#if this.isEditing}} - - {{else}} - {{@user.email}} - {{/if}} + @@ -64,22 +70,20 @@ Email Confirmed - {{#if this.isEditing}} - {{#if this.unconfirmedEmail}} - {{#if @user.email}} - - - {{/if}} - {{else}} - {{@user.isEmailConfirmed}} - {{/if}} + {{#if this.noEmail}} + No email address provided + {{else if @user.isEmailConfirmed}} + {{! Note: Use the original value so can undo during editing }} + Yes {{else}} - {{@user.isEmailConfirmed}} + {{/if}} @@ -87,8 +91,23 @@ {{#if this.isEditing}} Password - + + {{#if this.isResettingPassword}} + + {{else}} + + {{/if}} + {{/if}} {{#unless @user.organizationRequest}} @@ -97,14 +116,16 @@ {{#if this.isEditing}}*{{/if}} {{#if this.isEditing}} {{#if @currentUser.isAdmin}} - + + /> + {{else}} {{@user.organization.name}} {{/if}} @@ -121,8 +142,11 @@ Would you like to create {{@user.organizationRequest}}
- - + + {{else}} {{@user.organizationRequest}} @@ -139,10 +163,11 @@ User's Sections {{#each @userSections as |section|}} -

{{section.name}}

+

+ + {{section.name}} + +

{{/each}} @@ -150,32 +175,36 @@ Location - {{#if this.isEditing}} - - {{else}} - {{@user.location}} - {{/if}} + Seen Tour - {{#if this.isEditing}} - {{#if this.seenTour}} - - {{else}} - - {{/if}} - {{/if}} - {{this.tourDate}} + Last Seen {{#if @user.lastSeen}} - {{format-date @user.lastSeen 'MMM Do YYYY'}} + + {{format-date @user.lastSeen 'MMM Do YYYY'}} at - {{format-date @user.lastSeen 'hh:mma'}} + {{format-date @user.lastSeen 'hh:mma'}} + {{else}} Never {{/if}} @@ -183,9 +212,11 @@ {{#if @user.lastModifiedBy}} Last Modified - {{format-date @user.lastModifiedDate 'MMM Do YYYY'}} + + {{format-date @user.lastModifiedDate 'MMM Do YYYY'}} by - {{@user.lastModifiedBy.username}} + {{@user.lastModifiedBy.username}} + {{/if}} @@ -195,15 +226,10 @@ {{#if this.canEdit}} - {{/if}} @@ -211,112 +237,87 @@ {{account-types @user.accountType}} {{/if}} - {{#if @user.isAuthorized}} + + Authorized + + {{#if this.canEdit}} + + {{else}} + {{yes-no @user.isAuthorized}} + {{/if}} + + + {{#if @user.authorizedBy}} - Authorized - - {{#if this.isEditing}} - {{#if this.canEdit}} - - {{else}} - {{@user.isAuthorized}} - {{/if}} - {{else}} - {{@user.isAuthorized}} - {{/if}} - + Authorized By + {{@user.authorizedBy.username}} - {{#if @user.authorizedBy}} - - Authorized By - {{@user.authorizedBy.username}} - - {{/if}} - {{else}} + {{/if}} + {{#if @user.requestReason}} - Request Reason + Reason for Authorization Requst {{@user.requestReason}} - {{#if this.isEditing}} - - Authorize - - {{#if this.canEdit}} - - {{else}} - {{@user.isAuthorized}} - {{/if}} - - - {{else}} - - Authorized - - No - - - {{/if}} {{/if}} {{#if this.canEdit}} {{#if this.isEditing}} - {{#if this.isResettingPassword}} - - {{/if}} - {{#unless this.isResettingPassword}} -
- - -
- {{/unless}} +
+ + +
{{else}}
{{#if @currentUser.isAdmin}} {{#if @user.isTrashed}} {{else}} {{/if}} {{/if}}
{{/if}} {{else}} -

You can't edit users you haven't created. Contact - an admin to make changes.

+

You may not edit users whom you did not create. + Contact an admin to make changes.

{{/if}}
-{{#each this.errorHandling.findRecordErrors as |error|}} +{{#each this.findRecordErrors as |error|}}

{{error}}

{{/each}} -{{#each this.errorHandling.createRecordErrors as |error|}} +{{#each this.createRecordErrors as |error|}}

{{error}}

{{/each}} -{{#each this.errorHandling.updateRecordErrors as |error|}} +{{#each this.updateRecordErrors as |error|}}

{{error}}

{{/each}} \ No newline at end of file diff --git a/app/components/user-info.js b/app/components/user-info.js index 0db8cbb15..a76bcb77a 100644 --- a/app/components/user-info.js +++ b/app/components/user-info.js @@ -12,63 +12,57 @@ export default class UserInfoComponent extends Component { @service store; @tracked isEditing = false; - @tracked authorized = null; - @tracked selectedType = null; - @tracked willOveride = null; - @tracked fieldType = 'password'; - @tracked loadOrgsErrors = []; - @tracked updateRecordErrors = []; - @tracked createRecordErrors = []; - @tracked findRecordErrors = []; @tracked isResettingPassword = false; + @tracked user = null; - get canEdit() { - let user = this.args.user; - let currentUser = this.args.currentUser; - if (!user || !currentUser) { - return false; - } - - // is Admin - if (this.args.currentUser.accountType === 'A') { - return true; - } + get loadOrgsErrors() { + return this.errorHandling.getErrors('loadOrgsErrors'); + } - // is self - if (user.get('id') === currentUser.get('id')) { - return true; - } + get updateRecordErrors() { + return this.errorHandling.getErrors('updateRecordErrors'); + } - let creatorId = this.utils.getBelongsToId(user, 'createdBy'); + get createRecordErrors() { + return this.errorHandling.getErrors('createRecordErrors'); + } - // is creator - if (currentUser.get('id') === creatorId) { - return true; - } + get findRecordErrors() { + return this.errorHandling.getErrors('findRecordErrors'); + } - // pd admin for user's org - if (this.basePermissions.isRecordInPdDomain(user)) { - return true; - } - return false; + get noEmail() { + return !this.args.user.email; } - get canConfirm() { - if (this.basePermissions.isActingAdmin) { - return true; - } - if (this.basePermissions.isRecordInPdDomain(this.args.user)) { - return true; - } - return false; + get canEdit() { + const { user, currentUser } = this.args; + if (!user || !currentUser) return false; + + const creatorId = this.utils.getBelongsToId(user, 'createdBy'); + + // can edit if: + // - current user is an admin + // - current user is editing their own profile + // - current user is the creator of the user + // - current user is a PD admin for the user's org + return ( + currentUser.accountType === 'A' || + user.id === currentUser.id || + creatorId === currentUser.id || + this.basePermissions.isRecordInPdDomain(user) + ); } - get unconfirmedEmail() { - return !this.args.user.isEmailConfirmed; + get canConfirm() { + return ( + this.basePermissions.isActingAdmin || + this.basePermissions.isRecordInPdDomain(this.args.user) + ); } get accountTypes() { - let accountType = this.args.currentUser.get('accountType'); + let accountType = this.args.currentUser.accountType; let accountTypes; if (accountType === 'A') { @@ -84,50 +78,53 @@ export default class UserInfoComponent extends Component { return accountTypes; } + get seenTour() { + return this.user?.seenTour ?? this.args.user.seenTour; + } + get tourDate() { - var date = this.seenTour; + var date = this.args.user.seenTour; if (date) { return moment(date).fromNow(); } - return 'no'; + return 'No'; } - @action editUser() { - let user = this.args.user; - this.userEmail = user.email; - - let accountType = user.get('accountType'); - if (accountType === 'S') { - this.selectedType = 'Student'; - } else if (accountType === 'T') { - this.selectedType = 'Teacher'; - } else if (accountType === 'A') { - this.selectedType = 'Admin'; - } else if (accountType === 'P') { - this.selectedType = 'Pd Admin'; - } else { - this.selectedType = null; - } + @action + editUser() { this.isEditing = true; - let isAuth = user.get('isAuthorized'); - this.authorized = isAuth; + this.user = this.extractEditableProperties(this.args.user); + } + + @action + handleCancelEdit() { + this.isEditing = false; + this.user = this.extractEditableProperties(this.args.user); + } + + @action + handleSave() { + this.checkOrgExists(); + } + + @action + handleChange(property, value) { + this.user = { ...this.user, [property]: value }; + } + + @action + handleAccountTypeChange(value) { + this.handleChange('accountType', value[0]); } @action checkOrgExists() { - let user = this.args.user; - let userOrg = user.get('organization.content'); - let userOrgRequest = user.get('organizationRequest'); + let user = this.user; + let userOrg = user.organization?.content; + let userOrgRequest = user.organizationRequest; let org = this.org; let orgReq = this.orgReq; - let options = [ - Boolean(userOrg), - Boolean(userOrgRequest), - Boolean(org), - Boolean(orgReq), - ]; - - if (options.includes(true)) { + if (userOrg || userOrgRequest || org || orgReq) { this.saveUser(); } else { this.alert @@ -146,38 +143,39 @@ export default class UserInfoComponent extends Component { } saveUser() { - let currentUser = this.args.currentUser; - let user = this.args.user; - let org = this.org; - let orgReq = this.orgReq; + const currentUser = this.args.currentUser; + const user = this.args.user; + + // set the isConfirmingEmail flag if the current user has manually confirmed this user's email + // so server knows whether to make request to sso server + user.isConfirmingEmail = + this.user.isEmailConfirmed && !user.isEmailConfirmed; + + // update user with the values from the properties in this.user + Object.keys(this.user).forEach((key) => { + user.set(key, this.user[key]); + }); + + const org = this.org; + const orgReq = this.orgReq; let orgs = this.args.orgList; let matchingOrg = orgs.findBy('name', orgReq); if (matchingOrg) { - org = matchingOrg; - orgReq = null; + this.org = matchingOrg; + this.orgReq = null; } - // should we check to see if any information was actually updated before updating modified by/date? - let accountType = this.selectedType; - let accountTypeLetter = accountType.charAt(0).toUpperCase(); - user.set('accountType', accountTypeLetter); - if (org) { - user.set('organization', org); + user.org = org; } if (orgReq) { - user.set('organizationRequest', orgReq); + user.organizationRequest = orgReq; } - user.set('email', this.userEmail); //if is authorized is now true, then we need to set the value of authorized by to current user - let newDate = new Date(); - user.set('lastModifiedBy', currentUser); - user.set('lastModifiedDate', newDate); - - // so server knows whether to make request to sso server - user.set('isConfirmingEmail', this.isConfirmingEmail); + user.lastModifiedBy = currentUser; + user.lastModifiedDate = new Date(); user .save() @@ -190,7 +188,6 @@ export default class UserInfoComponent extends Component { false, null ); - this.isEditing = false; this.errorHandling.removeMessages('updateRecordErrors'); }) .catch((err) => { @@ -211,14 +208,11 @@ export default class UserInfoComponent extends Component { } } - @action resetPassword() { + @action + resetPassword() { this.isResettingPassword = true; } - @action authEmail() { - this.willOveride = true; - } - @action confirmOrgModal() { let user = this.args.user; let reqOrg = user.get('organizationRequest'); @@ -255,7 +249,7 @@ export default class UserInfoComponent extends Component { user.set('organizationRequest', null); user .save() - .then((user) => { + .then(() => { this.alert.showToast( 'success', `${orgName} Created`, @@ -281,7 +275,7 @@ export default class UserInfoComponent extends Component { user.set('organizationRequest', null); user .save() - .then((res) => { + .then(() => { // handle success this.errorHandling.removeMessages('updateRecordErrors'); }) @@ -370,29 +364,37 @@ export default class UserInfoComponent extends Component { this.isResettingPassword = false; } - @action handleResetSuccess(updatedUser) { + @action handleResetSuccess() { this.isResettingPassword = false; this.resetPasswordSuccess = true; this.errorHandling.removeMessages('findRecordErrors'); this.args.refresh(); } - @action clearTour() { - this.args.user.seenTour = null; + @action + clearTour() { + this.handleChange('seenTour', null); } - @action doneTour() { - this.args.user.seenTour = new Date(); + @action + doneTour() { + this.handleChange('seenTour', new Date()); } - @action onManualConfirm() { - // clicked manual confirm email box - let user = this.args.user; - - if (user) { - let isConfirmingEmail = !user.get('isEmailConfirmed'); - this.isConfirmingEmail = isConfirmingEmail; - user.toggleProperty('isEmailConfirmed'); - } + extractEditableProperties(user) { + return { + username: user.username, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + isEmailConfirmed: user.isEmailConfirmed, + organizationRequest: user.organizationRequest, + location: user.location, + accountType: user.accountType, + organization: user.organization, + isAuthorized: user.isAuthorized, + isTrashed: user.isTrashed, + seenTour: user.seenTour, + }; } } diff --git a/app/components/user-list.hbs b/app/components/user-list.hbs index d2b4a27fb..a8d4ba270 100644 --- a/app/components/user-list.hbs +++ b/app/components/user-list.hbs @@ -7,57 +7,57 @@
-

EnCoMPASS +

EnCoMPASS is part of the Online Reflection and Community-based Instructional Development System for Mathematics Education (ORCIDS), an NSF-funded collaboration between Drexel University, California State University San @@ -91,16 +95,17 @@ Maintained by 21PSTEM.org This material is based upon work supported by the National Science Foundation under Grant No. 2010306. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation. EnCoMPASS version - {{this.version}} + {{this.model.version}} last updated on - {{this.buildDate}}. + {{this.model.buildDate}}.

From d9f2d9139445c5394d4dff424399c967aff8772b Mon Sep 17 00:00:00 2001 From: Irv Katz <60225276+exidy80@users.noreply.github.com> Date: Sat, 7 Dec 2024 19:01:24 -0500 Subject: [PATCH 03/35] additional changes related to user subsystem upgrade --- app/routes/users.js | 6 ++++-- app/styles/_users.scss | 3 +-- app/templates/users.hbs | 16 +++++++++------- app/templates/users/user.hbs | 10 ++++++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/routes/users.js b/app/routes/users.js index cecee2ae9..1e7e6b152 100644 --- a/app/routes/users.js +++ b/app/routes/users.js @@ -19,12 +19,14 @@ export default class UsersRoute extends Route { this.transitionTo('/'); } } - model() { + async model() { const currentUser = this.modelFor('application'); + const users = await this.store.findAll('user'); return hash({ currentUser, - users: this.store.findAll('user'), + users, organizations: this.store.findAll('organization'), + trashedUsers: users.filter((user) => user.isTrashed), }); } } diff --git a/app/styles/_users.scss b/app/styles/_users.scss index 4821cb3a4..63e357614 100644 --- a/app/styles/_users.scss +++ b/app/styles/_users.scss @@ -97,9 +97,8 @@ } .reset-user-box { - margin: 20px; .auth-form-input { - width: 50%; + width: 80%; margin: 0 auto; label { margin-bottom: 10px; diff --git a/app/templates/users.hbs b/app/templates/users.hbs index df2c9fa08..3a1d82b6a 100644 --- a/app/templates/users.hbs +++ b/app/templates/users.hbs @@ -1,12 +1,14 @@ {{#if this.model.currentUser.isStudent}} -
FORBIDDEN
+
FORBIDDEN
{{else}} - + {{/if}} - - -
+
{{outlet}} -
- +
\ No newline at end of file diff --git a/app/templates/users/user.hbs b/app/templates/users/user.hbs index be06db807..d6aa3082f 100644 --- a/app/templates/users/user.hbs +++ b/app/templates/users/user.hbs @@ -1,4 +1,10 @@ {{page-title this.model.user.username}} -
- +
+
\ No newline at end of file From d1110931e7b05b9da30e76353ce2eec5a726705a Mon Sep 17 00:00:00 2001 From: Irv Katz <60225276+exidy80@users.noreply.github.com> Date: Sat, 7 Dec 2024 19:27:17 -0500 Subject: [PATCH 04/35] used router service instead of deprecated this.transitionTo --- app/components/assignment-info-teacher.js | 10 +++++----- app/components/problem-info.js | 10 +++++----- app/routes/application.js | 15 ++++++++------- app/routes/assignments/assignment.js | 3 ++- app/routes/assignments/new.js | 7 ++++--- app/routes/folders_index.js | 4 +++- app/routes/problems/new.js | 5 +++-- app/routes/responses.js | 7 ++++--- app/routes/responses/new/submission.js | 7 ++++--- app/routes/responses/submission.js | 11 ++++++----- app/routes/sections/new.js | 3 ++- app/routes/sections/section.js | 5 +++-- app/routes/submission/response.js | 13 +++++++------ app/routes/submissions/first.js | 5 +++-- app/routes/submissions/submission.js | 11 ++++++++--- app/routes/unauthorized.js | 7 ++++--- app/routes/unconfirmed.js | 7 ++++--- app/routes/users.js | 3 ++- app/routes/vmt/import.js | 4 +++- app/routes/workspace/submissions/submission.js | 12 ++++++++---- app/routes/workspaces.js | 4 +++- app/routes/workspaces/copy.js | 3 ++- app/routes/workspaces/new.js | 7 ++++--- 23 files changed, 97 insertions(+), 66 deletions(-) diff --git a/app/components/assignment-info-teacher.js b/app/components/assignment-info-teacher.js index 4c07e8edc..a92d6b76c 100644 --- a/app/components/assignment-info-teacher.js +++ b/app/components/assignment-info-teacher.js @@ -6,6 +6,11 @@ import $ from 'jquery'; import moment from 'moment'; export default class AssignmentInfoTeacherComponent extends ErrorHandlingComponent { + @service store; + @service router; + @service('sweet-alert') alert; + @service('assignment-permissions') permissions; + @service('utility-methods') utils; @tracked formattedDueDate = null; @tracked formattedAssignedDate = null; @tracked isEditing = false; @@ -45,11 +50,6 @@ export default class AssignmentInfoTeacherComponent extends ErrorHandlingCompone get allGroupsHaveWs() { return this.groupsWithoutWorkspaces.length === 0; } - @service store; - @service router; - @service('sweet-alert') alert; - @service('assignment-permissions') permissions; - @service('utility-methods') utils; get hasLinkedWorkspaces() { return this.args.assignment.linkedWorkspaces.length > 0; diff --git a/app/components/problem-info.js b/app/components/problem-info.js index 0825aee79..c830052c1 100644 --- a/app/components/problem-info.js +++ b/app/components/problem-info.js @@ -6,6 +6,11 @@ import { inject as service } from '@ember/service'; import $ from 'jquery'; export default class ProblemInfoComponent extends ErrorHandlingComponent { + @service('sweet-alert') alert; + @service('problem-permissions') permissions; + @service('utility-methods') utils; + @service store; + @service router; @tracked isEditing = false; @tracked showGeneral = true; @tracked problemName = null; @@ -32,11 +37,6 @@ export default class ProblemInfoComponent extends ErrorHandlingComponent { @tracked showAdditional = false; @tracked showLegal = false; @tracked categoryTree = {}; - @service('sweet-alert') alert; - @service('problem-permissions') permissions; - @service('utility-methods') utils; - @service store; - @service router; @tracked copyrightNotice = ''; @tracked sharingAuth = ''; @tracked author = ''; diff --git a/app/routes/application.js b/app/routes/application.js index 6bb87f802..912c79ffa 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -13,11 +13,12 @@ import { action } from '@ember/object'; export default class Application extends Route { //the application route can't require authentication since it's getting the user - @service('user-ntfs') userNtfs; + @service userNtfs; @service store; - @service('workspace-permissions') workspacePermissions; - @service('edit-permissions') editPermissions; - @service('current-user') currentUser; + @service router; + @service workspacePermissions; + @service editPermissions; + @service currentUser; beforeModel() { let that = this; window.addEventListener( @@ -62,11 +63,11 @@ export default class Application extends Route { // should be extending AuthenticatedRoute. if (!user.get('isAuthenticated')) { this.store.unloadAll(); - this.transitionTo('welcome'); + this.router.transitionTo('welcome'); } else if (!user.get('isEmailConfirmed') && !user.get('isStudent')) { - this.transitionTo('unconfirmed'); + this.router.transitionTo('unconfirmed'); } else if (!user.get('isAuthz')) { - this.transitionTo('unauthorized'); + this.router.transitionTo('unauthorized'); } } diff --git a/app/routes/assignments/assignment.js b/app/routes/assignments/assignment.js index 4e0336d04..82ec09bce 100644 --- a/app/routes/assignments/assignment.js +++ b/app/routes/assignments/assignment.js @@ -4,6 +4,7 @@ import { hash } from 'rsvp'; import { inject as service } from '@ember/service'; export default class AssignmentsAssignmentRoute extends AuthenticatedRoute { @service store; + @service router; async model(params) { let currentUser = this.modelFor('application'); const assignment = await this.store.findRecord( @@ -27,6 +28,6 @@ export default class AssignmentsAssignmentRoute extends AuthenticatedRoute { }); } @action toAssignments() { - this.transitionTo('assignments'); + this.router.transitionTo('assignments'); } } diff --git a/app/routes/assignments/new.js b/app/routes/assignments/new.js index eb8ad1d41..8a148f7a8 100644 --- a/app/routes/assignments/new.js +++ b/app/routes/assignments/new.js @@ -4,12 +4,13 @@ import AuthenticatedRoute from '../_authenticated_route'; import { inject as service } from '@ember/service'; export default class AssignmentsNewRoute extends AuthenticatedRoute { @service store; + @service router; beforeModel() { const user = this.modelFor('application'); const isStudent = user.get('isStudent'); if (isStudent) { - this.transitionTo('assignments'); + this.router.transitionTo('assignments'); } } async model() { @@ -22,9 +23,9 @@ export default class AssignmentsNewRoute extends AuthenticatedRoute { }); } @action toAssignmentInfo(model) { - this.transitionTo('assignment', model); + this.router.transitionTo('assignment', model); } @action toAssignmentsHome() { - this.transitionTo('assignments'); + this.router.transitionTo('assignments'); } } diff --git a/app/routes/folders_index.js b/app/routes/folders_index.js index a17c88c26..024c757f1 100644 --- a/app/routes/folders_index.js +++ b/app/routes/folders_index.js @@ -4,15 +4,17 @@ * @since 1.0.0 */ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; export default class FoldersIndexRoute extends Route { + @service router; model() { return this.store.findAll('folder'); } afterModel(folders, transition) { if (folders.length === 1) { - this.transitionTo('workspaceFolder', folders.firstObject); + this.router.transitionTo('workspaceFolder', folders.firstObject); } } } diff --git a/app/routes/problems/new.js b/app/routes/problems/new.js index 1fbd7f8ae..289212b40 100644 --- a/app/routes/problems/new.js +++ b/app/routes/problems/new.js @@ -5,6 +5,7 @@ import AuthenticatedRoute from '../_authenticated_route'; export default class ProblemsNewRoute extends AuthenticatedRoute { @service store; + @service router; model() { let currentUser = this.modelFor('application'); return hash({ @@ -15,9 +16,9 @@ export default class ProblemsNewRoute extends AuthenticatedRoute { } @action toProblemInfo(problem) { - this.transitionTo('problems.problem', problem.id); + this.router.transitionTo('problems.problem', problem.id); } @action toProblemList() { - this.transitionTo('problems'); + this.router.transitionTo('problems'); } } diff --git a/app/routes/responses.js b/app/routes/responses.js index ef0c88aa7..e4230374a 100644 --- a/app/routes/responses.js +++ b/app/routes/responses.js @@ -1,15 +1,16 @@ import AuthenticatedRoute from './_authenticated_route'; import { action } from '@ember/object'; - +import { inject as service } from '@ember/service'; export default class ResponsesRoute extends AuthenticatedRoute { + @service router; @action toSubmissionResponse(subId) { - this.transitionTo('responses.submission', subId); + this.router.transitionTo('responses.submission', subId); } @action toResponses() { this.refresh(); } @action toResponse(submissionId, responseId) { - this.transitionTo('responses.submission', submissionId, { + this.router.transitionTo('responses.submission', submissionId, { queryParams: { responseId: responseId }, }); } diff --git a/app/routes/responses/new/submission.js b/app/routes/responses/new/submission.js index ed49f78e6..9cdc26645 100644 --- a/app/routes/responses/new/submission.js +++ b/app/routes/responses/new/submission.js @@ -9,6 +9,7 @@ export default class ResponsesNewSubmissionRoute extends Route.extend( ) { @service('utility-methods') utils; @service store; + @service router; renderTemplate() { this.render('responses/response'); } @@ -130,18 +131,18 @@ export default class ResponsesNewSubmissionRoute extends Route.extend( afterModel(model) { if (model.isDraft) { - this.transitionTo('responses.submission', model.submissionId, { + this.router.transitionTo('responses.submission', model.submissionId, { queryParams: { responseId: model.responseId }, }); } } @action toResponse(submissionId, responseId) { - this.transitionTo('responses.submission', submissionId, { + this.router.transitionTo('responses.submission', submissionId, { queryParams: { responseId: responseId }, }); } @action toResponseSubmission(subId) { - this.transitionTo('responses.submission', subId); + this.router.transitionTo('responses.submission', subId); } } diff --git a/app/routes/responses/submission.js b/app/routes/responses/submission.js index 7318894d4..064246382 100644 --- a/app/routes/responses/submission.js +++ b/app/routes/responses/submission.js @@ -5,6 +5,7 @@ import { action } from '@ember/object'; export default class ResponsesRoute extends AuthenticatedRoute { @service('utility-methods') utils; @service store; + @service router; queryParams = { responseId: { refreshModel: true, @@ -86,26 +87,26 @@ export default class ResponsesRoute extends AuthenticatedRoute { redirect(model, transition) { if (!model) { - this.transitionTo('responses'); + this.router.transitionTo('responses'); } } @action toResponseSubmission(subId) { - this.transitionTo('responses.submission', subId); + this.router.transitionTo('responses.submission', subId); } @action toResponse(submissionId, responseId) { - this.transitionTo('responses.submission', submissionId, { + this.router.transitionTo('responses.submission', submissionId, { queryParams: { responseId: responseId }, }); } @action toResponses() { - this.transitionTo('responses'); + this.router.transitionTo('responses'); } @action toNewResponse(submissionId, workspaceId) { - this.transitionTo('responses.new.submission', submissionId, { + this.router.transitionTo('responses.new.submission', submissionId, { queryParams: { workspaceId: workspaceId }, }); } diff --git a/app/routes/sections/new.js b/app/routes/sections/new.js index 1dff6df38..40fbc3b16 100644 --- a/app/routes/sections/new.js +++ b/app/routes/sections/new.js @@ -4,12 +4,13 @@ import { inject as service } from '@ember/service'; export default class SectionsNewRoute extends AuthenticatedRoute { @service store; + @service router; beforeModel() { const user = this.modelFor('application'); const isStudent = user.get('isStudent'); if (isStudent) { - this.transitionTo('sections'); + this.router.transitionTo('sections'); } } diff --git a/app/routes/sections/section.js b/app/routes/sections/section.js index 2fdb8a271..59d20db75 100644 --- a/app/routes/sections/section.js +++ b/app/routes/sections/section.js @@ -5,6 +5,7 @@ import { inject as service } from '@ember/service'; export default class SectionsSectionRoute extends AuthenticatedRoute { @service store; + @service router; async model(params) { let section = await this.store.findRecord('section', params.section_id); let groups = await this.store.query('group', { @@ -23,10 +24,10 @@ export default class SectionsSectionRoute extends AuthenticatedRoute { } @action toSectionList() { - this.transitionTo('sections'); + this.router.transitionTo('sections'); } @action toAssignmentInfo(assignment) { - this.transitionTo('assignments.assignment', assignment); + this.router.transitionTo('assignments.assignment', assignment); } @action refreshModel() { this.refresh(); diff --git a/app/routes/submission/response.js b/app/routes/submission/response.js index a6fd3953d..f9659d480 100644 --- a/app/routes/submission/response.js +++ b/app/routes/submission/response.js @@ -4,33 +4,34 @@ import { action } from '@ember/object'; export default class ResponsesRoute extends ConfirmLeavingRoute { @service('utility-methods') utils; @service store; + @service router; model(params) { return this.store.findRecord('response', params.response_id); } redirect(model, transition) { if (!model) { - this.transitionTo('responses'); + this.router.transitionTo('responses'); } else { let submissionId = this.utils.getBelongsToId(model, 'submission'); if (this.utils.isValidMongoId(submissionId)) { - this.transitionTo('responses.submission', submissionId, { + this.router.transitionTo('responses.submission', submissionId, { queryParams: { responseId: model.get('id') }, }); } else { - this.transitionTo('responses'); + this.router.transitionTo('responses'); } } } @action toResponseInfo(response) { - this.transitionTo('response', response.get('id')); + this.router.transitionTo('response', response.get('id')); } @action toResponses() { - this.transitionTo('responses'); + this.router.transitionTo('responses'); } @action toNewResponse(submissionId, workspaceId) { - this.transitionTo('responses.new.submission', submissionId, { + this.router.transitionTo('responses.new.submission', submissionId, { queryParams: { workspaceId: workspaceId }, }); } diff --git a/app/routes/submissions/first.js b/app/routes/submissions/first.js index 67c8ecf09..af73e7cbf 100644 --- a/app/routes/submissions/first.js +++ b/app/routes/submissions/first.js @@ -10,6 +10,7 @@ import { inject as service } from '@ember/service'; export default Route.extend({ utils: service('utility-methods'), alert: service('sweet-alert'), + router: service(), model: function () { return this.modelFor('workspace.submissions'); @@ -22,7 +23,7 @@ export default Route.extend({ let firstStudent = sorted.get('firstObject.student'); let lastRevision = sorted.getEach('student').lastIndexOf(firstStudent); - this.transitionTo( + this.router.transitionTo( 'workspace.submissions.submission', workspace, sorted.objectAt(lastRevision).get('id') @@ -38,7 +39,7 @@ export default Route.extend({ null ); - this.transitionTo('workspace.info'); + this.router.transitionTo('workspace.info'); } }, }); diff --git a/app/routes/submissions/submission.js b/app/routes/submissions/submission.js index 72a70d21e..3858769c5 100644 --- a/app/routes/submissions/submission.js +++ b/app/routes/submissions/submission.js @@ -18,6 +18,7 @@ import VmtHostMixin from '../mixins/vmt-host'; export default Route.extend(CurrentUserMixin, VmtHostMixin, { alert: service('sweet-alert'), utils: service('utility-methods'), + router: service(), queryParams: 'vmtRoomId', @@ -39,9 +40,13 @@ export default Route.extend(CurrentUserMixin, VmtHostMixin, { // so links to selections still work if (transition.intent.name === 'workspace.submissions.submission') { - this.transitionTo('workspace.submissions.submission', submission, { - queryParams: { vmtRoomId }, - }); + this.router.transitionTo( + 'workspace.submissions.submission', + submission, + { + queryParams: { vmtRoomId }, + } + ); } }); }, diff --git a/app/routes/unauthorized.js b/app/routes/unauthorized.js index 96857a9a5..3a49aa925 100644 --- a/app/routes/unauthorized.js +++ b/app/routes/unauthorized.js @@ -4,12 +4,13 @@ import { action } from '@ember/object'; export default class UnauthorizedRoute extends Route { @service store; + @service router; beforeModel() { // redirect to login if no user logged in const user = this.modelFor('application'); if (!user || !user.get('isAuthenticated')) { - return this.transitionTo('auth.login'); + return this.router.transitionTo('auth.login'); } // redirect to confirm email info page if // email still needs confirming @@ -19,12 +20,12 @@ export default class UnauthorizedRoute extends Route { !user.get('isStudent'); if (doesEmailNeedConfirming) { - return this.transitionTo('unconfirmed'); + return this.router.transitionTo('unconfirmed'); } // redirect to home page if already authorized if (user.get('isAuthz')) { - this.transitionTo('/'); + this.router.transitionTo('/'); } } diff --git a/app/routes/unconfirmed.js b/app/routes/unconfirmed.js index 21ef0706e..5f443a839 100644 --- a/app/routes/unconfirmed.js +++ b/app/routes/unconfirmed.js @@ -1,17 +1,18 @@ import Route from '@ember/routing/route'; - +import { inject as service } from '@ember/service'; export default class UnconfirmedRoute extends Route { + @service router; beforeModel() { // redirect to login if no user logged in const user = this.modelFor('application'); if (!user || !user.get('isAuthenticated')) { - return this.transitionTo('auth.login'); + return this.router.transitionTo('auth.login'); } // redirect to home page if email is already confirmed or user does not have an email if (user.get('isEmailConfirmed') || !user.get('email')) { - this.transitionTo('/'); + this.router.transitionTo('/'); } } } diff --git a/app/routes/users.js b/app/routes/users.js index 1e7e6b152..10b203f1c 100644 --- a/app/routes/users.js +++ b/app/routes/users.js @@ -11,12 +11,13 @@ import { inject as service } from '@ember/service'; export default class UsersRoute extends Route { @service store; + @service router; beforeModel() { const user = this.modelFor('application'); const isStudent = user.get('isStudent'); if (isStudent) { - this.transitionTo('/'); + this.router.transitionTo('/'); } } async model() { diff --git a/app/routes/vmt/import.js b/app/routes/vmt/import.js index 63f472f7b..856467191 100644 --- a/app/routes/vmt/import.js +++ b/app/routes/vmt/import.js @@ -1,8 +1,10 @@ import { hash } from 'rsvp'; import AuthenticatedRoute from '../_authenticated_route'; +import { inject as service } from '@ember/service'; export default AuthenticatedRoute.extend({ controllerName: 'vmt-import', + router: service(), model() { return hash({ @@ -13,7 +15,7 @@ export default AuthenticatedRoute.extend({ actions: { toWorkspaces: function (workspaceId) { - this.transitionTo('workspace.work', workspaceId); + this.router.transitionTo('workspace.work', workspaceId); // window.location.href = `#/workspaces/${workspace._id}/submissions/${workspace.submissions[0]}`; }, }, diff --git a/app/routes/workspace/submissions/submission.js b/app/routes/workspace/submissions/submission.js index 4ee987996..76702ce60 100644 --- a/app/routes/workspace/submissions/submission.js +++ b/app/routes/workspace/submissions/submission.js @@ -11,12 +11,12 @@ import Route from '@ember/routing/route'; import { schedule } from '@ember/runloop'; import { hash, resolve } from 'rsvp'; import { inject as service } from '@ember/service'; -import $ from 'jquery'; import { action } from '@ember/object'; export default class WorkspaceSubmissionRoute extends Route { @service sweetAlert; @service('utility-methods') utils; @service currentUser; + @service router; queryParams = { vmtRoomId: { @@ -44,9 +44,13 @@ export default class WorkspaceSubmissionRoute extends Route { // so links to selections still work if (transition.intent.name === 'workspace.submissions.submission') { - this.transitionTo('workspace.submissions.submission', submission, { - queryParams: { vmtRoomId }, - }); + this.router.transitionTo( + 'workspace.submissions.submission', + submission, + { + queryParams: { vmtRoomId }, + } + ); } }); } diff --git a/app/routes/workspaces.js b/app/routes/workspaces.js index 1182eb171..5ec6ed47a 100644 --- a/app/routes/workspaces.js +++ b/app/routes/workspaces.js @@ -7,11 +7,13 @@ import AuthenticatedRoute from './_authenticated_route'; import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; export default class WorkspacesRoute extends AuthenticatedRoute { + @service router; @action toCopyWorkspace(workspace) { let workspaceId = workspace.get('id'); - this.transitionTo('workspaces.copy', { + this.router.transitionTo('workspaces.copy', { queryParams: { workspace: workspaceId }, }); } diff --git a/app/routes/workspaces/copy.js b/app/routes/workspaces/copy.js index 6d6bf9e5f..f00117477 100644 --- a/app/routes/workspaces/copy.js +++ b/app/routes/workspaces/copy.js @@ -5,6 +5,7 @@ import AuthenticatedRoute from '../_authenticated_route'; export default class WorkspacesCopyRoute extends AuthenticatedRoute { @service store; + @service router; workspaceToCopy = null; workspaceId = null; beforeModel(transition) { @@ -23,6 +24,6 @@ export default class WorkspacesCopyRoute extends AuthenticatedRoute { } @action toWorkspace(id) { - this.transitionTo('workspace.work', id); + this.router.transitionTo('workspace.work', id); } } diff --git a/app/routes/workspaces/new.js b/app/routes/workspaces/new.js index 65838ccd7..519ac56f5 100644 --- a/app/routes/workspaces/new.js +++ b/app/routes/workspaces/new.js @@ -5,12 +5,13 @@ import AuthenticatedRoute from '../_authenticated_route'; export default class WorkspacesNewRoute extends AuthenticatedRoute { @service store; + @service router; beforeModel() { const user = this.modelFor('application'); const isStudent = user.get('isStudent'); if (isStudent) { - this.transitionTo('/'); + this.router.transitionTo('/'); } } model() { @@ -27,10 +28,10 @@ export default class WorkspacesNewRoute extends AuthenticatedRoute { } // Created workspaceId and is passed from component to redirect @action toWorkspaces(id) { - this.transitionTo('workspace.work', id); + this.router.transitionTo('workspace.work', id); } @action toWorkspace(id) { - this.transitionTo('workspace/work', id); + this.router.transitionTo('workspace/work', id); } } From a663e2e4616ade6208711403b6e09cef7d21b237 Mon Sep 17 00:00:00 2001 From: Irv Katz <60225276+exidy80@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:22:32 -0500 Subject: [PATCH 05/35] cleaned up a few more deprecations in models --- UPGRADE_NOTES.md | 10 +++++++++- app/models/folder.js | 2 +- app/models/tagging.js | 6 +++--- app/models/workspace.js | 2 +- app/routes/index.js | 30 ++++++++++++++++-------------- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/UPGRADE_NOTES.md b/UPGRADE_NOTES.md index 50f7261e4..dd8122c58 100644 --- a/UPGRADE_NOTES.md +++ b/UPGRADE_NOTES.md @@ -75,7 +75,11 @@ In Ember 4.5, helpers can now be regular functions rather than wrapped in a mana ## Controllers -Controllers are slated to be deprecated. Best practices are to replace them with the use of components -- the idea is that route templates reference components that contain work that had been done by controllers. +Controllers are slated to be deprecated. Best practice is to refactor the logic and properties in the controller, distributing them as appropriate to the route (for building the model), a service (for application state that will be used elsewhere), and a component (for everything else). The idea is that a route templates should be simple and reference just one or moe components that contain much of the work that had been done by the controller. + +## Route Templates + +Route templates should refer to the model via @model rather than this.model as per the Ember Octane upgrade guide. ## EmberTable @@ -92,6 +96,10 @@ The current code includes several subsystems of components that are tightly coup # Possible future upgrades +## Model definitions + +All hasMany and belowsTo relationships should specify inverse and async options explicitly. Not doing so is deprecated. + ## DB document timestamps Currently, all timestamping of db documents (users, problems, workspaces, etc.) appears to be done manually primarily on the client side. This approach could cause issues because the clients clocks might be wrong. Also, because the dates are updated manually (all over the codebase), there is a higher likelihood of errors. diff --git a/app/models/folder.js b/app/models/folder.js index 9ad85c21f..388a62ab2 100644 --- a/app/models/folder.js +++ b/app/models/folder.js @@ -4,7 +4,7 @@ import AuditableModel from './auditable'; export default class FolderModel extends AuditableModel { @attr('string') name; @attr('number') weight; - @hasMany('tagging', { async: true }) taggings; + @hasMany('tagging', { inverse: 'folder', async: true }) taggings; @belongsTo('folder', { inverse: 'children', async: true }) parent; @hasMany('folder', { inverse: 'parent', async: true }) children; @belongsTo('workspace', { async: true }) workspace; diff --git a/app/models/tagging.js b/app/models/tagging.js index 8d4d4c259..3d265e6a4 100644 --- a/app/models/tagging.js +++ b/app/models/tagging.js @@ -3,9 +3,9 @@ import { belongsTo } from '@ember-data/model'; import AuditableModel from './auditable'; export default class TaggingModel extends AuditableModel { - @belongsTo('workspace', { async: false }) workspace; - @belongsTo('selection', { async: true }) selection; - @belongsTo('folder', { async: true }) folder; + @belongsTo('workspace', { inverse: 'taggings', async: false }) workspace; + @belongsTo('selection', { inverse: 'taggings', async: true }) selection; + @belongsTo('folder', { inverse: 'taggings', async: true }) folder; @belongsTo('tagging', { inverse: null, async: true }) originalTagging; copy() { diff --git a/app/models/workspace.js b/app/models/workspace.js index bd3a06d9a..ab967673e 100644 --- a/app/models/workspace.js +++ b/app/models/workspace.js @@ -12,7 +12,7 @@ export default class WorkspaceModel extends AuditableModel { @belongsTo('user', { async: true }) owner; @hasMany('user', { async: true }) editors; @hasMany('folder', { async: true }) folders; - @attr() group; + @belongsTo('group', { inverse: null, async: true }) group; @hasMany('submission', { async: true }) submissions; @hasMany('response', { async: true }) responses; @hasMany('selection', { async: true }) selections; diff --git a/app/routes/index.js b/app/routes/index.js index 689611b5a..d81d0eaa9 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -32,21 +32,23 @@ export default class IndexRoute extends Route { const user = this.currentUser.user; const sections = await this.store.findAll('section'); - const teacherSections = sections.filter((section) => - section.teachers.includes(user) - ); - - const studentSections = sections.filter((section) => - section.students.includes(user) - ); - - const teacherAssignments = teacherSections.flatMap((section) => - section.assignments.toArray() - ); + const teacherAssignments = ( + await Promise.all( + sections.map(async (section) => { + const teachers = await section.teachers; + return teachers.includes(user) ? section.assignments.toArray() : []; + }) + ) + ).flat(); - const studentAssignments = studentSections.flatMap((section) => - section.assignments.toArray() - ); + const studentAssignments = ( + await Promise.all( + sections.map(async (section) => { + const students = await section.students; + return students.includes(user) ? section.assignments.toArray() : []; + }) + ) + ).flat(); const teacherClasses = user.sections.map((section) => section.sectionId); From 186e946a893a346b05b627551ddd33e1f185f318 Mon Sep 17 00:00:00 2001 From: Irv Katz <60225276+exidy80@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:39:00 -0500 Subject: [PATCH 06/35] move radiogroup to ui folder --- app/components/assignment-new.hbs | 8 +- app/components/parent-ws-collab-new.hbs | 4 +- app/components/{ => ui}/radio-group-item.hbs | 0 app/components/{ => ui}/radio-group.hbs | 2 +- app/components/{ => ui}/radio-group.js | 0 .../workspace-info-collaborators-new.hbs | 2 +- .../workspace-info-collaborators.hbs | 18 +- .../components/import-work-step2.hbs | 2 +- .../components/import-work-step5.hbs | 257 ++++++++++++------ .../components/new-folderset-form.hbs | 52 +++- .../components/workspace-new-settings.hbs | 4 +- app/templates/components/ws-copy-config.hbs | 49 +++- .../components/ws-copy-owner-settings.hbs | 2 +- .../ws-new-settings-permissions.hbs | 2 +- .../components/ws-permissions-new.hbs | 105 +++++-- 15 files changed, 352 insertions(+), 155 deletions(-) rename app/components/{ => ui}/radio-group-item.hbs (100%) rename app/components/{ => ui}/radio-group.hbs (96%) rename app/components/{ => ui}/radio-group.js (100%) diff --git a/app/components/assignment-new.hbs b/app/components/assignment-new.hbs index bcc3cb5db..2ae0094ab 100644 --- a/app/components/assignment-new.hbs +++ b/app/components/assignment-new.hbs @@ -200,7 +200,7 @@
- - - -
- -
  • -
  • -
    {{/if}} @@ -115,7 +117,9 @@
    {{#if - (not (is-equal collaborator.userObj this.selectedCollaborator)) + (not + (is-equal collaborator.userObj this.selectedCollaborator) + ) }}
    Permissions Type @@ -125,7 +129,7 @@ {{#if (is-equal collaborator.userObj this.selectedCollaborator) }} -
    {{/if}} diff --git a/app/templates/components/import-work-step2.hbs b/app/templates/components/import-work-step2.hbs index d40bdd5b3..ee11aba4f 100644 --- a/app/templates/components/import-work-step2.hbs +++ b/app/templates/components/import-work-step2.hbs @@ -7,7 +7,7 @@

    - +

    Would you like to create a new workspace? - - + +

    - + {{#if creatingWs}} -
    -
    -

    - Workspace Name - * - - - -

    -
    - -
    - {{#if missingNameError}} -
    - +
    +
    +

    + Workspace Name + * + + + +

    +
    + +
    + {{#if missingNameError}} +
    + +
    + {{/if}}
    - {{/if}} -
    - -
    -

    - Workspace Owner - * +

    +

    + Workspace Owner + * - - - -

    - - {{#if missingOwnerError}} -
    - + + + +

    + + {{#if missingOwnerError}} +
    + +
    + {{/if}}
    - {{/if}} -
    - -
    -

    - Privacy Setting - - - -

    - -
    +
    +

    + Privacy Setting + + + +

    + +
    -
    -

    - Folder Set - - - -

    - -
    -

    You can add collaborators and change workspace settings after creating the workspace

    +
    +

    + Folder Set + + + +

    + +
    +

    You can add collaborators and change workspace settings + after creating the workspace

    - {{/if}} +{{/if}} - {{#unless this.currentUser.isStudent}} - {{#if selectedSection}} -
    -

    +{{#unless this.currentUser.isStudent}} + {{#if selectedSection}} +

    +

    Would you like to save this work as an assignment? - - + +

    - +
    - {{/if}} - {{/unless}} + {{/if}} +{{/unless}} - {{#if creatingAssignment}} -
    -
    -

    +{{#if creatingAssignment}} +

    +
    +

    Assignment Name - - + +

    -
    - +
    +
    {{#if missingAssignmentError}} -
    - -
    +
    + +
    {{/if}}
    - {{/if}} - - +{{/if}} -