Skip to content

Added features from issue: 6042 - #6132

Open
nairaj2 wants to merge 1 commit into
learningequality:unstablefrom
nairaj2:issue-6042-organization-ui
Open

nairaj2 wants to merge 1 commit into
learningequality:unstablefrom
nairaj2:issue-6042-organization-ui

Conversation

@nairaj2

@nairaj2 nairaj2 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds frontend support for organizations in Studio:

  • Adds a My organizations navigation item.
  • Adds an organizations page listing organizations available to the user.
  • Adds a page for creating organizations.
  • Adds an organization details page with channels and users.
  • Adds an organization filter and organization creation shortcut to My channels.
  • Uses KDS components, Composition API composables, and existing organization API endpoints.
  • Adds frontend tests covering organization listing, creation, details, filtering, and navigation.

This is a frontend-only change and builds on the organization models and endpoints introduced previously.

References

Reviewer guidance

To verify manually:

  1. Log in and open My organizations from the navigation bar.
  2. Confirm that organizations available to the user are displayed as portrait cards.
  3. Select an organization and verify its details, channels, and users.
  4. Select New organization, complete the form, and confirm that the new organization is created and opened.
  5. Return to My channels and verify that:
    • The organization filter appears at the top right.
    • The filter does not expand across the page.
    • The Create button beside the filter opens the new organization page.
    • Selecting an organization filters the channel cards.
    • Clearing the filter restores all editable channels.
    • The selected filter is preserved through the URL and browser navigation.
  6. Check the layouts at desktop and mobile widths.

Automated verification performed:

node node_modules/jest/bin/jest.js \
  --config jest_config/jest.conf.js \
  contentcuration/contentcuration/frontend/channelList \
  --runInBand

All 24 channel-list test suites and 215 tests pass. Pre-commit frontend checks also pass.

The organization channel list depends on channel responses containing organization association metadata. If that metadata is unavailable, the details page displays an unavailable-state message rather than showing unrelated channels.

Because this PR changes several layouts and workflows, screenshots of My channels, My organizations, and the organization details page would help review.

AI usage

I used OpenAI Codex to analyze the issue, design document, and reference PR; implement the organization pages and channel filter; and write frontend tests. I reviewed and refined the generated changes against the project conventions, corrected invalid API query behavior found during local testing, limited the implementation to frontend files, and ran the focused and full channel-list test suites plus pre-commit checks.

@learning-equality-bot

Copy link
Copy Markdown

👋 Hi @nairaj2, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

@nairaj2 nairaj2 changed the title [DRAFT]: Added features from issue: 6042 Added features from issue: 6042 Sep 15, 2026
@learning-equality-bot

Copy link
Copy Markdown

📢✨ Before we assign a reviewer, we'll turn on @rtibblesbot to pre-review. Its comments are generated by an LLM, and should be evaluated accordingly.

@rtibblesbot

rtibblesbot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

🟡 Waiting for changes

Last updated: 2026-09-15 18:56 UTC

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #6132: both channel-related ACs are inert — viewsets/channel.py never serializes organization, so the filter holds only "All organizations" and the details page always shows its unavailable message. Specs pass only because fixtures hand-add the field.

CI red: Browser smoke testDownload frontend bundle, a 403 from actions/download-artifact, unrelated here.

Manual QA did not run; nothing visually verified. Unchecked: OrganizationCard sizing inside KCardGrid, .button-container wrapping under 600px.

Design (PDF p3): channels-table Actions column, title info/edit affordances, Publish control — absent. If deferred, say so. P2 annotates cards "0 resources · English", not public/private.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Ran a phased review pipeline over the pull request diff:

  • Classified the diff to select review passes (core, frontend, backend) and whether manual QA was required
  • Core review pass checked correctness, design, architecture, testing, completeness, and DRY/SRP/Rule-of-Three principles
  • Specialized frontend/backend review passes applied framework-specific lenses where those files changed
  • For UI changes: manual QA and an accessibility audit against a live dev server, when available
  • Checked CI status and linked issue acceptance criteria
  • Synthesized one review from those passes and chose the verdict from the findings, CI status, and QA evidence

