Merge pull request #2059 from makeplane/develop

Promote: Develop to Stage Release
This commit is contained in:
sriram veeraghanta 2023-09-01 20:53:35 +05:30 committed by GitHub
commit c1102180e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 192 additions and 676 deletions

View File

@ -32,19 +32,11 @@ class Migration(migrations.Migration):
field=models.CharField(blank=True, max_length=255, null=True), field=models.CharField(blank=True, max_length=255, null=True),
), ),
migrations.RunPython(update_user_timezones), migrations.RunPython(update_user_timezones),
migrations.AlterUniqueTogether(
name='issuevote',
unique_together=set(),
),
migrations.AlterField( migrations.AlterField(
model_name='issuevote', model_name='issuevote',
name='vote', name='vote',
field=models.IntegerField(choices=[(-1, 'DOWNVOTE'), (1, 'UPVOTE')], default=1), field=models.IntegerField(choices=[(-1, 'DOWNVOTE'), (1, 'UPVOTE')], default=1),
), ),
migrations.AlterUniqueTogether(
name='issuevote',
unique_together={('issue', 'actor', 'vote')},
),
migrations.CreateModel( migrations.CreateModel(
name='ProjectPublicMember', name='ProjectPublicMember',
fields=[ fields=[

View File

@ -481,7 +481,7 @@ class IssueVote(ProjectBaseModel):
) )
class Meta: class Meta:
unique_together = ["issue", "actor", "vote"] unique_together = ["issue", "actor",]
verbose_name = "Issue Vote" verbose_name = "Issue Vote"
verbose_name_plural = "Issue Votes" verbose_name_plural = "Issue Votes"
db_table = "issue_votes" db_table = "issue_votes"

View File

@ -1,5 +1,3 @@
"use client";
// constants // constants
import { issueGroupFilter } from "constants/data"; import { issueGroupFilter } from "constants/data";
@ -8,8 +6,8 @@ export const IssueBlockState = ({ state }: any) => {
if (stateGroup === null) return <></>; if (stateGroup === null) return <></>;
return ( return (
<div className="flex items-center justify-between gap-1 w-full rounded shadow-sm border-[0.5px] border-custom-border-300 duration-300 focus:outline-none px-2.5 py-1 text-xs cursor-pointer hover:bg-custom-background-80"> <div className="flex items-center justify-between gap-1 w-full rounded shadow-sm border-[0.5px] border-custom-border-300 duration-300 focus:outline-none px-2.5 py-1 text-xs">
<div className="flex items-center cursor-pointer w-full gap-1.5 text-custom-text-200"> <div className="flex items-center w-full gap-1.5 text-custom-text-200">
<stateGroup.icon /> <stateGroup.icon />
<div className="text-xs">{state?.name}</div> <div className="text-xs">{state?.name}</div>
</div> </div>

View File

@ -14,6 +14,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { IIssue } from "types/issue"; import { IIssue } from "types/issue";
// store // store
import { RootStore } from "store/root"; import { RootStore } from "store/root";
import { IssueVotes } from "components/issues/peek-overview";
export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => { export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
const { issue } = props; const { issue } = props;
@ -56,19 +57,6 @@ export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
</div> </div>
<div className="inline-flex flex-shrink-0 items-center gap-2 text-xs"> <div className="inline-flex flex-shrink-0 items-center gap-2 text-xs">
{projectStore.deploySettings?.votes && (
<>
{/* upvotes */}
<div className="flex-shrink-0">
<IssueBlockUpVotes number={totalUpVotes.length} />
</div>
{/* downotes */}
<div className="flex-shrink-0">
<IssueBlockDownVotes number={totalDownVotes.length} />
</div>
</>
)}
{/* priority */} {/* priority */}
{issue?.priority && ( {issue?.priority && (
<div className="flex-shrink-0"> <div className="flex-shrink-0">

View File

@ -1,25 +1,32 @@
"use client";
// next theme // next theme
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
// mobx react lite // mobx react lite
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
export const NavbarTheme = observer(() => { export const NavbarTheme = observer(() => {
const [appTheme, setAppTheme] = useState("light");
const { setTheme, theme } = useTheme(); const { setTheme, theme } = useTheme();
const handleTheme = () => { const handleTheme = () => {
setTheme(theme === "light" ? "dark" : "light"); setTheme(theme === "light" ? "dark" : "light");
}; };
useEffect(() => {
if (!theme) return;
setAppTheme(theme);
}, [theme]);
return ( return (
<button <button
type="button" type="button"
onClick={handleTheme} onClick={handleTheme}
className="relative w-7 h-7 grid place-items-center bg-custom-background-100 hover:bg-custom-background-80 text-custom-text-100 rounded" className="relative w-7 h-7 grid place-items-center bg-custom-background-100 hover:bg-custom-background-80 text-custom-text-100 rounded"
> >
<span className="material-symbols-rounded text-sm">{theme === "light" ? "dark_mode" : "light_mode"}</span> <span className="material-symbols-rounded text-sm">{appTheme === "light" ? "dark_mode" : "light_mode"}</span>
</button> </button>
); );
}); });

View File

@ -61,8 +61,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
) : ( ) : (
<div className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}> <div className={`grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-500 text-white`}>
{comment.actor_detail.is_bot {comment.actor_detail.is_bot
? comment.actor_detail.first_name.charAt(0) ? comment?.actor_detail?.first_name?.charAt(0)
: comment.actor_detail.display_name.charAt(0)} : comment?.actor_detail?.display_name?.charAt(0)}
</div> </div>
)} )}

View File

@ -70,7 +70,7 @@ export const PeekOverviewHeader: React.FC<Props> = (props) => {
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{issueDetailStore.peekMode === "side" && ( {issueDetailStore.peekMode === "side" && (
<button type="button" onClick={handleClose} autoFocus={false}> <button type="button" onClick={handleClose}>
<East <East
sx={{ sx={{
fontSize: "14px", fontSize: "14px",
@ -134,7 +134,7 @@ export const PeekOverviewHeader: React.FC<Props> = (props) => {
</div> </div>
{(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && ( {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
<button type="button" onClick={handleCopyLink} className="-rotate-45"> <button type="button" onClick={handleCopyLink} className="-rotate-45 focus:outline-none" tabIndex={1}>
<Icon iconName="link" /> <Icon iconName="link" />
</button> </button>
</div> </div>

View File

@ -47,7 +47,7 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
)} )}
</> </>
) : ( ) : (
<div className="bg-custom-background-80 px-2 py-2.5 flex items-center justify-between gap-2 border border-custom-border-300 rounded"> <div className="bg-custom-background-80 px-2 py-2.5 flex items-center justify-between gap-2 border border-custom-border-300 rounded mt-4">
<p className="flex gap-2 text-sm text-custom-text-200 break-words overflow-hidden"> <p className="flex gap-2 text-sm text-custom-text-200 break-words overflow-hidden">
<Icon iconName="lock" className="!text-sm" /> <Icon iconName="lock" className="!text-sm" />
Sign in to add your comment Sign in to add your comment

View File

@ -24,9 +24,7 @@ export const IssueEmojiReactions: React.FC = observer(() => {
if (!workspace_slug || !project_slug || !issueId) return; if (!workspace_slug || !project_slug || !issueId) return;
const userReaction = reactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex); const userReaction = reactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex);
if (userReaction) return; if (userReaction) return;
issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, { issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex);
reaction: reactionHex,
});
}; };
const handleReactionClick = (reactionHex: string) => { const handleReactionClick = (reactionHex: string) => {
@ -57,6 +55,7 @@ export const IssueEmojiReactions: React.FC = observer(() => {
if (reactions.length > 0) if (reactions.length > 0)
return ( return (
<Tooltip <Tooltip
key={reaction}
tooltipContent={ tooltipContent={
<div> <div>
{reactions {reactions
@ -74,7 +73,6 @@ export const IssueEmojiReactions: React.FC = observer(() => {
handleReactionClick(reaction); handleReactionClick(reaction);
}); });
}} }}
key={reaction}
className={`flex items-center gap-1 text-custom-text-100 text-sm h-full px-2 py-1 rounded-md ${ className={`flex items-center gap-1 text-custom-text-100 text-sm h-full px-2 py-1 rounded-md ${
reactions?.some((r) => r.actor_detail.id === user?.id && r.reaction === reaction) reactions?.some((r) => r.actor_detail.id === user?.id && r.reaction === reaction)
? "bg-custom-primary-100/10" ? "bg-custom-primary-100/10"

View File

@ -1,9 +1,10 @@
import { makeObservable, observable, action, runInAction } from "mobx"; import { makeObservable, observable, action, runInAction } from "mobx";
import { v4 as uuidv4 } from "uuid";
// store // store
import { RootStore } from "./root"; import { RootStore } from "./root";
// services // services
import IssueService from "services/issue.service"; import IssueService from "services/issue.service";
import { IIssue } from "types/issue"; import { IIssue, IVote } from "types/issue";
export type IPeekMode = "side" | "modal" | "full"; export type IPeekMode = "side" | "modal" | "full";
@ -32,8 +33,8 @@ export interface IIssueDetailStore {
) => Promise<any>; ) => Promise<any>;
deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void; deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void;
// issue reactions // issue reactions
addIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void; addIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionId: string) => void; removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
// issue votes // issue votes
addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise<void>; addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise<void>;
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>; removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
@ -88,7 +89,7 @@ class IssueDetailStore implements IssueDetailStore {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueId]: {
...issueDetails, ...(this.details[issueId] ?? issueDetails),
comments: commentsResponse, comments: commentsResponse,
}, },
}; };
@ -174,32 +175,64 @@ class IssueDetailStore implements IssueDetailStore {
} }
}; };
addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => {
try { try {
const issueVoteResponse = await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, data);
if (issueVoteResponse) {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueId]: {
...this.details[issueId], ...this.details[issueId],
reactions: [...this.details[issueId].reactions, issueVoteResponse], reactions: [
...this.details[issueId].reactions,
{
id: uuidv4(),
issue: issueId,
reaction: reactionHex,
actor_detail: this.rootStore.user.currentActor,
},
],
},
};
});
await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, {
reaction: reactionHex,
});
} catch (error) {
console.log("Failed to add issue vote");
const issueReactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
runInAction(() => {
this.details = {
...this.details,
[issueId]: {
...this.details[issueId],
reactions: issueReactions,
}, },
}; };
}); });
} }
} catch (error) {
console.log("Failed to add issue vote");
}
}; };
removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionId: string) => { removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => {
try { try {
await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionId); const newReactions = this.details[issueId].reactions.filter(
const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); (_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.currentUser?.id)
);
if (reactions) { runInAction(() => {
this.details = {
...this.details,
[issueId]: {
...this.details[issueId],
reactions: newReactions,
},
};
});
await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex);
} catch (error) {
console.log("Failed to remove issue reaction");
const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
@ -210,48 +243,76 @@ class IssueDetailStore implements IssueDetailStore {
}; };
}); });
} }
} catch (error) {
console.log("Failed to remove issue reaction");
}
}; };
addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => { addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => {
try { const newVote: IVote = {
const issueVoteResponse = await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data); actor: this.rootStore.user.currentUser?.id ?? "",
const issueDetails = await this.issueService.getIssueById(workspaceSlug, projectId, issueId); actor_detail: this.rootStore.user.currentActor,
issue: issueId,
project: projectId,
workspace: workspaceSlug,
vote: data.vote,
};
if (issueVoteResponse) { const filteredVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.currentUser?.id);
try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueId]: {
...issueDetails, ...this.details[issueId],
votes: [...filteredVotes, newVote],
}, },
}; };
}); });
}
await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data);
} catch (error) { } catch (error) {
console.log("Failed to add issue vote"); console.log("Failed to add issue vote");
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
runInAction(() => {
this.details = {
...this.details,
[issueId]: {
...this.details[issueId],
votes: issueVotes,
},
};
});
} }
}; };
removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => { removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => {
try { const newVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.currentUser?.id);
await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId);
const issueDetails = await this.issueService.getIssueById(workspaceSlug, projectId, issueId);
if (issueDetails) { try {
runInAction(() => { runInAction(() => {
this.details = { this.details = {
...this.details, ...this.details,
[issueId]: { [issueId]: {
...issueDetails, ...this.details[issueId],
votes: newVotes,
}, },
}; };
}); });
}
await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId);
} catch (error) { } catch (error) {
console.log("Failed to remove issue vote"); console.log("Failed to remove issue vote");
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
runInAction(() => {
this.details = {
...this.details,
[issueId]: {
...this.details[issueId],
votes: issueVotes,
},
};
});
} }
}; };
} }

