Skip to content
Merged
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
3 changes: 3 additions & 0 deletions libs/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,12 @@
"Continue": "Continue",
"Select Organization": "Select Organization",
"Unable to log in to the application": "Unable to log in to the application",
"You do not have access to any organizations.": "You do not have access to any organizations.",
"Please contact your administrator to be granted access to an organization.": "Please contact your administrator to be granted access to an organization.",
"We cannot log you in as we could not determine what organizations you have access to.": "We cannot log you in as we could not determine what organizations you have access to.",
"Please try refreshing the page. If the problem persists, contact your administrator.": "Please try refreshing the page. If the problem persists, contact your administrator.",
"Error details": "Error details",
"Reload organizations": "Reload organizations",
"Change Organization": "Change Organization",
"Settings": "Settings",
"Fleet not found": "Fleet not found",
Expand Down
112 changes: 76 additions & 36 deletions libs/ui-components/src/components/common/OrganizationGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ interface OrganizationContextType {
mustShowOrganizationSelector: boolean;
selectOrganization: (org: Organization) => void;
selectionError?: string;
isReloading: boolean;
isEmptyOrganizations: boolean;
refetch: (extraDelay: number) => Promise<void>;
}

const OrganizationContext = React.createContext<OrganizationContextType | null>(null);
Expand All @@ -29,6 +32,8 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
const [availableOrganizations, setAvailableOrganizations] = React.useState<Organization[]>([]);
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) => {
Expand All @@ -43,6 +48,63 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
}
}, []);

const fetchOrganizations = React.useCallback(async () => {
try {
const organizations = await fetch.get<OrganizationList>('organizations');
setAvailableOrganizations(organizations.items);

// Treat empty organizations list as an error
if (!organizations.items || organizations.items.length === 0) {
setSelectionError('No organizations available');
setIsEmptyOrganizations(true);
setOrganizationsLoaded(true);
return;
}

const currentOrgId = getCurrentOrganizationId();

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

if (currentOrg) {
// The previously selected organization exists - use it
selectOrganization(currentOrg);
} else {
if (organizations.items?.length === 1) {
// Only one organization available - select it automatically
selectOrganization(organizations.items[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);
storeCurrentOrganizationId('');
}
}
setSelectionError(undefined);
setIsEmptyOrganizations(false);
} catch (error) {
setSelectionError(getErrorMessage(error));
setAvailableOrganizations([]);
setIsEmptyOrganizations(false);
} finally {
setOrganizationsLoaded(true);
}
}, [fetch, selectOrganization]);

const refetch = React.useCallback(
async (addDelay: number = 0) => {
setIsReloading(true);
try {
await new Promise((resolve) => setTimeout(resolve, addDelay));
await fetchOrganizations();
} finally {
setIsReloading(false);
}
},
[fetchOrganizations],
);
Comment thread
celdrake marked this conversation as resolved.

// Determine if multi-orgs are enabled. If so, check if an organization is already selected
React.useEffect(() => {
// Prevent multiple initialization calls - only run once
Expand All @@ -52,41 +114,7 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {

initializationStartedRef.current = true;

const initializeOrganizations = async () => {
try {
const organizations = await fetch.get<OrganizationList>('organizations');
setAvailableOrganizations(organizations.items);

const currentOrgId = getCurrentOrganizationId();

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

if (currentOrg) {
// The previously selected organization exists - use it
selectOrganization(currentOrg);
} else {
if (organizations.items?.length === 1) {
// Only one organization available - select it automatically
selectOrganization(organizations.items[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);
storeCurrentOrganizationId('');
}
}
setSelectionError(undefined);
} catch (error) {
setSelectionError(getErrorMessage(error));
setAvailableOrganizations([]);
} finally {
setOrganizationsLoaded(true);
}
};

void initializeOrganizations();
void fetchOrganizations();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand All @@ -106,8 +134,20 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => {
mustShowOrganizationSelector,
selectOrganization,
selectionError,
isEmptyOrganizations,
isReloading,
refetch,
}),
[currentOrganization, availableOrganizations, mustShowOrganizationSelector, selectOrganization, selectionError],
[
currentOrganization,
availableOrganizations,
mustShowOrganizationSelector,
selectOrganization,
selectionError,
isEmptyOrganizations,
isReloading,
refetch,
],
);

return <OrganizationContext.Provider value={contextValue}>{children}</OrganizationContext.Provider>;
Expand Down
54 changes: 44 additions & 10 deletions libs/ui-components/src/components/common/OrganizationSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface OrganizationSelectorContentProps {
}

const MAX_ORGANIZATIONS_FOR_SCROLL = 4;
const EXTRA_DELAY = 450;

const OrganizationSelectorContent = ({
defaultOrganizationId,
Expand Down Expand Up @@ -141,8 +142,15 @@ interface OrganizationSelectorProps {
}

const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSelectorProps) => {
const { availableOrganizations, selectOrganization, mustShowOrganizationSelector, selectionError } =
useOrganizationGuardContext();
const {
availableOrganizations,
selectOrganization,
mustShowOrganizationSelector,
selectionError,
isEmptyOrganizations,
refetch,
isReloading,
} = useOrganizationGuardContext();
const { t } = useTranslation();

const getLastSelectedOrganization = React.useCallback(() => {
Expand Down Expand Up @@ -172,21 +180,47 @@ const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSele
[availableOrganizations, selectOrganization, onClose],
);

const handleRefetch = React.useCallback(async () => {
await refetch(EXTRA_DELAY);
}, [refetch]);

if (selectionError) {
return (
<PageSection variant="light">
<Bullseye>
<Alert variant="danger" title={t('Unable to log in to the application')} isInline>
<TextContent>
<Text>{t('We cannot log you in as we could not determine what organizations you have access to.')}</Text>
<Text>{t('Please try refreshing the page. If the problem persists, contact your administrator.')}</Text>
<Text>
<details>
<summary>{t('Error details')}:</summary>
{selectionError}
</details>
</Text>
{isEmptyOrganizations ? (
<>
<Text>{t('You do not have access to any organizations.')}</Text>
<Text>{t('Please contact your administrator to be granted access to an organization.')}</Text>
</>
) : (
<>
<Text>
{t('We cannot log you in as we could not determine what organizations you have access to.')}
</Text>
<Text>
{t('Please try refreshing the page. If the problem persists, contact your administrator.')}
</Text>
<Text component="pre">
<details>
<summary>{t('Error details')}:</summary>
{selectionError}
</details>
</Text>
</>
)}
</TextContent>
<ActionList className="pf-v5-u-mt-md">
<ActionListGroup>
<ActionListItem>
<Button variant="primary" onClick={handleRefetch} isDisabled={isReloading}>
{t('Reload organizations')}
</Button>
</ActionListItem>
</ActionListGroup>
</ActionList>
</Alert>
</Bullseye>
</PageSection>
Expand Down