[WEB-999] chore: updated UI improvements and workflow updates in the project inbox (#4180)

* chore: snoozed filter in the issue inbox filter

* chore: navigating to the next or previous issue when we accept, decline, or duplicate the issue in inbox

* chore: Implemented state, label, assignee and target_date in the inbox issue description and Implemented issue edit confirmation once we click accept the inbox issue

* chore: removed logs

* chore: inbox issue create response

* chore: update inbox issue response

* chore: updated inbox issue accept workflow and added issue properties in inbox issue create modal

* chore: resolved build errors and upgraded lucide react

* chore: updated inbox issue store hook

* chore: code cleanup and removed validation for inbox description

* fix: renamed the variable isLoading to loader in project-inbox store

* fix: updated set function for issue property update

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
guru_sainath 2024-04-15 12:49:14 +05:30 committed by GitHub
parent a44a032683
commit 20b0edeaa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 965 additions and 127 deletions

View file

@ -1,3 +1,4 @@
import clone from "lodash/clone";
import set from "lodash/set";
import { makeObservable, observable, runInAction, action } from "mobx";
import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types";
@ -5,6 +6,7 @@ import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } f
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
// services
import { InboxIssueService } from "@/services/inbox";
import { IssueService } from "@/services/issue";
// root store
import { RootStore } from "@/store/root.store";
@ -22,6 +24,8 @@ export interface IInboxIssueStore {
updateInboxIssueDuplicateTo: (issueId: string) => Promise<void>; // connecting the inbox issue to the project existing issue
updateInboxIssueSnoozeTill: (date: Date) => Promise<void>; // snooze the issue
updateIssue: (issue: Partial<TIssue>) => Promise<void>; // updating the issue
updateProjectIssue: (issue: Partial<TIssue>) => Promise<void>; // updating the issue
fetchIssueActivity: () => Promise<void>; // fetching the issue activity
}
export class InboxIssueStore implements IInboxIssueStore {
@ -38,6 +42,7 @@ export class InboxIssueStore implements IInboxIssueStore {
projectId: string;
// services
inboxIssueService;
issueService;
constructor(workspaceSlug: string, projectId: string, data: TInboxIssue, private store: RootStore) {
this.id = data.id;
@ -51,6 +56,7 @@ export class InboxIssueStore implements IInboxIssueStore {
this.projectId = projectId;
// services
this.inboxIssueService = new InboxIssueService();
this.issueService = new IssueService();
// observable variables should be defined after the initialization of the values
makeObservable(this, {
id: observable,
@ -65,6 +71,8 @@ export class InboxIssueStore implements IInboxIssueStore {
updateInboxIssueDuplicateTo: action,
updateInboxIssueSnoozeTill: action,
updateIssue: action,
updateProjectIssue: action,
fetchIssueActivity: action,
});
}
@ -72,12 +80,13 @@ export class InboxIssueStore implements IInboxIssueStore {
const previousData: Partial<TInboxIssue> = {
status: this.status,
};
try {
if (!this.issue.id) return;
set(this, "status", status);
await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
status: status,
});
runInAction(() => set(this, "status", inboxIssue?.status));
} catch {
runInAction(() => set(this, "status", previousData.status));
}
@ -85,20 +94,19 @@ export class InboxIssueStore implements IInboxIssueStore {
updateInboxIssueDuplicateTo = async (issueId: string) => {
const inboxStatus = EInboxIssueStatus.DUPLICATE;
const previousData: Partial<TInboxIssue> = {
status: this.status,
duplicate_to: this.duplicate_to,
};
try {
if (!this.issue.id) return;
set(this, "status", inboxStatus);
set(this, "duplicate_to", issueId);
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
status: inboxStatus,
duplicate_to: issueId,
});
runInAction(() => {
this.status = issueResponse.status;
this.duplicate_to = issueResponse.duplicate_to;
this.duplicate_issue_detail = issueResponse.duplicate_issue_detail;
});
@ -112,19 +120,21 @@ export class InboxIssueStore implements IInboxIssueStore {
updateInboxIssueSnoozeTill = async (date: Date) => {
const inboxStatus = EInboxIssueStatus.SNOOZED;
const previousData: Partial<TInboxIssue> = {
status: this.status,
snoozed_till: this.snoozed_till,
};
try {
if (!this.issue.id) return;
set(this, "status", inboxStatus);
set(this, "snoozed_till", date);
await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
status: inboxStatus,
snoozed_till: new Date(date),
});
runInAction(() => {
this.status = issueResponse?.status;
this.snoozed_till = issueResponse?.snoozed_till ? new Date(issueResponse.snoozed_till) : undefined;
});
} catch {
runInAction(() => {
set(this, "status", previousData.status);
@ -134,21 +144,49 @@ export class InboxIssueStore implements IInboxIssueStore {
};
updateIssue = async (issue: Partial<TIssue>) => {
const inboxIssue = this.issue;
const inboxIssue = clone(this.issue);
try {
if (!this.issue.id) return;
Object.keys(issue).forEach((key) => {
const issueKey = key as keyof TIssue;
set(inboxIssue, issueKey, issue[issueKey]);
set(this.issue, issueKey, issue[issueKey]);
});
await this.inboxIssueService.updateIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
// fetching activity
await this.store.issue.issueDetail.fetchActivities(this.workspaceSlug, this.projectId, this.issue.id);
this.fetchIssueActivity();
} catch {
Object.keys(issue).forEach((key) => {
const issueKey = key as keyof TIssue;
set(inboxIssue, issueKey, inboxIssue[issueKey]);
set(this.issue, issueKey, inboxIssue[issueKey]);
});
}
};
updateProjectIssue = async (issue: Partial<TIssue>) => {
const inboxIssue = clone(this.issue);
try {
if (!this.issue.id) return;
Object.keys(issue).forEach((key) => {
const issueKey = key as keyof TIssue;
set(this.issue, issueKey, issue[issueKey]);
});
await this.issueService.patchIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
// fetching activity
this.fetchIssueActivity();
} catch {
Object.keys(issue).forEach((key) => {
const issueKey = key as keyof TIssue;
set(this.issue, issueKey, inboxIssue[issueKey]);
});
}
};
fetchIssueActivity = async () => {
try {
if (!this.issue.id) return;
await this.store.issue.issueDetail.fetchActivities(this.workspaceSlug, this.projectId, this.issue.id);
} catch {
console.error("Failed to fetch issue activity");
}
};
}

View file

@ -31,7 +31,7 @@ type TLoader =
export interface IProjectInboxStore {
currentTab: TInboxIssueCurrentTab;
isLoading: TLoader;
loader: TLoader;
error: { message: string; status: "init-error" | "pagination-error" } | undefined;
currentInboxProjectId: string;
inboxFilters: Partial<TInboxIssueFilter>;
@ -42,7 +42,7 @@ export interface IProjectInboxStore {
getAppliedFiltersCount: number;
inboxIssuesArray: IInboxIssueStore[];
// helper actions
getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore | undefined;
getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore;
inboxIssueSorting: (issues: IInboxIssueStore[]) => IInboxIssueStore[];
inboxIssueQueryParams: (
inboxFilters: Partial<TInboxIssueFilter>,
@ -70,7 +70,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
PER_PAGE_COUNT = 10;
// observables
currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
isLoading: TLoader = "init-loading";
loader: TLoader = "init-loading";
error: { message: string; status: "init-error" | "pagination-error" } | undefined = undefined;
currentInboxProjectId: string = "";
inboxFilters: Partial<TInboxIssueFilter> = {
@ -88,7 +88,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
constructor(private store: RootStore) {
makeObservable(this, {
currentTab: observable.ref,
isLoading: observable.ref,
loader: observable.ref,
error: observable,
currentInboxProjectId: observable.ref,
inboxFilters: observable,
@ -123,17 +123,17 @@ export class ProjectInboxStore implements IProjectInboxStore {
}
get inboxIssuesArray() {
let appliedFilters =
this.currentTab === EInboxIssueCurrentTab.OPEN
? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED]
: [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE];
appliedFilters = appliedFilters.filter((filter) => this.inboxFilters?.status?.includes(filter));
return this.inboxIssueSorting(
Object.values(this.inboxIssues || {}).filter((inbox) =>
(this.currentTab === EInboxIssueCurrentTab.OPEN
? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED]
: [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE]
).includes(inbox.status)
)
Object.values(this.inboxIssues || {}).filter((inbox) => appliedFilters.includes(inbox.status))
);
}
getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId] || undefined);
getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId]);
// helpers
inboxIssueSorting = (issues: IInboxIssueStore[]) => {
@ -252,9 +252,9 @@ export class ProjectInboxStore implements IProjectInboxStore {
set(this, ["inboxIssues"], {});
set(this, ["inboxIssuePaginationInfo"], undefined);
}
if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading";
else this.isLoading = "mutation-loading";
if (loadingType) this.isLoading = loadingType;
if (Object.keys(this.inboxIssues).length === 0) this.loader = "init-loading";
else this.loader = "mutation-loading";
if (loadingType) this.loader = loadingType;
const queryParams = this.inboxIssueQueryParams(
this.inboxFilters,
@ -265,7 +265,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
runInAction(() => {
this.isLoading = undefined;
this.loader = undefined;
set(this, "inboxIssuePaginationInfo", paginationInfo);
if (results && results.length > 0)
results.forEach((value: TInboxIssue) => {
@ -279,7 +279,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
});
} catch (error) {
console.error("Error fetching the inbox issues", error);
this.isLoading = undefined;
this.loader = undefined;
this.error = {
message: "Error fetching the inbox issues please try again later.",
status: "init-error",
@ -301,7 +301,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
(this.inboxIssuePaginationInfo?.total_results &&
this.inboxIssuesArray.length < this.inboxIssuePaginationInfo?.total_results))
) {
this.isLoading = "pagination-loading";
this.loader = "pagination-loading";
const queryParams = this.inboxIssueQueryParams(
this.inboxFilters,
@ -312,7 +312,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams);
runInAction(() => {
this.isLoading = undefined;
this.loader = undefined;
set(this, "inboxIssuePaginationInfo", paginationInfo);
if (results && results.length > 0)
results.forEach((value: TInboxIssue) => {
@ -327,7 +327,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
} else set(this, ["inboxIssuePaginationInfo", "next_page_results"], false);
} catch (error) {
console.error("Error fetching the inbox issues", error);
this.isLoading = undefined;
this.loader = undefined;
this.error = {
message: "Error fetching the paginated inbox issues please try again later.",
status: "pagination-error",
@ -348,7 +348,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
inboxIssueId: string
): Promise<TInboxIssue> => {
try {
this.isLoading = "issue-loading";
this.loader = "issue-loading";
const inboxIssue = await this.inboxIssueService.retrieve(workspaceSlug, projectId, inboxIssueId);
const issueId = inboxIssue?.issue?.id || undefined;
@ -362,12 +362,12 @@ export class ProjectInboxStore implements IProjectInboxStore {
await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId);
// fetching comments
await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId);
this.isLoading = undefined;
this.loader = undefined;
}
return inboxIssue;
} catch (error) {
console.error("Error fetching the inbox issue with inbox issue id");
this.isLoading = undefined;
this.loader = undefined;
throw error;
}
};