View File

@ -1,566 +0,0 @@
// mobx
import { observable, action, computed, makeObservable, runInAction, reaction } from "mobx";
// service
import IssueService from "services/issue.service";
// types
import { IssueDetailType, TIssueBoardKeys, IIssueLabel, IIssueState, IIssue } from "types/issue";
export interface IIssueStore {
currentIssueBoardView: TIssueBoardKeys | null;
loader: boolean;
error: any | null;
states: IIssueState[] | null;
labels: IIssueLabel[] | null;
issues: IIssue[] | null;
issue_detail: IssueDetailType;
userSelectedStates: string[];
userSelectedLabels: string[];
userSelectedPriorities: string[];
activePeekOverviewIssueId: string | null;
getCountOfIssuesByState: (state: string) => number;
getFilteredIssuesByState: (state: string) => IIssue[];
getUserSelectedFilter: (key: "state" | "priority" | "label", value: string) => boolean;
checkIfFilterExistsForKey: (key: "state" | "priority" | "label") => boolean;
clearUserSelectedFilter: (key: "state" | "priority" | "label" | "all") => void;
getIfFiltersIsEmpty: () => boolean;
getURLDefinition: (
workspaceSlug: string,
projectId: string,
action?: {
key: "state" | "priority" | "label" | "all";
value?: string;
removeAll?: boolean;
}
) => string;
setActivePeekOverviewIssueId: (value: any) => void;
setCurrentIssueBoardView: (view: TIssueBoardKeys) => void;
fetchPublicIssues: (workspaceSlug: string, projectId: string, params: any) => Promise<void>;
getIssueByIdAsync: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IssueDetailType>;
}
class IssueLegacyStore {
currentIssueBoardView: TIssueBoardKeys | null = null;
loader: boolean = false;
error: any | null = null;
states: IIssueState[] | null = null;
labels: IIssueLabel[] | null = null;
issues: IIssue[] | null = null;
issue_detail: IssueDetailType = {};
activePeekOverviewIssueId: string | null = null;
userSelectedStates: string[] = [];
userSelectedLabels: string[] = [];
userSelectedPriorities: string[] = [];
// root store
rootStore;
// service
issueService;
constructor(_rootStore: any) {
makeObservable(this, {
// observable
currentIssueBoardView: observable,
loader: observable,
error: observable,
states: observable.ref,
labels: observable.ref,
issues: observable.ref,
issue_detail: observable.ref,
activePeekOverviewIssueId: observable.ref,
userSelectedStates: observable.ref,
userSelectedLabels: observable.ref,
userSelectedPriorities: observable.ref,
// action
setCurrentIssueBoardView: action,
fetchPublicIssues: action,
// computed
});
this.rootStore = _rootStore;
this.issueService = new IssueService();
}
// computed
getCountOfIssuesByState(state_id: string): number {
return this.issues?.filter((issue) => issue.state == state_id).length || 0;
}
getFilteredIssuesByState(state_id: string): IIssue[] | [] {
return this.issues?.filter((issue) => issue.state == state_id) || [];
}
setActivePeekOverviewIssueId = (issueId: string | null) => (this.activePeekOverviewIssueId = issueId);
/**
*
* @param key Is the key of the filter, i.e. state, label, priority
* @param value Is the value of the filter, i.e. state_id, label_id, priority
* @returns boolean
*/
getUserSelectedFilter(key: "state" | "priority" | "label", value: string): boolean {
if (key == "state") {
return this.userSelectedStates.includes(value);
} else if (key == "label") {
return this.userSelectedLabels.includes(value);
} else if (key == "priority") {
return this.userSelectedPriorities.includes(value);
} else {
return false;
}
}
checkIfFilterExistsForKey: (key: "state" | "priority" | "label") => boolean = (key) => {
if (key == "state") {
return this.userSelectedStates.length > 0;
} else if (key == "label") {
return this.userSelectedLabels.length > 0;
} else if (key == "priority") {
return this.userSelectedPriorities.length > 0;
} else {
return false;
}
};
clearUserSelectedFilter(key: "state" | "priority" | "label" | "all") {
if (key == "state") {
this.userSelectedStates = [];
} else if (key == "label") {
this.userSelectedLabels = [];
} else if (key == "priority") {
this.userSelectedPriorities = [];
} else if (key == "all") {
this.userSelectedStates = [];
this.userSelectedLabels = [];
this.userSelectedPriorities = [];
}
}
getIfFiltersIsEmpty: () => boolean = () =>
this.userSelectedStates.length === 0 &&
this.userSelectedLabels.length === 0 &&
this.userSelectedPriorities.length === 0;
getURLDefinition = (
workspaceSlug: string,
projectId: string,
action?: {
key: "state" | "priority" | "label" | "all";
value?: string;
removeAll?: boolean;
}
) => {
let url = `/${workspaceSlug}/${projectId}?board=${this.currentIssueBoardView}`;
if (action) {
if (action.key === "state")
this.userSelectedStates = action.removeAll
? []
: [...this.userSelectedStates].filter((state) => state !== action.value);
if (action.key === "label")
this.userSelectedLabels = action.removeAll
? []
: [...this.userSelectedLabels].filter((label) => label !== action.value);
if (action.key === "priority")
this.userSelectedPriorities = action.removeAll
? []
: [...this.userSelectedPriorities].filter((priority) => priority !== action.value);
if (action.key === "all") {
this.userSelectedStates = [];
this.userSelectedLabels = [];
this.userSelectedPriorities = [];
}
}
if (this.checkIfFilterExistsForKey("state")) {
url += `&states=${this.userSelectedStates.join(",")}`;
}
if (this.checkIfFilterExistsForKey("label")) {
url += `&labels=${this.userSelectedLabels.join(",")}`;
}
if (this.checkIfFilterExistsForKey("priority")) {
url += `&priorities=${this.userSelectedPriorities.join(",")}`;
}
return url;
};
// action
setCurrentIssueBoardView = async (view: TIssueBoardKeys) => {
this.currentIssueBoardView = view;
};
fetchPublicIssues = async (workspaceSlug: string, projectId: string, params: any) => {
try {
this.loader = true;
this.error = null;
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params);
if (response) {
const _states: IIssueState[] = [...response?.states];
const _labels: IIssueLabel[] = [...response?.labels];
const _issues: IIssue[] = [...response?.issues];
runInAction(() => {
this.states = _states;
this.labels = _labels;
this.issues = _issues;
this.loader = false;
});
return response;
}
} catch (error) {
this.loader = false;
this.error = error;
return error;
}
};
getIssueByIdAsync = async (workspaceSlug: string, projectId: string, issueId: string): Promise<IssueDetailType> => {
try {
const response = this.issues?.find((issue) => issue.id === issueId);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
issue: response,
comments: [],
reactions: [],
votes: [],
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
this.getIssueReactionsAsync(workspaceSlug, projectId, issueId);
this.getIssueVotesAsync(workspaceSlug, projectId, issueId);
this.getIssueCommentsAsync(workspaceSlug, projectId, issueId);
}
return this.issue_detail[issueId] as any;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
getIssueVotesAsync = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const response = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
votes: response,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
createIssueVoteAsync = async (
workspaceSlug: string,
projectId: string,
issueId: string,
data: {
vote: 1 | -1;
}
) => {
try {
const response = await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
votes: [
...{ ...this.issue_detail }[issueId].votes.filter(
(vote) => vote.actor !== this.rootStore?.user?.currentUser?.id
),
response,
],
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
deleteIssueVoteAsync = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const _votes = (this.issue_detail[issueId].votes = this.issue_detail[issueId].votes.filter(
(vote) => vote.actor !== this.rootStore?.user?.user?.id
));
runInAction(() => {
this.issue_detail[issueId].votes = _votes;
});
const response = await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId);
const votesAfterCall = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
if (votesAfterCall)
runInAction(() => {
this.issue_detail[issueId].votes = votesAfterCall;
});
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
getIssueReactionsAsync = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const response = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
reactions: response,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
createIssueReactionAsync = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
try {
const response = await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, data);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
reactions: [...this.issue_detail[issueId].reactions, response],
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
deleteIssueReactionAsync = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => {
try {
const newReactionsList = this.issue_detail[issueId].reactions.filter(
(reaction) => reaction.reaction !== reactionHex
);
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
reactions: newReactionsList,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
const response = await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex);
const reactionsAfterCall = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
if (reactionsAfterCall) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
reactions: reactionsAfterCall,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
getIssueCommentsAsync = async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
const response = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
comments: response,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
createIssueCommentAsync = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
try {
const response = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
comments: [...this.issue_detail[issueId].comments, response],
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
updateIssueCommentAsync = async (
workspaceSlug: string,
projectId: string,
issueId: string,
commentId: string,
data: any
) => {
try {
const response = await this.issueService.updateIssueComment(workspaceSlug, projectId, issueId, commentId, data);
if (response) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
comments: [
...this.issue_detail[issueId].comments.filter((comment) => comment.id !== response.id),
response,
],
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
deleteIssueCommentAsync = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => {
try {
const newCommentsList = this.issue_detail[issueId].comments.filter((comment) => comment.id !== commentId);
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
comments: newCommentsList,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
const response = await this.issueService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId);
const commentsAfterCall = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
if (commentsAfterCall) {
const _issue_detail = {
...this.issue_detail,
[issueId]: {
...this.issue_detail[issueId],
comments: commentsAfterCall,
},
};
runInAction(() => {
this.issue_detail = _issue_detail;
});
}
return response;
} catch (error) {
this.loader = false;
this.error = error;
throw error;
}
};
}
export default IssueLegacyStore;

View File

@ -2,14 +2,18 @@
import { observable, action, computed, makeObservable, runInAction } from "mobx"; import { observable, action, computed, makeObservable, runInAction } from "mobx";
// service // service
import UserService from "services/user.service"; import UserService from "services/user.service";
import { ActorDetail } from "types/issue";
// types
import { IUser } from "types/user";
export interface IUserStore { export interface IUserStore {
currentUser: any | null; currentUser: any | null;
fetchCurrentUser: () => void; fetchCurrentUser: () => void;
currentActor: () => any;
} }
class UserStore implements IUserStore { class UserStore implements IUserStore {
currentUser: any | null = null; currentUser: IUser | null = null;
// root store // root store
rootStore; rootStore;
// service // service
@ -22,6 +26,7 @@ class UserStore implements IUserStore {
// actions // actions
setCurrentUser: action, setCurrentUser: action,
// computed // computed
currentActor: computed,
}); });
this.rootStore = _rootStore; this.rootStore = _rootStore;
this.userService = new UserService(); this.userService = new UserService();
@ -33,6 +38,17 @@ class UserStore implements IUserStore {
}); });
}; };
get currentActor(): any {
return {
avatar: this.currentUser?.avatar,
display_name: this.currentUser?.display_name,
first_name: this.currentUser?.first_name,
id: this.currentUser?.id,
is_bot: false,
last_name: this.currentUser?.last_name,
};
}
/** /**
* *
* @param callback * @param callback

View File

@ -42,14 +42,7 @@ export interface IIssue {
state: string; state: string;
state_detail: any; state_detail: any;
target_date: any; target_date: any;
votes: { votes: IVote[];
issue: string;
vote: -1 | 1;
workspace: string;
project: string;
actor: string;
actor_detail: ActorDetail;
}[];
} }
export interface IIssueState { export interface IIssueState {
@ -65,6 +58,15 @@ export interface IIssueLabel {
color: string; color: string;
} }
export interface IVote {
issue: string;
vote: -1 | 1;
workspace: string;
project: string;
actor: string;
actor_detail: ActorDetail;
}
export interface Comment { export interface Comment {
id: string; id: string;
actor_detail: ActorDetail; actor_detail: ActorDetail;
@ -95,12 +97,12 @@ export interface IIssueReaction {
} }
export interface ActorDetail { export interface ActorDetail {
avatar: string; avatar?: string;
display_name: string; display_name?: string;
first_name: string; first_name?: string;
id: string; id?: string;
is_bot: boolean; is_bot?: boolean;
last_name: string; last_name?: string;
} }
export interface IssueDetail { export interface IssueDetail {

View File

@ -1,3 +1,23 @@
export interface IUser { export interface IUser {
avatar: string;
cover_image: string | null;
created_at: Date;
created_location: string;
date_joined: Date;
email: string;
display_name: string;
first_name: string; first_name: string;
id: string;
is_email_verified: boolean;
is_onboarded: boolean;
is_tour_completed: boolean;
last_location: string;
last_login: Date;
last_name: string;
mobile_number: string;
role: string;
token: string;
updated_at: Date;
username: string;
user_timezone: string;
} }