const filterMap = computed(() => {
const organizations = new Map();
for (const channel of channels.value) {
if (channel.organization && channel.organization_name) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: organization is in no channel payload — viewsets/channel.py has zero occurrences, despite Channel.organization (models.py:1163). KSelect renders one option forever. Add "organization_id"/"organization__name" to ChannelViewSet.values plus field_map, as OrganizationMemberViewSet does; otherwise make this PR depend on that work.

const hasOrganizationMetadata = accessibleChannels.some(channel =>
Object.prototype.hasOwnProperty.call(channel, 'organization'),
);
channelsUnavailable.value = !hasOrganizationMetadata;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Same cause: hasOwnProperty(channel, 'organization') is always false, so the Channels tab always renders channelsUnavailable. Sniffing the response shape also hides a missing contract instead of failing tests — drop it once organization ships. It also mislabels "no accessible channels" as "not available yet".


async function fetchAccessibleChannels() {
const channelLists = await Promise.all([
Channel.where({ edit: true }, true),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: ChannelListPagination.page_size = None (viewsets/channel.py:89), so { public: true } pulls every public channel into IndexedDB per page visit; searchCatalog (resources.js:1170) forces page_size for this reason. Add organization to ChannelFilter and fetch Channel.where({ organization }) — one query, no merge, no deleted refilter (line 174), and it can go through shared/vuex/channel/actions.js:loadChannelList like every other channel list.

</header>

<nav>
<KTabsList

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: Tabs emit aria-controls="organization-details-tabs-<tabId>-panel"; the <section>s (62, 79) have no id, role="tabpanel", aria-labelledby or tabindex, so the reference dangles. Use <KTabsPanel tabsId="organization-details-tabs" :tabId="tabIds.CHANNELS" :activeTabId="activeTab">.

</p>
<KCardGrid
v-else
layout="1-1-1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: LAYOUT_CONFIG_1_1_1 is one card per row at every breakpoint, so these 360×480 portrait cards form one narrow column; the design shows three across. Use layout="1-2-3". StudioChannelsPage differs — landscape full-width cards.

renderComponent({}, legacyChannels);
expect(await screen.findAllByTestId('channel-card')).toHaveLength(2);
expect(
await screen.findByText('All organizations', { selector: '.ui-select-display-value' }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: .ui-select-display-value / .ui-select-option-basic are keen-ui internals vendored by KDS — no other spec queries them, and this PR adds 7. Scope with within(screen.getByTestId('organization-filter')) and query by text.

expect(await screen.findByRole('heading', { name: 'Learning Together' })).toBeInTheDocument();
expect(screen.getByText('Organization channel')).toBeInTheDocument();
expect(screen.queryByText('Unrelated channel')).not.toBeInTheDocument();
expect(Channel.where).toHaveBeenCalledWith({ edit: true }, true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: These pin the fetch strategy; 136-137 already assert the behaviour. The server-side filter above would break them with output unchanged. Same at :89.

analyticsLabel: ListTypeToAnalyticsLabel[listType],
});

if (listType === ChannelListTypes.EDITABLE) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: lists is fixed, so this is "insert at index 1" by proxy — splice after the loop. analyticsLabel: 'ORGANIZATIONS' bypasses ListTypeToAnalyticsLabel, and the tab ("My organizations") and heading ("Organizations") disagree.

},
},
$trs: {
publicOrganization: 'Public organization',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Duplicated in OrganizationDetails.$trs — two translator entries per concept. See shared/strings/communityChannelsStrings.js.

},
});

// Derive options from the unfiltered list so selecting one organization does not

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: Deriving options from channels, not filteredChannels, avoids the self-narrowing dropdown, and the comment records why. unavailableOrganization fails visibly on an unknown id.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants