Skip to content

Task - Manage tags view (Editor/Auditor) #314

Closed
openedx/frontend-app-authoring
#3224

Description

@MaferMazu

Description

The idea is to implement in authoring what it looks like to have read-only permissions.

Approved design

https://www.figma.com/design/onU2END2OXaF7RRLWEHsZI/AuthZ---v2?node-id=9145-5836&t=JZXQnlvqwQjdT21C-4

Activity

  1. moved this to In Grooming in RBAC AuthZ Boardon Jun 2, 2026
  2. MaferMazu commented on Jun 3, 2026

    @MaferMazu
    ContributorAuthor

    When refining this card, take into account: openedx/frontend-app-authoring#3036

  3. bra-i-am commented on Jun 3, 2026

    @bra-i-am

    @MaferMazu, I have a question. These are blocked:

    Image Image

    Becasue:


    Hide "Manage Tags" actions for Course Auditor

    Permission used: courses.manage_tags (backend action string, course-scoped).
    Pattern: useCourseUserPermissions(courseId, permissions)

    Frontend bugs (see UI location 4)

    Bug A — canTagObject hardcoded to false for otherTaxonomies (ContentTagsDrawerHelper.jsx). The can_tag_object field from object_tags was available but unused.

    Bug B — The merge useEffect in ContentTagsDrawerHelper.jsx only merged staged tags into fetchedTaxonomies entries. When a taxonomy was in otherTaxonomies (taxonomy list empty), staged tags were discarded and never reached appliedContentTagsTree → "Add" appeared to work but nothing appeared on screen.

    Bug C — The tag selector <Select> in ContentTagsCollapsible.jsx was gated only on isEditMode, not on canTagObject. This showed an interactive selector for taxonomies where the user has no permission.


    Files to change

    1. src/authz/constants.ts

    export const COURSE_PERMISSIONS = {
      ...
      MANAGE_TAGS: 'courses.manage_tags',
    };

    2. src/authz/permissionHelpers.ts

    export const getTagsPermissions = (courseId: string) => ({
      canManageTags: {
        action: COURSE_PERMISSIONS.MANAGE_TAGS,
        scope: courseId,
      },
    });

    UI location 1 — Course Outline info sidebar (3-dot menu on Taxonomy Alignments section)

    File: src/course-outline/outline-sidebar/info-sidebar/CourseInfoSidebar.tsx

    Resolve permission in DetailsTab and make the actions array conditional:

    const DetailsTab = () => {
      const { courseId } = useCourseAuthoringContext();
      const { canManageTags } = useCourseUserPermissions(courseId, getTagsPermissions(courseId));
    
      ...
    
      return (
        <SidebarSection
          title={intl.formatMessage(messages.sidebarSectionTaxonomy)}
          icon={Tag}
          actions={canManageTags ? [{
            label: intl.formatMessage(messages.sidebarSectionTaxonomyManageTags),
            onClick: openManageTagsDrawer,
          }] : []}
        >
          <ContentTagsSnippet contentId={courseId} />
        </SidebarSection>
      );
    };

    UI location 2 — Course Outline Align sidebar ("Manage tags" button when unit selected)

    Files: OutlineAlignSidebar.tsx → AlignSidebar.tsx

    The "Manage tags" button is inside ContentTagsComponentVariantFooter, already gated on !readOnly. Only prop-threading is needed.

    src/course-outline/outline-sidebar/OutlineAlignSidebar.tsx — resolve and pass down:

    export const OutlineAlignSidebar = () => {
      const { courseId } = useCourseAuthoringContext();
      const { canManageTags } = useCourseUserPermissions(courseId, getTagsPermissions(courseId));
    
      return (
        <AlignSidebar
          title={...}
          contentId={sidebarContentId}
          onBackBtnClick={...}
          readOnly={!canManageTags}
        />
      );
    };

    src/generic/sidebar/AlignSidebar.tsx — add and forward readOnly:

    export interface AlignSidebarProps {
      contentId: string;
      title: string;
      onBackBtnClick?: () => void;
      readOnly?: boolean;
    }
    
    export const AlignSidebar = ({ contentId, title, onBackBtnClick, readOnly = false }: AlignSidebarProps) => (
      <div>
        <SidebarTitle title={title} icon={SchoolOutline} onBackBtnClick={onBackBtnClick} />
        <ContentTagsDrawer id={contentId} variant="component" readOnly={readOnly} />
      </div>
    );

    UI location 3 — Course Unit legacy sidebar ("Manage tags" button in Unit tags section)

    Files: LegacySidebar → TagsSidebarControls → TagsSidebarBody

    src/course-unit/legacy-sidebar/index.tsx — resolve and pass down:

    const LegacySidebar = ({ unitTitle, isUnitVerticalType, xBlocks, readOnly, isSplitTestType }) => {
      const { courseId } = useCourseAuthoringContext();
      const { canManageTags } = useCourseUserPermissions(courseId, getTagsPermissions(courseId));
    
      return (
        <Stack gap={3} className="px-4 mw-sm">
          {isUnitVerticalType && (
            <PluginSlot ...>
              {getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
                <SidebarSection className="tags-sidebar">
                  <TagsSidebarControls readOnly={readOnly} canManageTags={canManageTags} />
                </SidebarSection>
              )}
            </PluginSlot>
          )}
        </Stack>
      );
    };

    src/content-tags-drawer/tags-sidebar-controls/index.tsx — forward prop:

    interface TagsSidebarControlsProps {
      readOnly: boolean;
      canManageTags: boolean;
    }
    
    const TagsSidebarControls = ({ readOnly, canManageTags }: TagsSidebarControlsProps) => (
      <>
        <TagsSidebarHeader />
        <TagsSidebarBody readOnly={readOnly} canManageTags={canManageTags} />
      </>
    );

    src/content-tags-drawer/tags-sidebar-controls/TagsSidebarBody.tsx — hide button:

    interface TagsSidebarBodyProps {
      readOnly: boolean;
      canManageTags: boolean;
    }
    
    const TagsSidebarBody = ({ readOnly, canManageTags }: TagsSidebarBodyProps) => {
      ...
      return (
        <Card.Body ...>
          <Stack>
            ...
            {canManageTags && (
              <Button
                className="mt-3 ml-2"
                variant="outline-primary"
                size="sm"
                onClick={() => setShowManageTags(true)}
                disabled={readOnly}
              >
                {intl.formatMessage(messages.manageTagsButton)}
              </Button>
            )}
          </Stack>
        </Card.Body>
      );
    };

    UI location 4 — Content Tags Drawer: fix Add button for authz users (Bugs A, B, C)

    Files: content-tags-drawer/ContentTagsDrawerHelper.jsx → content-tags-drawer/ContentTagsCollapsible.jsx

    src/content-tags-drawer/ContentTagsDrawerHelper.jsx — use canTagObject from API instead of hardcoding false, and extend the staged-tag merge to cover otherTaxonomies:

    // Bug A fix — line ~113: use actual value from object_tags response
    otherTaxonomiesList.push({
      canTagObject: contentTaxonomyTags.canTagObject,  // was: false
      ...
    });
    
    // Bug B fix — line ~334: add else-if branch so staged tags are merged for otherTaxonomies too
    Object.keys(globalStagedContentTags).forEach((taxonomyId) => {
      if (mergedTags[taxonomyId]) {
        // existing logic (unchanged)
      } else if (mergedOtherTaxonomies[taxonomyId]) {
        const stagedLineages = globalStagedContentTags[taxonomyId].map((t) => t.lineage.slice(0, -1)).flat();
        const fetchedTags = mergedOtherTaxonomies[taxonomyId].contentTags.filter((t) => !stagedLineages.includes(t.value));
        mergedOtherTaxonomies[taxonomyId].contentTags = [
          ...fetchedTags,
          ...globalStagedContentTags[taxonomyId],
        ];
      }
    });

    src/content-tags-drawer/ContentTagsCollapsible.jsx — Bug C fix: gate the tag selector on canTagObject:

    // was: {isEditMode && (
    {isEditMode && canTagObject && (
      <Select ... />
    )}
  4. MaferMazu commented on Jun 5, 2026

    @MaferMazu
    ContributorAuthor

    @bra-i-am thanks for sharing the info. But I didn't understand the question.

    Regarding the taxonomy permission, we can ignore it because it is not implemented. For more info: openedx/openedx-platform#38222 and #309

    Regarding tagging the specific object (in this case, a course). Do we have issues here? Is there something you need that isn't implemented?

  5. bra-i-am commented on Jun 5, 2026

    @bra-i-am

    @MaferMazu, thanks a lot for taking a look at this!

    I'll ignore the Taxonomy things, and the tags related bugs are just additional notes to take into account as part of the refinement, no questions there 😅

  6. moved this from In Grooming to Ready for Development in RBAC AuthZ Boardon Aug 4, 2026
  7. moved this from Ready for Development to In Progress in RBAC AuthZ Boardon Aug 10, 2026
  8. dcoa commented on Aug 10, 2026

    @dcoa
    Contributor

    @BryanttV @mariajgrimaldi I was working on this and as Course Auditor I can see the course outline however when I enter a unit the backend return 403, is that the expected behavior? Base on the Figma the Course Auditor should be able to navigate.

  9. BryanttV commented on Aug 11, 2026

    @BryanttV
    Contributor

    Hi @dcoa, yes, we need to fix this on the backend. I've created this issue to work on it: #384. Thanks for the report!

  10. moved this from In Progress to Blocked in RBAC AuthZ Boardon Aug 13, 2026
  11. moved this from Blocked to Ready for Review in RBAC AuthZ Boardon Sep 1, 2026
  12. moved this from Ready for Review to In Review in RBAC AuthZ Boardon Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

willowReleased in Willow

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions