Skip to content
6 changes: 6 additions & 0 deletions app/client/src/ce/actions/organizationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ export const updateOrganizationConfig = (
type: ReduxActionTypes.UPDATE_ORGANIZATION_CONFIG,
payload,
});

export const fetchMyOrganizations = () => {
return {
type: ReduxActionTypes.FETCH_MY_ORGANIZATIONS_INIT,
};
};
18 changes: 18 additions & 0 deletions app/client/src/ce/api/OrganizationApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,20 @@ export interface UpdateOrganizationConfigRequest {
apiConfig?: AxiosRequestConfig;
}

export type FetchMyOrganizationsResponse = ApiResponse<{
organizations: Organization[];
}>;

export interface Organization {
organizationId: string;
organizationName: string;
organizationUrl: string;
state: string;
}

export class OrganizationApi extends Api {
static tenantsUrl = "v1/tenants";
static meUrl = "v1/users/me";

static async fetchCurrentOrganizationConfig(): Promise<
AxiosPromise<FetchCurrentOrganizationConfigResponse>
Expand All @@ -41,6 +53,12 @@ export class OrganizationApi extends Api {
},
);
}

static async fetchMyOrganizations(): Promise<
AxiosPromise<FetchMyOrganizationsResponse>
> {
return Api.get(`${OrganizationApi.meUrl}/organizations`);
}
}

export default OrganizationApi;
3 changes: 3 additions & 0 deletions app/client/src/ce/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1229,13 +1229,16 @@ const OrganizationActionTypes = {
FETCH_PRODUCT_ALERT_INIT: "FETCH_PRODUCT_ALERT_INIT",
FETCH_PRODUCT_ALERT_SUCCESS: "FETCH_PRODUCT_ALERT_SUCCESS",
UPDATE_PRODUCT_ALERT_CONFIG: "UPDATE_PRODUCT_ALERT_CONFIG",
FETCH_MY_ORGANIZATIONS_INIT: "FETCH_MY_ORGANIZATIONS_INIT",
FETCH_MY_ORGANIZATIONS_SUCCESS: "FETCH_MY_ORGANIZATIONS_SUCCESS",
};

const OrganizationActionErrorTypes = {
FETCH_CURRENT_ORGANIZATION_CONFIG_ERROR:
"FETCH_CURRENT_ORGANIZATION_CONFIG_ERROR",
UPDATE_ORGANIZATION_CONFIG_ERROR: "UPDATE_ORGANIZATION_CONFIG_ERROR",
FETCH_PRODUCT_ALERT_FAILED: "FETCH_PRODUCT_ALERT_FAILED",
FETCH_MY_ORGANIZATIONS_ERROR: "FETCH_MY_ORGANIZATIONS_ERROR",
};

