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
4 changes: 4 additions & 0 deletions apps/web/ce/store/user/permission.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export class UserPermissionStore extends BaseUserPermissionStore implements IUse
(workspaceSlug: string, projectId?: string): EUserPermissions | undefined =>
this.getProjectRole(workspaceSlug, projectId)
);

fetchWorkspaceLevelProjectEntities = (workspaceSlug: string, projectId: string): void => {
void this.store.projectRoot.project.fetchProjectDetails(workspaceSlug, projectId);
};
}
3 changes: 0 additions & 3 deletions apps/web/core/components/project/join-project-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Button } from "@plane/propel/button";
import type { IProject } from "@plane/types";
// ui
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";

Expand All @@ -24,7 +23,6 @@ export function JoinProjectModal(props: TJoinProjectModalProps) {
const [isJoiningLoading, setIsJoiningLoading] = useState(false);
// store hooks
const { joinProject } = useUserPermissions();
const { fetchProjectDetails } = useProject();
// router
const router = useAppRouter();

Expand All @@ -34,7 +32,6 @@ export function JoinProjectModal(props: TJoinProjectModalProps) {
joinProject(workspaceSlug, project.id)
.then(() => {
router.push(`/${workspaceSlug}/projects/${project.id}/issues`);
fetchProjectDetails(workspaceSlug, project.id);
handleClose();
})
.finally(() => {
Expand Down
53 changes: 34 additions & 19 deletions apps/web/core/constants/fetch-keys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IJiraMetadata } from "@plane/types";
import type { EUserPermissions, IJiraMetadata } from "@plane/types";

const paramsToKey = (params: any) => {
const {
Expand Down Expand Up @@ -70,6 +70,9 @@ export const WORKSPACE_INVITATION = (invitationId: string) => `WORKSPACE_INVITAT
export const WORKSPACE_MEMBER_ME_INFORMATION = (workspaceSlug: string) =>
`WORKSPACE_MEMBER_ME_INFORMATION_${workspaceSlug.toUpperCase()}`;

export const WORKSPACE_MEMBER_ACTIVITY = (workspaceSlug: string) =>
`WORKSPACE_MEMBER_ACTIVITY_${workspaceSlug.toUpperCase()}`;

export const WORKSPACE_PROJECTS_ROLES_INFORMATION = (workspaceSlug: string) =>
`WORKSPACE_PROJECTS_ROLES_INFORMATION_${workspaceSlug.toUpperCase()}`;

Expand Down Expand Up @@ -154,29 +157,41 @@ export const PROJECT_DETAILS = (workspaceSlug: string, projectId: string) =>
export const PROJECT_ME_INFORMATION = (workspaceSlug: string, projectId: string) =>
`PROJECT_ME_INFORMATION_${projectId.toString().toUpperCase()}`;

export const PROJECT_LABELS = (workspaceSlug: string, projectId: string) =>
`PROJECT_LABELS_${projectId.toString().toUpperCase()}`;
export const PROJECT_LABELS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_LABELS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_MEMBERS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_MEMBERS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_STATES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_STATES_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_INTAKE_STATE = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_INTAKE_STATE_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_ESTIMATES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_ESTIMATES_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_MEMBERS = (workspaceSlug: string, projectId: string) =>
`PROJECT_MEMBERS_${projectId.toString().toUpperCase()}`;
export const PROJECT_ALL_CYCLES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_ALL_CYCLES_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_STATES = (workspaceSlug: string, projectId: string) =>
`PROJECT_STATES_${projectId.toString().toUpperCase()}`;
export const PROJECT_MODULES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_MODULES_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_INTAKE_STATE = (workspaceSlug: string, projectId: string) =>
`PROJECT_INTAKE_STATE_${projectId.toString().toUpperCase()}`;
export const PROJECT_VIEWS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_VIEWS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_ESTIMATES = (workspaceSlug: string, projectId: string) =>
`PROJECT_ESTIMATES_${projectId.toString().toUpperCase()}`;
export const PROJECT_MEMBER_PREFERENCES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_MEMBER_PREFERENCES_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_ALL_CYCLES = (workspaceSlug: string, projectId: string) =>
`PROJECT_ALL_CYCLES_${projectId.toString().toUpperCase()}`;
export const PROJECT_WORKFLOWS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_WORKFLOWS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_MODULES = (workspaceSlug: string, projectId: string) =>
`PROJECT_MODULES_${projectId.toString().toUpperCase()}`;
export const EPICS_PROPERTIES_AND_OPTIONS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`EPICS_PROPERTIES_AND_OPTIONS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_VIEWS = (workspaceSlug: string, projectId: string) =>
`PROJECT_VIEWS_${projectId.toString().toUpperCase()}`;
export const WORK_ITEM_TYPES_PROPERTIES_AND_OPTIONS = (projectId: string, projectRole: EUserPermissions | undefined) =>
`WORK_ITEM_TYPES_PROPERTIES_AND_OPTIONS_${projectId.toString().toUpperCase()}_${projectRole}`;

export const PROJECT_MEMBER_PREFERENCES = (workspaceSlug: string, projectId: string) =>
`PROJECT_MEMBER_PREFERENCES_${projectId.toString().toUpperCase()}`;
export const PROJECT_MILESTONES = (projectId: string, projectRole: EUserPermissions | undefined) =>
`PROJECT_MILESTONES_${projectId.toString().toUpperCase()}_${projectRole}`;
26 changes: 12 additions & 14 deletions apps/web/core/layouts/auth-layout/project-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const ProjectAuthWrapper = observer(function ProjectAuthWrapper(props: IP
// states
const [isJoiningProject, setIsJoiningProject] = useState(false);
// store hooks
const { fetchUserProjectInfo, allowPermissions } = useUserPermissions();
const { fetchUserProjectInfo, allowPermissions, getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
const { fetchProjectDetails } = useProject();
const { joinProject } = useUserPermissions();
const { fetchAllCycles } = useCycle();
Expand All @@ -65,8 +65,8 @@ export const ProjectAuthWrapper = observer(function ProjectAuthWrapper(props: IP
workspaceSlug,
projectId
);
const currentProjectRole = getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug, projectId);
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE, workspaceSlug);

// Initialize module timeline chart
useEffect(() => {
initGantt();
Expand All @@ -82,60 +82,58 @@ export const ProjectAuthWrapper = observer(function ProjectAuthWrapper(props: IP
useSWR(PROJECT_ME_INFORMATION(workspaceSlug, projectId), () => fetchUserProjectInfo(workspaceSlug, projectId));
// fetching project member preferences
useSWR(
currentUserData?.id ? PROJECT_MEMBER_PREFERENCES(workspaceSlug, projectId) : null,
currentUserData?.id ? PROJECT_MEMBER_PREFERENCES(projectId, currentProjectRole) : null,
currentUserData?.id ? () => fetchProjectMemberPreferences(workspaceSlug, projectId, currentUserData.id) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project labels
useSWR(PROJECT_LABELS(workspaceSlug, projectId), () => fetchProjectLabels(workspaceSlug, projectId), {
useSWR(PROJECT_LABELS(projectId, currentProjectRole), () => fetchProjectLabels(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project members
useSWR(PROJECT_MEMBERS(workspaceSlug, projectId), () => fetchProjectMembers(workspaceSlug, projectId), {
useSWR(PROJECT_MEMBERS(projectId, currentProjectRole), () => fetchProjectMembers(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project states
useSWR(PROJECT_STATES(workspaceSlug, projectId), () => fetchProjectStates(workspaceSlug, projectId), {
useSWR(PROJECT_STATES(projectId, currentProjectRole), () => fetchProjectStates(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project intake state
useSWR(PROJECT_INTAKE_STATE(workspaceSlug, projectId), () => fetchProjectIntakeState(workspaceSlug, projectId), {
useSWR(PROJECT_INTAKE_STATE(projectId, currentProjectRole), () => fetchProjectIntakeState(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project estimates
useSWR(PROJECT_ESTIMATES(workspaceSlug, projectId), () => getProjectEstimates(workspaceSlug, projectId), {
useSWR(PROJECT_ESTIMATES(projectId, currentProjectRole), () => getProjectEstimates(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project cycles
useSWR(PROJECT_ALL_CYCLES(workspaceSlug, projectId), () => fetchAllCycles(workspaceSlug, projectId), {
useSWR(PROJECT_ALL_CYCLES(projectId, currentProjectRole), () => fetchAllCycles(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});
// fetching project modules
useSWR(
PROJECT_MODULES(workspaceSlug, projectId),
PROJECT_MODULES(projectId, currentProjectRole),
async () => {
await Promise.all([fetchModulesSlim(workspaceSlug, projectId), fetchModules(workspaceSlug, projectId)]);
},
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project views
useSWR(PROJECT_VIEWS(workspaceSlug, projectId), () => fetchViews(workspaceSlug, projectId), {
useSWR(PROJECT_VIEWS(projectId, currentProjectRole), () => fetchViews(workspaceSlug, projectId), {
revalidateIfStale: false,
revalidateOnFocus: false,
});

// handle join project
const handleJoinProject = () => {
setIsJoiningProject(true);
joinProject(workspaceSlug, projectId)
.then(() => fetchProjectDetails(workspaceSlug, projectId))
.finally(() => setIsJoiningProject(false));
joinProject(workspaceSlug, projectId).finally(() => setIsJoiningProject(false));
};

const isProjectLoading = (isParentLoading || isProjectDetailsLoading) && !projectDetailsError;
Expand Down
11 changes: 11 additions & 0 deletions apps/web/core/store/user/base-permissions.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface IBaseUserPermissionStore {
workspaceSlug: string,
projectId?: string
) => EUserPermissions | undefined;
fetchWorkspaceLevelProjectEntities: (workspaceSlug: string, projectId: string) => void;
allowPermissions: (
allowPermissions: ETempUserRole[],
level: TUserPermissionsLevel,
Expand Down Expand Up @@ -148,6 +149,15 @@ export abstract class BaseUserPermissionStore implements IBaseUserPermissionStor
projectId?: string
) => EUserPermissions | undefined;

/**
* @description Fetches project-level entities that are not automatically loaded by the project wrapper.
* This is used when joining a project to ensure all necessary workspace-level project data is available.
* @param { string } workspaceSlug
* @param { string } projectId
* @returns { Promise<void> }
*/
abstract fetchWorkspaceLevelProjectEntities: (workspaceSlug: string, projectId: string) => void;
Comment on lines +152 to +159
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.

⚠️ Potential issue | 🟡 Minor

JSDoc return type inconsistency.

The @returns annotation says { Promise<void> } but the actual method signature declares => void. The implementation in permission.store.ts returns void (not a Promise).

   /**
    * @description Fetches project-level entities that are not automatically loaded by the project wrapper.
    * This is used when joining a project to ensure all necessary workspace-level project data is available.
    * @param { string } workspaceSlug
    * @param { string } projectId
-   * @returns { Promise<void> }
+   * @returns { void }
    */
   abstract fetchWorkspaceLevelProjectEntities: (workspaceSlug: string, projectId: string) => void;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @description Fetches project-level entities that are not automatically loaded by the project wrapper.
* This is used when joining a project to ensure all necessary workspace-level project data is available.
* @param { string } workspaceSlug
* @param { string } projectId
* @returns { Promise<void> }
*/
abstract fetchWorkspaceLevelProjectEntities: (workspaceSlug: string, projectId: string) => void;
/**
* @description Fetches project-level entities that are not automatically loaded by the project wrapper.
* This is used when joining a project to ensure all necessary workspace-level project data is available.
* @param { string } workspaceSlug
* @param { string } projectId
* @returns { void }
*/
abstract fetchWorkspaceLevelProjectEntities: (workspaceSlug: string, projectId: string) => void;
🤖 Prompt for AI Agents
In apps/web/core/store/user/base-permissions.store.ts around lines 152 to 159,
the JSDoc @returns states { Promise<void> } but the abstract method signature
returns void and implementations return void; update the JSDoc to @returns {
void } (or simply remove the Promise mention) so the doc matches the method
signature and implementations, ensuring no async/Promise wording remains in the
description.


/**
* @description Returns whether the user has the permission to access a page
* @param { string } page
Expand Down Expand Up @@ -309,6 +319,7 @@ export abstract class BaseUserPermissionStore implements IBaseUserPermissionStor
runInAction(() => {
set(this.workspaceProjectsPermissions, [workspaceSlug, projectId], projectMemberRole);
});
void this.fetchWorkspaceLevelProjectEntities(workspaceSlug, projectId);
}
} catch (error) {
console.error("Error user joining the project", error);
Expand Down
Loading