Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const TerminalTab = ({ device }: TerminalTabProps) => {

const { sendMessage, isClosed, error, reconnect, isConnecting } = useWebSocket(
device.metadata.name || '',
currentOrganization?.metadata?.name || undefined,
currentOrganization?.id || undefined,
onMsgReceived,
wsMeta,
);
Expand Down
65 changes: 48 additions & 17 deletions libs/ui-components/src/components/common/OrganizationGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,46 @@ import { getErrorMessage } from '../../utils/error';
import { getCurrentOrganizationId, storeCurrentOrganizationId } from '../../utils/organizationStorage';
import { showSpinnerBriefly } from '../../utils/time';

export type OrganizationItem = {
id: string;
label?: string;
description?: string;
};

// Returns the list of UserOrganizations ready to be displayed.
// Whenever two organizations have the same displayName, the description is set to the id.
const toOrganizationItems = (apiOrgs: Organization[]): OrganizationItem[] => {
const displayNameCounts = new Map<string, number>();
for (const org of apiOrgs) {
const displayName = org.spec?.displayName;
if (displayName) {
const prevCount = displayNameCounts.get(displayName) ?? 0;
displayNameCounts.set(displayName, prevCount + 1);
}
}
return apiOrgs.map((org) => {
const id = org.metadata?.name as string;
const displayName = org.spec?.displayName;
const sameNameCount = displayNameCounts.get(displayName || '') ?? 0;
if (sameNameCount > 1) {
return {
id,
label: displayName,
description: id,
};
}
return {
id,
label: displayName,
};
});
};

interface OrganizationContextType {
currentOrganization?: Organization;
availableOrganizations: Organization[];
currentOrganization?: OrganizationItem;
availableOrganizations: OrganizationItem[];
mustShowOrganizationSelector: boolean;
selectOrganization: (org: Organization) => void;
selectOrganization: (org: OrganizationItem) => void;
selectionError?: string;
isReloading: boolean;
isEmptyOrganizations: boolean;
Expand All @@ -29,20 +64,17 @@ export const useOrganizationGuardContext = (): OrganizationContextType => {
const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
const { fetch } = useAppContext();

const [currentOrganization, setCurrentOrganization] = React.useState<Organization | undefined>();
const [availableOrganizations, setAvailableOrganizations] = React.useState<Organization[]>([]);
const [currentOrganization, setCurrentOrganization] = React.useState<OrganizationItem | undefined>();
const [availableOrganizations, setAvailableOrganizations] = React.useState<OrganizationItem[]>([]);
const [organizationsLoaded, setOrganizationsLoaded] = React.useState(false);
const [selectionError, setSelectionError] = React.useState<string | undefined>();
const [isEmptyOrganizations, setIsEmptyOrganizations] = React.useState(false);
const [isReloading, setIsReloading] = React.useState(false);
const initializationStartedRef = React.useRef(false);

const selectOrganization = React.useCallback((org: Organization) => {
const organizationId = org.metadata?.name || '';

const selectOrganization = React.useCallback((org: OrganizationItem) => {
try {
// Store organization in localStorage - headers will handle the rest
storeCurrentOrganizationId(organizationId);
storeCurrentOrganizationId(org.id);
setCurrentOrganization(org);
} catch (error) {
setSelectionError(getErrorMessage(error));
Expand All @@ -52,10 +84,11 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
const fetchOrganizations = React.useCallback(async () => {
try {
const organizations = await fetch.get<OrganizationList>('organizations');
setAvailableOrganizations(organizations.items);
const orgItems = toOrganizationItems(organizations.items ?? []);
setAvailableOrganizations(orgItems);

// Treat empty organizations list as an error
if (!organizations.items || organizations.items.length === 0) {
if (!orgItems.length) {
setSelectionError('No organizations available');
setIsEmptyOrganizations(true);
setOrganizationsLoaded(true);
Expand All @@ -65,17 +98,15 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
const currentOrgId = getCurrentOrganizationId();

// Validate current organization against available organizations
const currentOrg = currentOrgId
? organizations.items.find((org) => org.metadata?.name === currentOrgId)
: undefined;
const currentOrg = currentOrgId ? orgItems.find((org) => org.id === currentOrgId) : undefined;

if (currentOrg) {
// The previously selected organization exists - use it
selectOrganization(currentOrg);
} else {
if (organizations.items?.length === 1) {
if (orgItems.length === 1) {
// Only one organization available - select it automatically
selectOrganization(organizations.items[0]);
selectOrganization(orgItems[0]);
} else if (currentOrgId) {
// Previously set organization does not exist anymore - remove it from localStorage so the user can select a new organization
setCurrentOrganization(undefined);
Expand Down
22 changes: 9 additions & 13 deletions libs/ui-components/src/components/common/OrganizationSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ import {
Title,
} from '@patternfly/react-core';

import { Organization } from '@flightctl/types';
import { useTranslation } from '../../hooks/useTranslation';
import { useOrganizationGuardContext } from './OrganizationGuard';
import { type OrganizationItem, useOrganizationGuardContext } from './OrganizationGuard';
import { ORGANIZATION_STORAGE_KEY } from '../../utils/organizationStorage';

interface OrganizationSelectorContentProps {
defaultOrganizationId?: string;
organizations: Organization[];
organizations: OrganizationItem[];
onSelect: (orgId: string) => void;
onCancel?: () => void;
allowCancel?: boolean;
Expand Down Expand Up @@ -73,14 +72,11 @@ const OrganizationSelectorContent = ({
>
<MenuContent menuHeight={needsScroll ? '230px' : 'auto'}>
<MenuList>
{organizations.map((org) => {
const orgId = org.metadata?.name as string;
return (
<MenuItem itemId={orgId} key={orgId}>
{org.spec?.displayName || orgId}
</MenuItem>
);
})}
{organizations.map((org) => (
<MenuItem key={org.id} itemId={org.id} description={org.description}>
{org.label || org.id}
</MenuItem>
))}
</MenuList>
</MenuContent>
</Menu>
Expand Down Expand Up @@ -155,7 +151,7 @@ const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSele
const getLastSelectedOrganization = React.useCallback(() => {
try {
const savedOrgId = localStorage.getItem(ORGANIZATION_STORAGE_KEY);
if (savedOrgId && availableOrganizations.some((org) => org.metadata?.name === savedOrgId)) {
if (savedOrgId && availableOrganizations.some((org) => org.id === savedOrgId)) {
return savedOrgId;
}
} catch (error) {
Expand All @@ -166,7 +162,7 @@ const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSele

const handleSelect = React.useCallback(
(orgId: string) => {
const org = availableOrganizations.find((org) => org.metadata?.name === orgId);
const org = availableOrganizations.find((o) => o.id === orgId);
if (org) {
try {
selectOrganization(org);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const PageNavigation = ({ showSettings = true }: { showSettings?: boolean }) =>
const [showOrganizationModal, setShowOrganizationModal] = React.useState(false);
const [showLoginCommandModal, setShowLoginCommandModal] = React.useState(false);
const showOrganizationSelection = availableOrganizations.length > 1;
const currentOrgDisplayName = currentOrganization?.spec?.displayName || currentOrganization?.metadata?.name || '';
const currentOrgDisplayName = currentOrganization?.label || currentOrganization?.id;

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const PermissionsContextProvider = ({ children }: React.PropsWithChildren
const [error, setError] = React.useState<string | undefined>();
const { currentOrganization } = useOrganizationGuardContext();

const orgId = currentOrganization?.metadata?.name;
const orgId = currentOrganization?.id;

const { get } = useFetch();

Expand Down
Loading