const AnalyticsActionTypes = {
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2717,3 +2717,5 @@ export const MULTI_ORG_FOOTER_CREATE_ORG_LEFT_TEXT = () =>
"Looking to create one?";
export const MULTI_ORG_FOOTER_CREATE_ORG_RIGHT_TEXT = () =>
"Create an organization";

export const PENDING_INVITATIONS = () => "Pending invitations";
39 changes: 38 additions & 1 deletion app/client/src/ce/pages/Applications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ import {
getIsFetchingApplications,
} from "ee/selectors/selectedWorkspaceSelectors";
import {
getIsFetchingMyOrganizations,
getMyOrganizations,
getOrganizationPermissions,
shouldShowLicenseBanner,
activeOrganizationId,
} from "ee/selectors/organizationSelectors";
import { getWorkflowsList } from "ee/selectors/workflowSelectors";
import {
Expand Down Expand Up @@ -141,6 +144,10 @@ import {
} from "git";
import OldRepoLimitExceededErrorModal from "pages/Editor/gitSync/RepoLimitExceededErrorModal";
import { trackCurrentDomain } from "utils/multiOrgDomains";
import OrganizationDropdown from "components/OrganizationDropdown";
import { fetchMyOrganizations } from "ee/actions/organizationActions";
import type { Organization } from "ee/api/OrganizationApi";
import { useIsCloudBillingEnabled } from "hooks";

function GitModals() {
const isGitModEnabled = useGitModEnabled();
Expand Down Expand Up @@ -434,25 +441,45 @@ export const submitCreateWorkspaceForm = async (data: any, dispatch: any) => {
};

export interface LeftPaneProps {
activeOrganizationId?: string;
activeWorkspaceId?: string;
isBannerVisible?: boolean;
isFetchingOrganizations: boolean;
isFetchingWorkspaces: boolean;
organizations: Organization[];
workspaces: Workspace[];
activeWorkspaceId?: string;
}

export function LeftPane(props: LeftPaneProps) {
const {
activeOrganizationId,
activeWorkspaceId,
isBannerVisible = false,
isFetchingOrganizations,
isFetchingWorkspaces,
workspaces = [],
organizations = [],
} = props;
const isMobile = useIsMobileDevice();
const isCloudBillingEnabled = useIsCloudBillingEnabled();

if (isMobile) return null;

return (
<LeftPaneWrapper isBannerVisible={isBannerVisible}>
{isCloudBillingEnabled &&
!isFetchingOrganizations &&
organizations.length > 0 && (
<OrganizationDropdown
organizations={organizations}
selectedOrganization={
organizations.find(
(organization) =>
organization.organizationId === activeOrganizationId,
) || organizations[0]
}
/>
)}
<LeftPaneSection
heading={createMessage(WORKSPACES_HEADING)}
isBannerVisible={isBannerVisible}
Expand Down Expand Up @@ -992,6 +1019,9 @@ export const ApplictionsMainPage = (props: any) => {
const isHomePage = useRouteMatch("/applications")?.isExact;
const isLicensePage = useRouteMatch("/license")?.isExact;
const isBannerVisible = showBanner && (isHomePage || isLicensePage);
const organizations = useSelector(getMyOrganizations);
const isFetchingOrganizations = useSelector(getIsFetchingMyOrganizations);
const currentOrganizationId = useSelector(activeOrganizationId);

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -1013,6 +1043,10 @@ export const ApplictionsMainPage = (props: any) => {
workspaceIdFromQueryParams ? workspaceIdFromQueryParams : workspaces[0]?.id,
);

useEffect(() => {
dispatch(fetchMyOrganizations());
}, []);

useEffect(() => {
setActiveWorkspaceId(
workspaceIdFromQueryParams
Expand Down Expand Up @@ -1056,9 +1090,12 @@ export const ApplictionsMainPage = (props: any) => {
return (
<PageWrapper displayName="Applications">
<LeftPane
activeOrganizationId={currentOrganizationId}
activeWorkspaceId={activeWorkspaceId}
isBannerVisible={isBannerVisible}
isFetchingOrganizations={isFetchingOrganizations}
isFetchingWorkspaces={isFetchingWorkspaces}
organizations={organizations}
workspaces={workspaces}
/>
<MediaQuery maxWidth={MOBILE_MAX_WIDTH}>
Expand Down
31 changes: 31 additions & 0 deletions app/client/src/ce/reducers/organizationReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
createBrandColorsFromPrimaryColor,
} from "utils/BrandingUtils";
import { createReducer } from "utils/ReducerUtils";
import type { Organization } from "ee/api/OrganizationApi";

export interface OrganizationReduxState<T> {
displayName?: string;
Expand All @@ -21,6 +22,8 @@ export interface OrganizationReduxState<T> {
instanceId: string;
tenantId: string;
isWithinAnOrganization: boolean;
myOrganizations: Organization[];
isFetchingMyOrganizations: boolean;
}

export const defaultBrandingConfig = {
Expand All @@ -43,6 +46,8 @@ export const initialState: OrganizationReduxState<any> = {
instanceId: "",
tenantId: "",
isWithinAnOrganization: false,
myOrganizations: [],
isFetchingMyOrganizations: false,
};

export const handlers = {
Expand Down Expand Up @@ -113,6 +118,32 @@ export const handlers = {
...state,
isLoading: false,
}),
[ReduxActionTypes.FETCH_MY_ORGANIZATIONS_INIT]: (
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
state: OrganizationReduxState<any>,
) => ({
...state,
isFetchingMyOrganizations: true,
}),
[ReduxActionTypes.FETCH_MY_ORGANIZATIONS_SUCCESS]: (
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
state: OrganizationReduxState<any>,
action: ReduxAction<Organization[]>,
) => ({
...state,
myOrganizations: action.payload,
isFetchingMyOrganizations: false,
}),
[ReduxActionErrorTypes.FETCH_MY_ORGANIZATIONS_ERROR]: (
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
state: OrganizationReduxState<any>,
) => ({
...state,
isFetchingMyOrganizations: false,
}),
};

export default createReducer(initialState, handlers);
28 changes: 27 additions & 1 deletion app/client/src/ce/sagas/organizationSagas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
} from "ee/constants/ReduxActionConstants";
import { call, put } from "redux-saga/effects";
import type { APIResponseError, ApiResponse } from "api/ApiResponses";
import type { UpdateOrganizationConfigRequest } from "ee/api/OrganizationApi";
import type {
FetchMyOrganizationsResponse,
UpdateOrganizationConfigRequest,
} from "ee/api/OrganizationApi";
import { OrganizationApi } from "ee/api/OrganizationApi";
import { validateResponse } from "sagas/ErrorSagas";
import { safeCrashAppRequest } from "actions/errorActions";
Expand Down Expand Up @@ -158,3 +161,26 @@ export function* updateOrganizationConfigSaga(
});
}
}

export function* fetchMyOrganizationsSaga() {
try {
const response: FetchMyOrganizationsResponse = yield call(
OrganizationApi.fetchMyOrganizations,
);
const isValidResponse: boolean = yield validateResponse(response);

if (isValidResponse) {
yield put({
type: ReduxActionTypes.FETCH_MY_ORGANIZATIONS_SUCCESS,
payload: response.data,
});
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.FETCH_MY_ORGANIZATIONS_ERROR,
payload: {
error,
},
});
}
}
12 changes: 12 additions & 0 deletions app/client/src/ce/selectors/organizationSelectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,15 @@ export const isFreePlan = (state: DefaultRootState) => true;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const isWithinAnOrganization = (state: DefaultRootState) => true;

export const getMyOrganizations = (state: DefaultRootState) => {
return state.organization?.myOrganizations || [];
};

export const getIsFetchingMyOrganizations = (state: DefaultRootState) => {
return state.organization?.isFetchingMyOrganizations || false;
};

export const activeOrganizationId = (state: DefaultRootState) => {
return state.organization?.tenantId;
};
Loading
Loading