Affiliation-extras plugin: add Affiliation Catalogs#6
Conversation
3a9c0ea to
d3959e6
Compare
* Add invite affiliations tab Move AffiliationInvitations definition to another file Improve AddItemsModal UI Migrate invite dialog to use AffiliationListField Replaces the regform-endpoint-based AffiliationsTab with AffiliationInvitations, which delegates affiliation selection to the existing AffiliationListField component. AddItemsModal is refactored from a generic data-URL modal to an affiliation-specific search modal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Show user count per affiliation in AddItemsModal search results Extends the affiliations search API to include user_count per affiliation. AddItemsModal gains a showInviteCount prop that, when enabled, displays the registration count per result item and a running total of registrations for newly added affiliations in the footer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Fix send button to show correct invitation count Adds a backend endpoint that counts unique users across the selected affiliations, groups, and tags (deduplicating users who belong to multiple entries). AffiliationListField fetches this count asynchronously and stores it in _userCount so the synchronous getCount callback in AffiliationInvitations can return the accurate value for the send confirmation dialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Improve ui Self review minor improvements Turn the results column in a ternary expression Decouple userCount from AffiliationListField and AddItemsModal Disable search button if no search input Change UserBasicSchema for BasicUserSchema Remove comments Use resolve_affiliations when necessary Rename affiliationInvitations Move AddItemsModal.jsx Fix rebase problems Convert AddItemsModal from JS to TS Remove email attachments comments Improve CSS for AddItemsModal.tsx Small refactor Self review fixes * Small refactor to follow indico patterns * Improve tags * Refactor useEffect and variable names * Add AddAffiliationsModal scss module * Add AffiliationListField scss module * Improve css styles * Run biomejs * Fix affiliation invitation schema import * Fix conditional hook * Add compatibility shim * Apply code style improvements --------- Co-authored-by: Duarte Galvão <duarte.galvao@unconventional.dev>
1d1223e to
617ad71
Compare
| <TagsItems tags={tags} groupTags={groupTags} /> | ||
| {affiliationCount > 0 && ( | ||
| <Popup | ||
| content={Translate.string('Affiliations')} |
There was a problem hiding this comment.
I'd be good to list the affiliation names in this popup rather than the static Affiliations label. value.affiliations already carries them (CatalogListField only passes .length), so it'd mirror the group/tag hover. Worth capping for long lists, since See affiliations already shows the full resolved set.
* test: mock plugin manifest so manage pages render in tests * fix: keep unchanged representation value valid after catalog changes * perf: resolve inherited default catalogs without per-ancestor queries * fix: reject duplicate affiliation list names in a catalog * refactor: register all models from the package init * fix: handle failed affiliation resolve without crashing the list * fix: use stable keys for unsaved catalog list rows * fix: correct invalid contact emails warning condition
There was a problem hiding this comment.
A few things that don't map to a single line:
- No controller tests. The access matrix (event/category × method × role) is the biggest gap;
receipts/controllers/access_test.pyis a good template. schemas_test.pystubssys.modulesand skips the catalog schemas entirely, so the catalog args + the uniqueness validators (the headline feature) are untested.- State/props are snake_case while one endpoint opts into
camelize. Core camelizes at the boundary; worth picking one.
| return jsonify(count=count) | ||
|
|
||
|
|
||
| class RHInviteByAffiliation(RHAdminBase): |
There was a problem hiding this comment.
Should this be admin-only? A regform manager who is not also a site admin then cannot invite by affiliation.
| invitation_list=RegistrationInvitationSchema(many=True).dump(invitations), | ||
| ) | ||
|
|
||
| def _get_allowed_sender_emails(self, *, for_sending=False): |
There was a problem hiding this comment.
Could this reuse the event-level sender validation instead of duplicating it?
| model = AffiliationCatalog | ||
| fields = ('id', 'name', 'owner', 'lists') | ||
|
|
||
| owner = fields.Nested(OwnerDataSchema) |
There was a problem hiding this comment.
This loads users one affiliation at a time; a single batched query would help, though for the all-affiliations endpoint that would pull every user in the system.
| return jsonify(url=file.signed_download_url) | ||
| @use_kwargs({'file': fields.Raw(required=True, data_key='upload')}, location='files') | ||
| def _process(self, file): | ||
| response, __ = UploadFileMixin._process.__wrapped__(self, file) |
There was a problem hiding this comment.
This reaches into a wrapped internal; could it call the public save helper directly? The refresh just below also looks unnecessary.
| log_fields[key] = {'title': f'List: {name} - {title}', 'type': type_} | ||
| return changes, log_fields | ||
|
|
||
|
|
There was a problem hiding this comment.
This looks almost identical to the contact-list change logging above; could the two share a helper?
|
|
||
| blueprint = IndicoPluginBlueprint('affiliation_extras', __name__) | ||
|
|
||
| _admin_prefix = '/api/admin/plugins/affiliation_extras' |
There was a problem hiding this comment.
Nit: the URL and endpoint naming here differs from core convention, so it might be worth aligning.
| nullable=False, | ||
| index=True, | ||
| ) | ||
| position = db.Column(db.Integer, nullable=False) |
There was a problem hiding this comment.
Could this default instead of requiring every caller to set it, the way core positioned models do?
| registerPluginObject(PLUGIN_NAME, 'invite-dialog-extra-modes', affiliationInvitations); | ||
|
|
||
| // Category management | ||
| import setupAffiliationCatalogs from './catalogs'; |
There was a problem hiding this comment.
Nit: this import could move up with the others rather than sitting below the code.
| const countURL = userCountByIdsURL({event_id: eventId, reg_form_id: regformId}); | ||
| const renderItemExtra = item => | ||
| item.extraInfo !== undefined | ||
| ? React.createElement( |
There was a problem hiding this comment.
Could this be plain JSX like the other components, rather than building the elements by hand?
| value.name.trim() || value.id != null ? setModalOpen('delete') : onDelete(); | ||
| const hasMembers = | ||
| value.groups.length > 0 || value.tags.length > 0 || value.affiliations.length > 0; | ||
| const isEnabled = value.is_enabled; |
There was a problem hiding this comment.
Nit: this inline width duplicates the stylesheet, so it could move into the SCSS module.
This PR contains a second version of the affiliation-extras plugin.
Changelog:
Depends on: