[WEB-3268] feat: url pattern (#6546)

* feat: meta endpoint for issue

* chore: add detail endpoint

* chore: getIssueMetaFromURL and retrieveWithIdentifier endpoint added

* chore: issue store updated

* chore: move issue detail to new route and add redirection for old route

* fix: issue details permission

* fix: work item detail header

* chore: generateWorkItemLink helper function added

* chore: copyTextToClipboard helper function updated

* chore: workItemLink updated

* chore: workItemLink updated

* chore: workItemLink updated

* fix: issues navigation tab active status

* fix: invalid workitem error state

* chore: peek view parent issue redirection improvement

* fix: issue detail endpoint to not return epics and intake issue

* fix: workitem empty state redirection and header

* fix: workitem empty state redirection and header

* chore: code refactor

* chore: project auth wrapper improvement

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2025-02-15 05:05:00 +05:30 committed by GitHub
parent 82eea3e802
commit 4353cc0c4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 1032 additions and 282 deletions

View file

@ -32,6 +32,7 @@ export interface IIssueStoreActions {
removeModuleIds: string[]
) => Promise<void>;
removeIssueFromModule: (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => Promise<void>;
fetchIssueWithIdentifier: (workspaceSlug: string, project_identifier: string, sequence_id: string) => Promise<TIssue>;
}
export interface IIssueStore extends IIssueStoreActions {
@ -39,6 +40,7 @@ export interface IIssueStore extends IIssueStoreActions {
getIsLocalDBIssueDescription: (issueId: string | undefined) => boolean;
// helper methods
getIssueById: (issueId: string) => TIssue | undefined;
getIssueIdByIdentifier: (issueIdentifier: string) => string | undefined;
}
export class IssueStore implements IIssueStore {
@ -86,6 +88,11 @@ export class IssueStore implements IIssueStore {
return this.rootIssueDetailStore.rootIssueStore.issues.getIssueById(issueId) ?? undefined;
});
getIssueIdByIdentifier = computedFn((issueIdentifier: string) => {
if (!issueIdentifier) return undefined;
return this.rootIssueDetailStore.rootIssueStore.issues.getIssueIdByIdentifier(issueIdentifier) ?? undefined;
});
// actions
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, issueStatus = "DEFAULT") => {
const query = {
@ -285,4 +292,65 @@ export class IssueStore implements IIssueStore {
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
return currentModule;
};
fetchIssueWithIdentifier = async (workspaceSlug: string, project_identifier: string, sequence_id: string) => {
const query = {
expand: "issue_reactions,issue_attachments,issue_link,parent",
};
const issue = await this.issueService.retrieveWithIdentifier(workspaceSlug, project_identifier, sequence_id, query);
const issueIdentifier = `${project_identifier}-${sequence_id}`;
const issueId = issue?.id;
const projectId = issue?.project_id;
if (!issue || !projectId || !issueId) throw new Error("Issue not found");
const issuePayload = this.addIssueToStore(issue);
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]);
// handle parent issue if exists
if (issue?.parent && issue?.parent?.id && issue?.parent?.project_id) {
this.issueService.retrieve(workspaceSlug, issue.parent.project_id, issue.parent.id).then((res) => {
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([res]);
});
}
// add identifiers to map
this.rootIssueDetailStore.rootIssueStore.issues.addIssueIdentifier(issueIdentifier, issueId);
// add related data
if (issue.issue_reactions) this.rootIssueDetailStore.addReactions(issue.id, issue.issue_reactions);
if (issue.issue_link) this.rootIssueDetailStore.addLinks(issue.id, issue.issue_link);
if (issue.issue_attachments) this.rootIssueDetailStore.addAttachments(issue.id, issue.issue_attachments);
this.rootIssueDetailStore.addSubscription(issue.id, issue.is_subscribed);
// fetch related data
// issue reactions
if (issue.issue_reactions) this.rootIssueDetailStore.addReactions(issueId, issue.issue_reactions);
// fetch issue links
if (issue.issue_link) this.rootIssueDetailStore.addLinks(issueId, issue.issue_link);
// fetch issue attachments
if (issue.issue_attachments) this.rootIssueDetailStore.addAttachments(issueId, issue.issue_attachments);
this.rootIssueDetailStore.addSubscription(issueId, issue.is_subscribed);
// fetch issue activity
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
// fetch issue comments
this.rootIssueDetailStore.comment.fetchComments(workspaceSlug, projectId, issueId);
// fetch sub issues
this.rootIssueDetailStore.subIssues.fetchSubIssues(workspaceSlug, projectId, issueId);
// fetch issue relations
this.rootIssueDetailStore.relation.fetchRelations(workspaceSlug, projectId, issueId);
// fetching states
// TODO: check if this function is required
this.rootIssueDetailStore.rootIssueStore.rootStore.state.fetchProjectStates(workspaceSlug, projectId);
return issue;
};
}

View file

@ -259,6 +259,8 @@ export class IssueDetail implements IIssueDetail {
issueId: string,
issueStatus: "DEFAULT" | "DRAFT" = "DEFAULT"
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueStatus);
fetchIssueWithIdentifier = async (workspaceSlug: string, projectIdentifier: string, sequenceId: string) =>
this.issue.fetchIssueWithIdentifier(workspaceSlug, projectIdentifier, sequenceId);
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) =>
this.issue.updateIssue(workspaceSlug, projectId, issueId, data);
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>

View file

@ -15,19 +15,23 @@ import { IssueService } from "@/services/issue";
export type IIssueStore = {
// observables
issuesMap: Record<string, TIssue>; // Record defines issue_id as key and TIssue as value
issuesIdentifierMap: Record<string, string>; // Record defines issue_identifier as key and issue_id as value
// actions
getIssues(workspaceSlug: string, projectId: string, issueIds: string[]): Promise<TIssue[]>;
addIssue(issues: TIssue[]): void;
addIssueIdentifier(issueIdentifier: string, issueId: string): void;
updateIssue(issueId: string, issue: Partial<TIssue>): void;
removeIssue(issueId: string): void;
// helper methods
getIssueById(issueId: string): undefined | TIssue;
getIssueIdByIdentifier(issueIdentifier: string): undefined | string;
getIssuesByIds(issueIds: string[], type: "archived" | "un-archived"): TIssue[]; // Record defines issue_id as key and TIssue as value
};
export class IssueStore implements IIssueStore {
// observables
issuesMap: { [issue_id: string]: TIssue } = {};
issuesIdentifierMap: { [issue_identifier: string]: string } = {};
// service
issueService;
@ -35,8 +39,10 @@ export class IssueStore implements IIssueStore {
makeObservable(this, {
// observable
issuesMap: observable,
issuesIdentifierMap: observable,
// actions
addIssue: action,
addIssueIdentifier: action,
updateIssue: action,
removeIssue: action,
});
@ -59,6 +65,19 @@ export class IssueStore implements IIssueStore {
});
};
/**
* @description This method will add issue_identifier to the issuesIdentifierMap
* @param issueIdentifier
* @param issueId
* @returns {void}
*/
addIssueIdentifier = (issueIdentifier: string, issueId: string) => {
if (!issueIdentifier || !issueId) return;
runInAction(() => {
set(this.issuesIdentifierMap, issueIdentifier, issueId);
});
};
getIssues = async (workspaceSlug: string, projectId: string, issueIds: string[]) => {
const issues = await this.issueService.retrieveIssues(workspaceSlug, projectId, issueIds);
@ -116,6 +135,16 @@ export class IssueStore implements IIssueStore {
return this.issuesMap[issueId];
});
/**
* @description This method will return the issue_id from the issuesIdentifierMap
* @param {string} issueIdentifier
* @returns {string | undefined}
*/
getIssueIdByIdentifier = computedFn((issueIdentifier: string) => {
if (!issueIdentifier || !this.issuesIdentifierMap[issueIdentifier]) return undefined;
return this.issuesIdentifierMap[issueIdentifier];
});
/**
* @description This method will return the issues from the issuesMap
* @param {string[]} issueIds