feat: saved views of my work, across my projects
A person saves named views of «my work»: projects, assigned to me, that I follow, status categories, priorities, bugs only, overdue and text. They are private (Mongo my_views, no history), listed and deleted by their owner (GraphQL myViews / saveMyView / deleteMyView, REST /api/me/views), and run as the cross-project search, which gains project, priority, bugsOnly and overdue filters (also in MCP my_issues). The home page shows them as a bar above «my work», with a drawer to save a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,12 @@ export class GraphqlIssuesGateway extends IssuesGateway {
|
||||
categories: filter.categories as never,
|
||||
limit: filter.limit,
|
||||
search: filter.search?.trim() || null,
|
||||
projectIds: filter.projectIds?.length ? filter.projectIds : null,
|
||||
priorities: filter.priorities?.length
|
||||
? (filter.priorities as never)
|
||||
: null,
|
||||
bugsOnly: filter.bugsOnly ?? null,
|
||||
overdue: filter.overdue ?? null,
|
||||
})
|
||||
.pipe(
|
||||
map((d) => ({
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
MyView,
|
||||
MyViewCriteria,
|
||||
SavedFilter,
|
||||
SavedFilterCriteria,
|
||||
SavedFiltersGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import {
|
||||
DeleteMyViewDocument,
|
||||
MyViewsDocument,
|
||||
SaveMyViewDocument,
|
||||
DeleteSavedFilterDocument,
|
||||
SavedFiltersDocument,
|
||||
SaveFilterDocument,
|
||||
@@ -38,4 +43,25 @@ export class GraphqlSavedFiltersGateway extends SavedFiltersGateway {
|
||||
.mutate(DeleteSavedFilterDocument, { id })
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
myViews(): Observable<MyView[]> {
|
||||
return this.gql
|
||||
.query(MyViewsDocument, {})
|
||||
.pipe(map((d) => d.myViews as MyView[]));
|
||||
}
|
||||
|
||||
saveMyView(input: {
|
||||
name: string;
|
||||
criteria: MyViewCriteria;
|
||||
}): Observable<string> {
|
||||
return this.gql
|
||||
.mutate(SaveMyViewDocument, { input: input as never })
|
||||
.pipe(map((d) => d.saveMyView));
|
||||
}
|
||||
|
||||
removeMyView(id: string): Observable<void> {
|
||||
return this.gql
|
||||
.mutate(DeleteMyViewDocument, { id })
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,18 @@ export type LinkIssuesInput = {
|
||||
type: IssueLinkType;
|
||||
};
|
||||
|
||||
export type MyViewCriteriaInput = {
|
||||
assignedToMe?: boolean | null | undefined;
|
||||
bugsOnly?: boolean | null | undefined;
|
||||
categories?: Array<StatusCategory> | null | undefined;
|
||||
overdue?: boolean | null | undefined;
|
||||
priorities?: Array<IssuePriority> | null | undefined;
|
||||
/** Empty: all my projects */
|
||||
projectIds?: Array<string> | null | undefined;
|
||||
search?: string | null | undefined;
|
||||
watching?: boolean | null | undefined;
|
||||
};
|
||||
|
||||
/** Why someone is told */
|
||||
export type NotificationKind =
|
||||
| 'ADDED_TO_PROJECT'
|
||||
@@ -293,6 +305,11 @@ export type SaveFilterInput = {
|
||||
shared?: boolean;
|
||||
};
|
||||
|
||||
export type SaveMyViewInput = {
|
||||
criteria: MyViewCriteriaInput;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type SavedFilterCriteriaInput = {
|
||||
areas?: Array<string> | null | undefined;
|
||||
assigneeId?: string | null | undefined;
|
||||
@@ -1023,6 +1040,10 @@ export type MyIssuesQueryVariables = Exact<{
|
||||
categories?: Array<StatusCategory> | StatusCategory | null | undefined;
|
||||
limit: number;
|
||||
search?: string | null | undefined;
|
||||
projectIds?: Array<string> | string | null | undefined;
|
||||
priorities?: Array<IssuePriority> | IssuePriority | null | undefined;
|
||||
bugsOnly?: boolean | null | undefined;
|
||||
overdue?: boolean | null | undefined;
|
||||
}>;
|
||||
|
||||
export type MyIssuesQuery = {
|
||||
@@ -2117,6 +2138,38 @@ export type DeleteSavedFilterMutationVariables = Exact<{
|
||||
|
||||
export type DeleteSavedFilterMutation = { deleteSavedFilter: boolean };
|
||||
|
||||
export type MyViewsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type MyViewsQuery = {
|
||||
myViews: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
criteria: {
|
||||
projectIds: Array<string> | null;
|
||||
assignedToMe: boolean | null;
|
||||
watching: boolean | null;
|
||||
categories: Array<StatusCategory> | null;
|
||||
priorities: Array<IssuePriority> | null;
|
||||
bugsOnly: boolean | null;
|
||||
overdue: boolean | null;
|
||||
search: string | null;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
|
||||
export type SaveMyViewMutationVariables = Exact<{
|
||||
input: SaveMyViewInput;
|
||||
}>;
|
||||
|
||||
export type SaveMyViewMutation = { saveMyView: string };
|
||||
|
||||
export type DeleteMyViewMutationVariables = Exact<{
|
||||
id: string;
|
||||
}>;
|
||||
|
||||
export type DeleteMyViewMutation = { deleteMyView: boolean };
|
||||
|
||||
export type SprintFieldsFragment = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
@@ -6604,6 +6657,53 @@ export const MyIssuesDocument = {
|
||||
},
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
|
||||
},
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectIds' },
|
||||
},
|
||||
type: {
|
||||
kind: 'ListType',
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'priorities' },
|
||||
},
|
||||
type: {
|
||||
kind: 'ListType',
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'IssuePriority' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'bugsOnly' },
|
||||
},
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'Boolean' } },
|
||||
},
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'overdue' },
|
||||
},
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'Boolean' } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
@@ -6652,6 +6752,38 @@ export const MyIssuesDocument = {
|
||||
name: { kind: 'Name', value: 'search' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'projectIds' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectIds' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'priorities' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'priorities' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'bugsOnly' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'bugsOnly' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'overdue' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'overdue' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
@@ -12637,6 +12769,161 @@ export const DeleteSavedFilterDocument = {
|
||||
DeleteSavedFilterMutation,
|
||||
DeleteSavedFilterMutationVariables
|
||||
>;
|
||||
export const MyViewsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'MyViews' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'myViews' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'criteria' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'projectIds' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'assignedToMe' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'watching' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'categories' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'priorities' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'bugsOnly' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'overdue' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'search' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<MyViewsQuery, MyViewsQueryVariables>;
|
||||
export const SaveMyViewDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'SaveMyView' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'SaveMyViewInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'saveMyView' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<SaveMyViewMutation, SaveMyViewMutationVariables>;
|
||||
export const DeleteMyViewDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'DeleteMyView' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: { kind: 'Variable', name: { kind: 'Name', value: 'id' } },
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'deleteMyView' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'id' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'id' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
DeleteMyViewMutation,
|
||||
DeleteMyViewMutationVariables
|
||||
>;
|
||||
export const SprintsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
|
||||
@@ -136,6 +136,10 @@ query MyIssues(
|
||||
$categories: [StatusCategory!]
|
||||
$limit: Int!
|
||||
$search: String
|
||||
$projectIds: [ID!]
|
||||
$priorities: [IssuePriority!]
|
||||
$bugsOnly: Boolean
|
||||
$overdue: Boolean
|
||||
) {
|
||||
myIssues(
|
||||
assignedToMe: $assignedToMe
|
||||
@@ -143,6 +147,10 @@ query MyIssues(
|
||||
categories: $categories
|
||||
limit: $limit
|
||||
search: $search
|
||||
projectIds: $projectIds
|
||||
priorities: $priorities
|
||||
bugsOnly: $bugsOnly
|
||||
overdue: $overdue
|
||||
) {
|
||||
total
|
||||
items {
|
||||
|
||||
@@ -32,3 +32,29 @@ mutation SaveFilter($input: SaveFilterInput!) {
|
||||
mutation DeleteSavedFilter($id: ID!) {
|
||||
deleteSavedFilter(id: $id)
|
||||
}
|
||||
|
||||
query MyViews {
|
||||
myViews {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
criteria {
|
||||
projectIds
|
||||
assignedToMe
|
||||
watching
|
||||
categories
|
||||
priorities
|
||||
bugsOnly
|
||||
overdue
|
||||
search
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation SaveMyView($input: SaveMyViewInput!) {
|
||||
saveMyView(input: $input)
|
||||
}
|
||||
|
||||
mutation DeleteMyView($id: ID!) {
|
||||
deleteMyView(id: $id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user