MemberJunction / MemberJunction/MJ

`mj-tag-chips` bypasses `TagScopeFilterBuilder`: suggestions ignore scope and `Status`, and inline-created tags default to `IsGlobal = 1`

Open
#3,965 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

### What we're trying to achieve

We run a multi-tenant LXP on MemberJunction (**5.51.0**). Customer organizations each administer
their own content library, and we want them to be able to tag their own records with their own
taxonomy — while a shared, platform-curated set of tags remains visible to everyone. That is
exactly the model `Tag.IsGlobal` + `MJ: Tag Scopes` appears designed for, and
`guides/TAXONOMY_TAGGING_GUIDE.md` describes it that way.

We can't get there with `mj-tag-chips` today, and we'd rather ask than fork.

### The problem

`@memberjunction/ng-list-management` → `TagChipsComponent` (`mj-tag-chips`) has no notion of
scope. Its entire input surface is `EntityName`, `RecordID`, `Editable`, `MaxDisplay`.

Two consequences in a multi-tenant deployment, both from
`dist/lib/components/tag-chips/tag-chips.component.js` in 5.51.0:

**1. Newly created tags are global.** The "Create ''" path sets only `Name` and
`DisplayName`:

```js
const tag = await md.GetEntityObject('MJ: Tags', ...);
tag.NewRecord();
tag.Name = name;
tag.DisplayName = name;
const ok = await tag.Save();
```

`IsGlobal` is never set, and `__mj.Tag` has `CONSTRAINT [DF_Tag_IsGlobal] DEFAULT ((1))`. So
every tag any tenant types lands in the shared vocabulary as global. `Status` is likewise unset.
No `MJ: Tag Scopes` row is created, so there is no way to express "this tag belongs to tenant X"
even after the fact.

**2. Suggestions span the whole instance.** The autocomplete is unfiltered:

```js
ExtraFilter: `Name LIKE '%${escaped}%'`
```

No `IsGlobal` predicate, no scope predicate, no `Status` filter. So tenant B's admin typing into
the tag box sees tenant A's tag names. For us that is a real disclosure — a tag like
`acme-merger-onboarding` is internal language, and it surfaces in an unrelated customer's
dropdown.

### Why this reads as a gap rather than a design choice

**MJ already ships the entire mechanism — this component just doesn't call it.**
`@memberjunction/tag-engine-base` exposes `TagScopeFilterBuilder`, whose stated visibility rule
is exactly what we want:

> a tag is visible iff `Status='Active' AND (IsGlobal=1 OR ID IN (SELECT TagID FROM TagScope WHERE …))`

and `guides/TAXONOMY_TAGGING_GUIDE.md` describes `MJ: Tag Scopes` as:

> Polymorphic visibility rows for a non-global tag. A tag with one or more scope rows is visible
> *only* inside those `(Entity, Record)` scopes.

So the data model, the filter builder and the documentation all agree. `mj-tag-chips` uses none
of them: it neither passes a `TagScopeContext` when suggesting nor writes `IsGlobal` / a
`TagScope` row when creating.

Worth noting the model is **opt-in by design** — `TagScopeFilterBuilder`'s own docstring says
"an empty context returns everything Active (no scope filtering at all)". That is a reasonable
default for a single-tenant instance, but it means a consumer that simply doesn't participate
gets instance-wide behaviour silently, with nothing to signal it. That is what happened here.

**A `Status` bug falls out of the same cause, independent of tenancy:** the builder always emits
`Status='Active'`, but the component's raw `Name LIKE '%…%'` has no `Status` predicate, so it
suggests inactive tags — in single-tenant deployments too.

We understand the component is deliberately lightweight ("not a tag manager", per its own
docstring, pointing to `mj-record-tags` for richer tooling). This isn't a request to make it a
tag manager — it's that the *inline create* it does offer has an instance-wide side effect with
no way to opt out.

### What would unblock us

Any of these, in rough order of preference:

1. **A `TagScopeContext` input**, passed straight to `TagScopeFilterBuilder` for suggestions,
and used on create to write `IsGlobal = 0` plus the matching `MJ: Tag Scopes` row. Since both
the context type and the builder already exist, this looks like wiring rather than design.
2. **An `[AllowCreate]` input** (default `true` for compatibility). Setting it `false` would let
us keep attach/detach while routing tag creation through our own curated path. This alone
would unblock us and looks like the smallest change.
3. **Documented guidance** that `mj-tag-chips` is single-tenant-only and multi-tenant consumers
should build their own chips against the `TagScope` model — we'd take that as an answer and
stop trying to use it.

We're happy to contribute a PR for (1) or (2) if you can confirm the intended shape — mainly we
don't want to guess at how `TagScope` is meant to be written and diverge from your model.

### Minor, while you're in the file

The suggestion query uses `ResultType: 'entity_object'` for what is a read-only autocomplete of
at most 8 rows; `'simple'` with a narrowed `Fields` list would be cheaper on a large tag table.
(Adopting `TagScopeFilterBuilder` would fix the missing `Status='Active'` predicate at the same
time, since the builder emits it unconditionally.)

### Environment

- MemberJunction **5.51.0** (`@memberjunction/ng-list-management@5.51.0`)
- SQL Server, Angular consumer app
- Multi-tenant: many customer organizations in one instance, tenant isolation enforced at the
application layer

Contributor guide

Open the contributing guide

Research direction

Start with dist/lib/components/tag-chips/tag-chips.component.js and review TagScopeFilterBuilder alongside guides/TAXONOMY_TAGGING_GUIDE.md. Confirm the intended input and creation behavior with maintainers before changing the component. Done means suggestions honor Active status and scope visibility, while inline creation either writes the requested scoped tag data or can be disabled as agreed.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, sql, typescript
Domain
database, frontend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.