feat: add task activity

This commit is contained in:
Jordan Knott 2020-12-18 20:34:35 -06:00
parent f732b211c9
commit f4ef7fec83
33 changed files with 6209 additions and 4648 deletions

View File

@ -21,4 +21,4 @@ windows:
- database: - database:
root: ./ root: ./
panes: panes:
- pgcli postgres://taskcafe:taskcafe_test@localhost:5432/taskcafe - pgcli postgres://taskcafe:taskcafe_test@localhost:8855/taskcafe

View File

@ -182,7 +182,7 @@ const AdminRoute = () => {
updateApolloCache<UsersQuery>(client, UsersDocument, cache => updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.invitedUsers = cache.invitedUsers.filter( draftCache.invitedUsers = cache.invitedUsers.filter(
u => u.id !== response.data.deleteInvitedUserAccount.invitedUser.id, u => u.id !== response.data?.deleteInvitedUserAccount.invitedUser.id,
); );
}), }),
); );
@ -192,7 +192,7 @@ const AdminRoute = () => {
update: (client, response) => { update: (client, response) => {
updateApolloCache<UsersQuery>(client, UsersDocument, cache => updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.users = cache.users.filter(u => u.id !== response.data.deleteUserAccount.userAccount.id); draftCache.users = cache.users.filter(u => u.id !== response.data?.deleteUserAccount.userAccount.id);
}), }),
); );
}, },
@ -203,7 +203,7 @@ const AdminRoute = () => {
query: UsersDocument, query: UsersDocument,
}); });
const newData = produce(cacheData, (draftState: any) => { const newData = produce(cacheData, (draftState: any) => {
draftState.users = [...draftState.users, { ...createData.data.createUserAccount }]; draftState.users = [...draftState.users, { ...createData.data?.createUserAccount }];
}); });
client.writeQuery({ client.writeQuery({

View File

@ -25,6 +25,11 @@ const MainContent = styled.div`
flex-grow: 1; flex-grow: 1;
`; `;
type RefreshTokenResponse = {
accessToken: string;
setup?: null | { confirmToken: string };
};
const AuthorizedRoutes = () => { const AuthorizedRoutes = () => {
const history = useHistory(); const history = useHistory();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);

View File

@ -167,7 +167,7 @@ const ProjectFinder = () => {
return <span>error</span>; return <span>error</span>;
}; };
type ProjectPopupProps = { type ProjectPopupProps = {
history: History<History.PoorMansUnknown>; history: any;
name: string; name: string;
projectID: string; projectID: string;
}; };
@ -182,7 +182,7 @@ export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, proje
const newData = produce(cacheData, (draftState: any) => { const newData = produce(cacheData, (draftState: any) => {
draftState.projects = draftState.projects.filter( draftState.projects = draftState.projects.filter(
(project: any) => project.id !== deleteData.data.deleteProject.project.id, (project: any) => project.id !== deleteData.data?.deleteProject.project.id,
); );
}); });

View File

@ -46,10 +46,6 @@ const StyledContainer = styled(ToastContainer).attrs({
`; `;
const history = createBrowserHistory(); const history = createBrowserHistory();
type RefreshTokenResponse = {
accessToken: string;
isInstalled: boolean;
};
const App = () => { const App = () => {
const [user, setUser] = useState<CurrentUserRaw | null>(null); const [user, setUser] = useState<CurrentUserRaw | null>(null);

View File

@ -280,7 +280,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.taskGroups = draftCache.findProject.taskGroups.filter( draftCache.findProject.taskGroups = draftCache.findProject.taskGroups.filter(
(taskGroup: TaskGroup) => taskGroup.id !== deletedTaskGroupData.data.deleteTaskGroup.taskGroup.id, (taskGroup: TaskGroup) => taskGroup.id !== deletedTaskGroupData.data?.deleteTaskGroup.taskGroup.id,
); );
}), }),
{ projectID }, { projectID },
@ -296,9 +296,11 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const { taskGroups } = cache.findProject; const { taskGroups } = cache.findProject;
const idx = taskGroups.findIndex(taskGroup => taskGroup.id === newTaskData.data.createTask.taskGroup.id); const idx = taskGroups.findIndex(taskGroup => taskGroup.id === newTaskData.data?.createTask.taskGroup.id);
if (idx !== -1) { if (idx !== -1) {
draftCache.findProject.taskGroups[idx].tasks.push({ ...newTaskData.data.createTask }); if (newTaskData.data) {
draftCache.findProject.taskGroups[idx].tasks.push({ ...newTaskData.data.createTask });
}
} }
}), }),
{ projectID }, { projectID },
@ -313,7 +315,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] }); if (newTaskGroupData.data) {
draftCache.findProject.taskGroups.push({ ...newTaskGroupData.data.createTaskGroup, tasks: [] });
}
}), }),
{ projectID }, { projectID },
); );
@ -332,7 +336,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const idx = cache.findProject.taskGroups.findIndex( const idx = cache.findProject.taskGroups.findIndex(
t => t.id === resp.data.deleteTaskGroupTasks.taskGroupID, t => t.id === resp.data?.deleteTaskGroupTasks.taskGroupID,
); );
if (idx !== -1) { if (idx !== -1) {
draftCache.findProject.taskGroups[idx].tasks = []; draftCache.findProject.taskGroups[idx].tasks = [];
@ -348,7 +352,9 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.taskGroups.push(resp.data.duplicateTaskGroup.taskGroup); if (resp.data) {
draftCache.findProject.taskGroups.push(resp.data.duplicateTaskGroup.taskGroup);
}
}), }),
{ projectID }, { projectID },
); );
@ -364,19 +370,24 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const { previousTaskGroupID, task } = newTask.data.updateTaskLocation; if (newTask.data) {
if (previousTaskGroupID !== task.taskGroup.id) { const { previousTaskGroupID, task } = newTask.data.updateTaskLocation;
const { taskGroups } = cache.findProject; if (previousTaskGroupID !== task.taskGroup.id) {
const oldTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === previousTaskGroupID); const { taskGroups } = cache.findProject;
const newTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === task.taskGroup.id); const oldTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === previousTaskGroupID);
if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) { const newTaskGroupIdx = taskGroups.findIndex((t: TaskGroup) => t.id === task.taskGroup.id);
draftCache.findProject.taskGroups[oldTaskGroupIdx].tasks = taskGroups[oldTaskGroupIdx].tasks.filter( if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) {
(t: Task) => t.id !== task.id, const previousTask = cache.findProject.taskGroups[oldTaskGroupIdx].tasks.find(t => t.id === task.id);
); draftCache.findProject.taskGroups[oldTaskGroupIdx].tasks = taskGroups[oldTaskGroupIdx].tasks.filter(
draftCache.findProject.taskGroups[newTaskGroupIdx].tasks = [ (t: Task) => t.id !== task.id,
...taskGroups[newTaskGroupIdx].tasks, );
{ ...task }, if (previousTask) {
]; draftCache.findProject.taskGroups[newTaskGroupIdx].tasks = [
...taskGroups[newTaskGroupIdx].tasks,
{ ...previousTask },
];
}
}
} }
} }
}), }),

View File

@ -138,21 +138,23 @@ const Details: React.FC<DetailsProps> = ({
FindTaskDocument, FindTaskDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const { prevChecklistID, checklistID, checklistItem } = response.data.updateTaskChecklistItemLocation; if (response.data) {
if (checklistID !== prevChecklistID) { const { prevChecklistID, taskChecklistID, checklistItem } = response.data.updateTaskChecklistItemLocation;
const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID); if (taskChecklistID !== prevChecklistID) {
const newIdx = cache.findTask.checklists.findIndex(c => c.id === checklistID); const oldIdx = cache.findTask.checklists.findIndex(c => c.id === prevChecklistID);
if (oldIdx > -1 && newIdx > -1) { const newIdx = cache.findTask.checklists.findIndex(c => c.id === taskChecklistID);
const item = cache.findTask.checklists[oldIdx].items.find(i => i.id === checklistItem.id); if (oldIdx > -1 && newIdx > -1) {
if (item) { const item = cache.findTask.checklists[oldIdx].items.find(i => i.id === checklistItem.id);
draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter( if (item) {
i => i.id !== checklistItem.id, draftCache.findTask.checklists[oldIdx].items = cache.findTask.checklists[oldIdx].items.filter(
); i => i.id !== checklistItem.id,
draftCache.findTask.checklists[newIdx].items.push({ );
...item, draftCache.findTask.checklists[newIdx].items.push({
position: checklistItem.position, ...item,
taskChecklistID: checklistID, position: checklistItem.position,
}); taskChecklistID: taskChecklistID,
});
}
} }
} }
} }
@ -188,7 +190,7 @@ const Details: React.FC<DetailsProps> = ({
produce(cache, draftCache => { produce(cache, draftCache => {
const { checklists } = cache.findTask; const { checklists } = cache.findTask;
draftCache.findTask.checklists = checklists.filter( draftCache.findTask.checklists = checklists.filter(
c => c.id !== deleteData.data.deleteTaskChecklist.taskChecklist.id, c => c.id !== deleteData.data?.deleteTaskChecklist.taskChecklist.id,
); );
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = { draftCache.findTask.badges.checklist = {
@ -212,8 +214,10 @@ const Details: React.FC<DetailsProps> = ({
FindTaskDocument, FindTaskDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const item = createData.data.createTaskChecklist; if (createData.data) {
draftCache.findTask.checklists.push({ ...item }); const item = createData.data.createTaskChecklist;
draftCache.findTask.checklists.push({ ...item });
}
}), }),
{ taskID }, { taskID },
); );
@ -227,36 +231,14 @@ const Details: React.FC<DetailsProps> = ({
FindTaskDocument, FindTaskDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem; if (deleteData.data) {
const targetIdx = cache.findTask.checklists.findIndex(c => c.id === item.taskChecklistID); const item = deleteData.data.deleteTaskChecklistItem.taskChecklistItem;
if (targetIdx > -1) { const targetIdx = cache.findTask.checklists.findIndex(c => c.id === item.taskChecklistID);
draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter( if (targetIdx > -1) {
c => item.id !== c.id, draftCache.findTask.checklists[targetIdx].items = cache.findTask.checklists[targetIdx].items.filter(
); c => item.id !== c.id,
} );
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); }
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}),
{ taskID },
);
},
});
const [createTaskChecklistItem] = useCreateTaskChecklistItemMutation({
update: (client, newTaskItem) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
const item = newTaskItem.data.createTaskChecklistItem;
const { checklists } = cache.findTask;
const idx = checklists.findIndex(c => c.id === item.taskChecklistID);
if (idx !== -1) {
draftCache.findTask.checklists[idx].items.push({ ...item });
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists); const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = { draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge', __typename: 'ChecklistBadge',
@ -269,7 +251,33 @@ const Details: React.FC<DetailsProps> = ({
); );
}, },
}); });
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } }); const [createTaskChecklistItem] = useCreateTaskChecklistItemMutation({
update: (client, newTaskItem) => {
updateApolloCache<FindTaskQuery>(
client,
FindTaskDocument,
cache =>
produce(cache, draftCache => {
if (newTaskItem.data) {
const item = newTaskItem.data.createTaskChecklistItem;
const { checklists } = cache.findTask;
const idx = checklists.findIndex(c => c.id === item.taskChecklistID);
if (idx !== -1) {
draftCache.findTask.checklists[idx].items.push({ ...item });
const { complete, total } = calculateChecklistBadge(draftCache.findTask.checklists);
draftCache.findTask.badges.checklist = {
__typename: 'ChecklistBadge',
complete,
total,
};
}
}
}),
{ taskID },
);
},
});
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID }, fetchPolicy: 'cache-and-network' });
const [setTaskComplete] = useSetTaskCompleteMutation(); const [setTaskComplete] = useSetTaskCompleteMutation();
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({ const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
onCompleted: () => { onCompleted: () => {

View File

@ -36,7 +36,9 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.labels.push({ ...newLabelData.data.createProjectLabel }); if (newLabelData.data) {
draftCache.findProject.labels.push({ ...newLabelData.data.createProjectLabel });
}
}), }),
{ {
projectID, projectID,
@ -53,7 +55,7 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.labels = cache.findProject.labels.filter( draftCache.findProject.labels = cache.findProject.labels.filter(
label => label.id !== newLabelData.data.deleteProjectLabel.id, label => label.id !== newLabelData.data?.deleteProjectLabel.id,
); );
}), }),
{ projectID }, { projectID },

View File

@ -32,7 +32,6 @@ import {
FindProjectDocument, FindProjectDocument,
FindProjectQuery, FindProjectQuery,
} from 'shared/generated/graphql'; } from 'shared/generated/graphql';
import produce from 'immer'; import produce from 'immer';
import UserContext, { useCurrentUser } from 'App/context'; import UserContext, { useCurrentUser } from 'App/context';
import Input from 'shared/components/Input'; import Input from 'shared/components/Input';
@ -423,14 +422,16 @@ const Project = () => {
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
const taskGroupIdx = draftCache.findProject.taskGroups.findIndex( if (resp.data) {
tg => tg.tasks.findIndex(t => t.id === resp.data.deleteTask.taskID) !== -1, const taskGroupIdx = draftCache.findProject.taskGroups.findIndex(
); tg => tg.tasks.findIndex(t => t.id === resp.data?.deleteTask.taskID) !== -1,
);
if (taskGroupIdx !== -1) { if (taskGroupIdx !== -1) {
draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[ draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[
taskGroupIdx taskGroupIdx
].tasks.filter(t => t.id !== resp.data.deleteTask.taskID); ].tasks.filter(t => t.id !== resp.data?.deleteTask.taskID);
}
} }
}), }),
{ projectID }, { projectID },
@ -450,7 +451,7 @@ const Project = () => {
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.name = newName.data.updateProjectName.name; draftCache.findProject.name = newName.data?.updateProjectName.name ?? '';
}), }),
{ projectID }, { projectID },
); );
@ -464,14 +465,16 @@ const Project = () => {
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.members = [ if (response.data) {
...cache.findProject.members, draftCache.findProject.members = [
...response.data.inviteProjectMembers.members, ...cache.findProject.members,
]; ...response.data.inviteProjectMembers.members,
draftCache.findProject.invitedMembers = [ ];
...cache.findProject.invitedMembers, draftCache.findProject.invitedMembers = [
...response.data.inviteProjectMembers.invitedMembers, ...cache.findProject.invitedMembers,
]; ...response.data.inviteProjectMembers.invitedMembers,
];
}
}), }),
{ projectID }, { projectID },
); );
@ -485,7 +488,7 @@ const Project = () => {
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter( draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter(
m => m.email !== response.data.deleteInvitedProjectMember.invitedMember.email, m => m.email !== response.data?.deleteInvitedProjectMember.invitedMember.email ?? '',
); );
}), }),
{ projectID }, { projectID },
@ -500,7 +503,7 @@ const Project = () => {
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.members = cache.findProject.members.filter( draftCache.findProject.members = cache.findProject.members.filter(
m => m.id !== response.data.deleteProjectMember.member.id, m => m.id !== response.data?.deleteProjectMember.member.id,
); );
}), }),
{ projectID }, { projectID },

View File

@ -210,7 +210,9 @@ const Projects = () => {
update: (client, newProject) => { update: (client, newProject) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache => updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.projects.push({ ...newProject.data.createProject }); if (newProject.data) {
draftCache.projects.push({ ...newProject.data.createProject });
}
}), }),
); );
}, },
@ -222,7 +224,9 @@ const Projects = () => {
update: (client, createData) => { update: (client, createData) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache => updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.teams.push({ ...createData.data.createTeam }); if (createData.data) {
draftCache.teams.push({ ...createData.data?.createTeam });
}
}), }),
); );
}, },

View File

@ -430,11 +430,13 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
GetTeamDocument, GetTeamDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findTeam.members.push({ if (response.data) {
...response.data.createTeamMember.teamMember, draftCache.findTeam.members.push({
member: { __typename: 'MemberList', projects: [], teams: [] }, ...response.data.createTeamMember.teamMember,
owned: { __typename: 'OwnedList', projects: [], teams: [] }, member: { __typename: 'MemberList', projects: [], teams: [] },
}); owned: { __typename: 'OwnedList', projects: [], teams: [] },
});
}
}), }),
{ teamID }, { teamID },
); );
@ -459,7 +461,7 @@ const Members: React.FC<MembersProps> = ({ teamID }) => {
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findTeam.members = cache.findTeam.members.filter( draftCache.findTeam.members = cache.findTeam.members.filter(
member => member.id !== response.data.deleteTeamMember.userID, member => member.id !== response.data?.deleteTeamMember.userID,
); );
}), }),
{ teamID }, { teamID },

View File

@ -33,7 +33,7 @@ const Wrapper = styled.div`
`; `;
type TeamPopupProps = { type TeamPopupProps = {
history: History<History.PoorMansUnknown>; history: History<any>;
name: string; name: string;
teamID: string; teamID: string;
}; };
@ -44,9 +44,9 @@ export const TeamPopup: React.FC<TeamPopupProps> = ({ history, name, teamID }) =
update: (client, deleteData) => { update: (client, deleteData) => {
updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache => updateApolloCache<GetProjectsQuery>(client, GetProjectsDocument, cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.teams = cache.teams.filter((team: any) => team.id !== deleteData.data.deleteTeam.team.id); draftCache.teams = cache.teams.filter((team: any) => team.id !== deleteData.data?.deleteTeam.team.id);
draftCache.projects = cache.projects.filter( draftCache.projects = cache.projects.filter(
(project: any) => project.team.id !== deleteData.data.deleteTeam.team.id, (project: any) => project.team.id !== deleteData.data?.deleteTeam.team.id,
); );
}), }),
); );

View File

@ -263,7 +263,7 @@ const NewProject: React.FC<NewProjectProps> = ({ initialTeamID, teams, onClose,
onChange={(e: any) => { onChange={(e: any) => {
setTeam(e.value); setTeam(e.value);
}} }}
value={options.filter(d => d.value === team)} value={options.find(d => d.value === team)}
styles={colourStyles} styles={colourStyles}
classNamePrefix="teamSelect" classNamePrefix="teamSelect"
options={options} options={options}

View File

@ -0,0 +1,23 @@
import React from 'react';
import styled from 'styled-components';
import { TaskActivityData, ActivityType } from 'shared/generated/graphql';
type ActivityMessageProps = {
type: ActivityType;
data: Array<TaskActivityData>;
};
function getVariable(data: Array<TaskActivityData>, name: string) {
const target = data.find(d => d.name === name);
return target ? target.value : null;
}
const ActivityMessage: React.FC<ActivityMessageProps> = ({ type, data }) => {
switch (type) {
case ActivityType.TaskAdded:
return <>`added this task to ${getVariable(data, 'TaskGroup')}`</>;
}
return <h1>hello</h1>;
};
export default ActivityMessage;

View File

@ -537,25 +537,26 @@ export const ActivityItem = styled.div`
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
display: flex;
`; `;
export const ActivityItemHeader = styled.div` export const ActivityItemHeader = styled.div`
display: flex; display: flex;
flex-direction: column;
padding-left: 8px;
`; `;
export const ActivityItemHeaderUser = styled(TaskAssignee)` export const ActivityItemHeaderUser = styled(TaskAssignee)``;
margin-right: 4px;
`;
export const ActivityItemHeaderTitle = styled.div` export const ActivityItemHeaderTitle = styled.div`
margin-left: 4px;
line-height: 18px;
display: flex; display: flex;
align-items: center; align-items: center;
color: ${props => props.theme.colors.text.primary};
padding-bottom: 2px;
`; `;
export const ActivityItemHeaderTitleName = styled.span` export const ActivityItemHeaderTitleName = styled.span`
color: ${props => props.theme.colors.text.primary};
font-weight: 500; font-weight: 500;
padding-right: 2px;
`; `;
export const ActivityItemTimestamp = styled.span<{ margin: number }>` export const ActivityItemTimestamp = styled.span<{ margin: number }>`

View File

@ -19,8 +19,12 @@ import styled from 'styled-components';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import ActivityMessage from './ActivityMessage';
import Task from 'shared/icons/Task'; import Task from 'shared/icons/Task';
import { import {
ActivityItemHeader,
ActivityItemTimestamp,
ActivityItem,
TaskDetailLabel, TaskDetailLabel,
CommentContainer, CommentContainer,
MetaDetailContent, MetaDetailContent,
@ -67,9 +71,13 @@ import {
CommentInnerWrapper, CommentInnerWrapper,
ActivitySection, ActivitySection,
TaskDetailsEditor, TaskDetailsEditor,
ActivityItemHeaderUser,
ActivityItemHeaderTitle,
ActivityItemHeaderTitleName,
} from './Styles'; } from './Styles';
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist'; import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
import onDragEnd from './onDragEnd'; import onDragEnd from './onDragEnd';
import TaskAssignee from 'shared/components/TaskAssignee';
const ChecklistContainer = styled.div``; const ChecklistContainer = styled.div``;
@ -425,7 +433,36 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TabBarSection> <TabBarSection>
<TabBarItem>Activity</TabBarItem> <TabBarItem>Activity</TabBarItem>
</TabBarSection> </TabBarSection>
<ActivitySection /> <ActivitySection>
{task.activity &&
task.activity.map(activity => (
<ActivityItem>
<ActivityItemHeaderUser
size={32}
member={{
id: activity.causedBy.id,
fullName: activity.causedBy.fullName,
profileIcon: activity.causedBy.profileIcon
? activity.causedBy.profileIcon
: {
url: null,
initials: activity.causedBy.fullName.charAt(0),
bgColor: '#fff',
},
}}
/>
<ActivityItemHeader>
<ActivityItemHeaderTitle>
<ActivityItemHeaderTitleName>{activity.causedBy.fullName}</ActivityItemHeaderTitleName>
<ActivityMessage type={activity.type} data={activity.data} />
</ActivityItemHeaderTitle>
<ActivityItemTimestamp margin={0}>
{dayjs(activity.createdAt).format('MMM D [at] h:mm A')}
</ActivityItemTimestamp>
</ActivityItemHeader>
</ActivityItem>
))}
</ActivitySection>
</InnerContentContainer> </InnerContentContainer>
<CommentContainer> <CommentContainer>
{me && ( {me && (

View File

@ -168,6 +168,41 @@ export type TaskBadges = {
checklist?: Maybe<ChecklistBadge>; checklist?: Maybe<ChecklistBadge>;
}; };
export type CausedBy = {
__typename?: 'CausedBy';
id: Scalars['ID'];
fullName: Scalars['String'];
profileIcon?: Maybe<ProfileIcon>;
};
export type TaskActivityData = {
__typename?: 'TaskActivityData';
name: Scalars['String'];
value: Scalars['String'];
};
export enum ActivityType {
TaskAdded = 'TASK_ADDED',
TaskMoved = 'TASK_MOVED',
TaskMarkedComplete = 'TASK_MARKED_COMPLETE',
TaskMarkedIncomplete = 'TASK_MARKED_INCOMPLETE',
TaskDueDateChanged = 'TASK_DUE_DATE_CHANGED',
TaskDueDateAdded = 'TASK_DUE_DATE_ADDED',
TaskDueDateRemoved = 'TASK_DUE_DATE_REMOVED',
TaskChecklistChanged = 'TASK_CHECKLIST_CHANGED',
TaskChecklistAdded = 'TASK_CHECKLIST_ADDED',
TaskChecklistRemoved = 'TASK_CHECKLIST_REMOVED'
}
export type TaskActivity = {
__typename?: 'TaskActivity';
id: Scalars['ID'];
type: ActivityType;
data: Array<TaskActivityData>;
causedBy: CausedBy;
createdAt: Scalars['Time'];
};
export type Task = { export type Task = {
__typename?: 'Task'; __typename?: 'Task';
id: Scalars['ID']; id: Scalars['ID'];
@ -183,6 +218,7 @@ export type Task = {
labels: Array<TaskLabel>; labels: Array<TaskLabel>;
checklists: Array<TaskChecklist>; checklists: Array<TaskChecklist>;
badges: TaskBadges; badges: TaskBadges;
activity: Array<TaskActivity>;
}; };
export type Organization = { export type Organization = {
@ -209,6 +245,11 @@ export type TaskChecklist = {
items: Array<TaskChecklistItem>; items: Array<TaskChecklistItem>;
}; };
export enum ShareStatus {
Invited = 'INVITED',
Joined = 'JOINED'
}
export enum RoleLevel { export enum RoleLevel {
Admin = 'ADMIN', Admin = 'ADMIN',
Member = 'MEMBER' Member = 'MEMBER'
@ -1050,17 +1091,16 @@ export type DeleteInvitedUserAccountPayload = {
}; };
export type MemberSearchFilter = { export type MemberSearchFilter = {
SearchFilter: Scalars['String']; searchFilter: Scalars['String'];
projectID?: Maybe<Scalars['UUID']>; projectID?: Maybe<Scalars['UUID']>;
}; };
export type MemberSearchResult = { export type MemberSearchResult = {
__typename?: 'MemberSearchResult'; __typename?: 'MemberSearchResult';
similarity: Scalars['Int']; similarity: Scalars['Int'];
user: UserAccount; id: Scalars['String'];
confirmed: Scalars['Boolean']; user?: Maybe<UserAccount>;
invited: Scalars['Boolean']; status: ShareStatus;
joined: Scalars['Boolean'];
}; };
export type UpdateUserInfoPayload = { export type UpdateUserInfoPayload = {
@ -1344,7 +1384,21 @@ export type FindTaskQuery = (
& { taskGroup: ( & { taskGroup: (
{ __typename?: 'TaskGroup' } { __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name'> & Pick<TaskGroup, 'id' | 'name'>
), badges: ( ), activity: Array<(
{ __typename?: 'TaskActivity' }
& Pick<TaskActivity, 'id' | 'type' | 'createdAt'>
& { causedBy: (
{ __typename?: 'CausedBy' }
& Pick<CausedBy, 'id' | 'fullName'>
& { profileIcon?: Maybe<(
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'initials' | 'bgColor' | 'url'>
)> }
), data: Array<(
{ __typename?: 'TaskActivityData' }
& Pick<TaskActivityData, 'name' | 'value'>
)> }
)>, badges: (
{ __typename?: 'TaskBadges' } { __typename?: 'TaskBadges' }
& { checklist?: Maybe<( & { checklist?: Maybe<(
{ __typename?: 'ChecklistBadge' } { __typename?: 'ChecklistBadge' }
@ -2825,6 +2879,24 @@ export const FindTaskDocument = gql`
id id
name name
} }
activity {
id
type
causedBy {
id
fullName
profileIcon {
initials
bgColor
url
}
}
createdAt
data {
name
value
}
}
badges { badges {
checklist { checklist {
total total

View File

@ -10,6 +10,24 @@ query findTask($taskID: UUID!) {
id id
name name
} }
activity {
id
type
causedBy {
id
fullName
profileIcon {
initials
bgColor
url
}
}
createdAt
data {
name
value
}
}
badges { badges {
checklist { checklist {
total total

View File

@ -9,7 +9,7 @@ export function updateApolloCache<T>(
update: UpdateCacheFn<T>, update: UpdateCacheFn<T>,
variables?: object, variables?: object,
) { ) {
let queryArgs: DataProxy.Query<any>; let queryArgs: DataProxy.Query<any, any>;
if (variables) { if (variables) {
queryArgs = { queryArgs = {
query: document, query: document,

View File

@ -1,3 +1,10 @@
type ProjectLabel = {
id: string;
createdDate: string;
name?: string | null;
labelColor: LabelColor;
};
type ProfileIcon = { type ProfileIcon = {
url?: string | null; url?: string | null;
initials?: string | null; initials?: string | null;
@ -56,6 +63,24 @@ type TaskBadges = {
checklist?: ChecklistBadge | null; checklist?: ChecklistBadge | null;
}; };
type TaskActivityData = {
name: string;
value: string;
};
type CausedBy = {
id: string;
fullName: string;
profileIcon?: null | ProfileIcon;
};
type TaskActivity = {
id: string;
type: any;
data: Array<TaskActivityData>;
causedBy: CausedBy;
createdAt: string;
};
type Task = { type Task = {
id: string; id: string;
taskGroup: InnerTaskGroup; taskGroup: InnerTaskGroup;
@ -69,6 +94,7 @@ type Task = {
description?: string | null; description?: string | null;
assigned?: Array<TaskUser>; assigned?: Array<TaskUser>;
checklists?: Array<TaskChecklist> | null; checklists?: Array<TaskChecklist> | null;
activity?: Array<TaskActivity> | null;
}; };
type Project = { type Project = {
@ -89,10 +115,3 @@ type Team = {
name: string; name: string;
createdAt: string; createdAt: string;
}; };
type ProjectLabel = {
id: string;
createdDate: string;
name?: string | null;
labelColor: LabelColor;
};

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ package db
import ( import (
"database/sql" "database/sql"
"encoding/json"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -103,6 +104,22 @@ type Task struct {
CompletedAt sql.NullTime `json:"completed_at"` CompletedAt sql.NullTime `json:"completed_at"`
} }
type TaskActivity struct {
TaskActivityID uuid.UUID `json:"task_activity_id"`
Active bool `json:"active"`
TaskID uuid.UUID `json:"task_id"`
CreatedAt time.Time `json:"created_at"`
CausedBy uuid.UUID `json:"caused_by"`
ActivityTypeID int32 `json:"activity_type_id"`
Data json.RawMessage `json:"data"`
}
type TaskActivityType struct {
TaskActivityTypeID int32 `json:"task_activity_type_id"`
Code string `json:"code"`
Template string `json:"template"`
}
type TaskAssigned struct { type TaskAssigned struct {
TaskAssignedID uuid.UUID `json:"task_assigned_id"` TaskAssignedID uuid.UUID `json:"task_assigned_id"`
TaskID uuid.UUID `json:"task_id"` TaskID uuid.UUID `json:"task_id"`

View File

@ -23,6 +23,7 @@ type Querier interface {
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
CreateSystemOption(ctx context.Context, arg CreateSystemOptionParams) (SystemOption, error) CreateSystemOption(ctx context.Context, arg CreateSystemOptionParams) (SystemOption, error)
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
CreateTaskActivity(ctx context.Context, arg CreateTaskActivityParams) (TaskActivity, error)
CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (Task, error) CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (Task, error)
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error) CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error) CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
@ -55,6 +56,7 @@ type Querier interface {
DeleteTeamMember(ctx context.Context, arg DeleteTeamMemberParams) error DeleteTeamMember(ctx context.Context, arg DeleteTeamMemberParams) error
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
DeleteUserAccountInvitedForEmail(ctx context.Context, email string) error DeleteUserAccountInvitedForEmail(ctx context.Context, email string) error
GetActivityForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskActivity, error)
GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error) GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error)
GetAllOrganizations(ctx context.Context) ([]Organization, error) GetAllOrganizations(ctx context.Context) ([]Organization, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
@ -74,6 +76,7 @@ type Querier interface {
GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error) GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
GetLabelColors(ctx context.Context) ([]LabelColor, error) GetLabelColors(ctx context.Context) ([]LabelColor, error)
GetLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) (GetLastMoveForTaskIDRow, error)
GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error)
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
@ -113,6 +116,7 @@ type Querier interface {
GetTeamRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetTeamRolesForUserIDRow, error) GetTeamRolesForUserID(ctx context.Context, userID uuid.UUID) ([]GetTeamRolesForUserIDRow, error)
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error) GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
GetTeamsForUserIDWhereAdmin(ctx context.Context, userID uuid.UUID) ([]Team, error) GetTeamsForUserIDWhereAdmin(ctx context.Context, userID uuid.UUID) ([]Team, error)
GetTemplateForActivityID(ctx context.Context, taskActivityTypeID int32) (string, error)
GetUserAccountByEmail(ctx context.Context, email string) (UserAccount, error) GetUserAccountByEmail(ctx context.Context, email string) (UserAccount, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
@ -120,6 +124,7 @@ type Querier interface {
HasActiveUser(ctx context.Context) (bool, error) HasActiveUser(ctx context.Context) (bool, error)
HasAnyUser(ctx context.Context) (bool, error) HasAnyUser(ctx context.Context) (bool, error)
SetFirstUserActive(ctx context.Context) (UserAccount, error) SetFirstUserActive(ctx context.Context) (UserAccount, error)
SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error) SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error) SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)

View File

@ -0,0 +1,22 @@
-- name: CreateTaskActivity :one
INSERT INTO task_activity (task_id, caused_by, created_at, activity_type_id, data)
VALUES ($1, $2, $3, $4, $5) RETURNING *;
-- name: GetActivityForTaskID :many
SELECT * FROM task_activity WHERE task_id = $1 AND active = true;
-- name: GetTemplateForActivityID :one
SELECT template FROM task_activity_type WHERE task_activity_type_id = $1;
-- name: GetLastMoveForTaskID :one
SELECT active, created_at, data->>'CurTaskGroupID' AS cur_task_group_id, data->>'PrevTaskGroupID' AS prev_task_group_id FROM task_activity
WHERE task_id = $1 AND activity_type_id = 2 AND created_at >= NOW() - INTERVAL '5 minutes'
ORDER BY created_at DESC LIMIT 1;
-- name: SetInactiveLastMoveForTaskID :exec
UPDATE task_activity SET active = false WHERE task_activity_id = (
SELECT task_activity_id FROM task_activity AS ta
WHERE ta.activity_type_id = 2 AND ta.task_id = $1
AND ta.created_at >= NOW() - INTERVAL '5 minutes'
ORDER BY created_at DESC LIMIT 1
);

View File

@ -0,0 +1,131 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_activity.sql
package db
import (
"context"
"encoding/json"
"time"
"github.com/google/uuid"
)
const createTaskActivity = `-- name: CreateTaskActivity :one
INSERT INTO task_activity (task_id, caused_by, created_at, activity_type_id, data)
VALUES ($1, $2, $3, $4, $5) RETURNING task_activity_id, active, task_id, created_at, caused_by, activity_type_id, data
`
type CreateTaskActivityParams struct {
TaskID uuid.UUID `json:"task_id"`
CausedBy uuid.UUID `json:"caused_by"`
CreatedAt time.Time `json:"created_at"`
ActivityTypeID int32 `json:"activity_type_id"`
Data json.RawMessage `json:"data"`
}
func (q *Queries) CreateTaskActivity(ctx context.Context, arg CreateTaskActivityParams) (TaskActivity, error) {
row := q.db.QueryRowContext(ctx, createTaskActivity,
arg.TaskID,
arg.CausedBy,
arg.CreatedAt,
arg.ActivityTypeID,
arg.Data,
)
var i TaskActivity
err := row.Scan(
&i.TaskActivityID,
&i.Active,
&i.TaskID,
&i.CreatedAt,
&i.CausedBy,
&i.ActivityTypeID,
&i.Data,
)
return i, err
}
const getActivityForTaskID = `-- name: GetActivityForTaskID :many
SELECT task_activity_id, active, task_id, created_at, caused_by, activity_type_id, data FROM task_activity WHERE task_id = $1 AND active = true
`
func (q *Queries) GetActivityForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskActivity, error) {
rows, err := q.db.QueryContext(ctx, getActivityForTaskID, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TaskActivity
for rows.Next() {
var i TaskActivity
if err := rows.Scan(
&i.TaskActivityID,
&i.Active,
&i.TaskID,
&i.CreatedAt,
&i.CausedBy,
&i.ActivityTypeID,
&i.Data,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLastMoveForTaskID = `-- name: GetLastMoveForTaskID :one
SELECT active, created_at, data->>'CurTaskGroupID' AS cur_task_group_id, data->>'PrevTaskGroupID' AS prev_task_group_id FROM task_activity
WHERE task_id = $1 AND activity_type_id = 2 AND created_at >= NOW() - INTERVAL '5 minutes'
ORDER BY created_at DESC LIMIT 1
`
type GetLastMoveForTaskIDRow struct {
Active bool `json:"active"`
CreatedAt time.Time `json:"created_at"`
CurTaskGroupID interface{} `json:"cur_task_group_id"`
PrevTaskGroupID interface{} `json:"prev_task_group_id"`
}
func (q *Queries) GetLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) (GetLastMoveForTaskIDRow, error) {
row := q.db.QueryRowContext(ctx, getLastMoveForTaskID, taskID)
var i GetLastMoveForTaskIDRow
err := row.Scan(
&i.Active,
&i.CreatedAt,
&i.CurTaskGroupID,
&i.PrevTaskGroupID,
)
return i, err
}
const getTemplateForActivityID = `-- name: GetTemplateForActivityID :one
SELECT template FROM task_activity_type WHERE task_activity_type_id = $1
`
func (q *Queries) GetTemplateForActivityID(ctx context.Context, taskActivityTypeID int32) (string, error) {
row := q.db.QueryRowContext(ctx, getTemplateForActivityID, taskActivityTypeID)
var template string
err := row.Scan(&template)
return template, err
}
const setInactiveLastMoveForTaskID = `-- name: SetInactiveLastMoveForTaskID :exec
UPDATE task_activity SET active = false WHERE task_activity_id = (
SELECT task_activity_id FROM task_activity AS ta
WHERE ta.activity_type_id = 2 AND ta.task_id = $1
AND ta.created_at >= NOW() - INTERVAL '5 minutes'
ORDER BY created_at DESC LIMIT 1
)
`
func (q *Queries) SetInactiveLastMoveForTaskID(ctx context.Context, taskID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, setInactiveLastMoveForTaskID, taskID)
return err
}

View File

@ -47,6 +47,7 @@ type ResolverRoot interface {
Query() QueryResolver Query() QueryResolver
RefreshToken() RefreshTokenResolver RefreshToken() RefreshTokenResolver
Task() TaskResolver Task() TaskResolver
TaskActivity() TaskActivityResolver
TaskChecklist() TaskChecklistResolver TaskChecklist() TaskChecklistResolver
TaskChecklistItem() TaskChecklistItemResolver TaskChecklistItem() TaskChecklistItemResolver
TaskGroup() TaskGroupResolver TaskGroup() TaskGroupResolver
@ -60,6 +61,12 @@ type DirectiveRoot struct {
} }
type ComplexityRoot struct { type ComplexityRoot struct {
CausedBy struct {
FullName func(childComplexity int) int
ID func(childComplexity int) int
ProfileIcon func(childComplexity int) int
}
ChecklistBadge struct { ChecklistBadge struct {
Complete func(childComplexity int) int Complete func(childComplexity int) int
Total func(childComplexity int) int Total func(childComplexity int) int
@ -346,6 +353,7 @@ type ComplexityRoot struct {
} }
Task struct { Task struct {
Activity func(childComplexity int) int
Assigned func(childComplexity int) int Assigned func(childComplexity int) int
Badges func(childComplexity int) int Badges func(childComplexity int) int
Checklists func(childComplexity int) int Checklists func(childComplexity int) int
@ -361,6 +369,19 @@ type ComplexityRoot struct {
TaskGroup func(childComplexity int) int TaskGroup func(childComplexity int) int
} }
TaskActivity struct {
CausedBy func(childComplexity int) int
CreatedAt func(childComplexity int) int
Data func(childComplexity int) int
ID func(childComplexity int) int
Type func(childComplexity int) int
}
TaskActivityData struct {
Name func(childComplexity int) int
Value func(childComplexity int) int
}
TaskBadges struct { TaskBadges struct {
Checklist func(childComplexity int) int Checklist func(childComplexity int) int
} }
@ -583,6 +604,13 @@ type TaskResolver interface {
Labels(ctx context.Context, obj *db.Task) ([]db.TaskLabel, error) Labels(ctx context.Context, obj *db.Task) ([]db.TaskLabel, error)
Checklists(ctx context.Context, obj *db.Task) ([]db.TaskChecklist, error) Checklists(ctx context.Context, obj *db.Task) ([]db.TaskChecklist, error)
Badges(ctx context.Context, obj *db.Task) (*TaskBadges, error) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, error)
Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error)
}
type TaskActivityResolver interface {
ID(ctx context.Context, obj *db.TaskActivity) (uuid.UUID, error)
Type(ctx context.Context, obj *db.TaskActivity) (ActivityType, error)
Data(ctx context.Context, obj *db.TaskActivity) ([]TaskActivityData, error)
CausedBy(ctx context.Context, obj *db.TaskActivity) (*CausedBy, error)
} }
type TaskChecklistResolver interface { type TaskChecklistResolver interface {
ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error) ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error)
@ -634,6 +662,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
_ = ec _ = ec
switch typeName + "." + field { switch typeName + "." + field {
case "CausedBy.fullName":
if e.complexity.CausedBy.FullName == nil {
break
}
return e.complexity.CausedBy.FullName(childComplexity), true
case "CausedBy.id":
if e.complexity.CausedBy.ID == nil {
break
}
return e.complexity.CausedBy.ID(childComplexity), true
case "CausedBy.profileIcon":
if e.complexity.CausedBy.ProfileIcon == nil {
break
}
return e.complexity.CausedBy.ProfileIcon(childComplexity), true
case "ChecklistBadge.complete": case "ChecklistBadge.complete":
if e.complexity.ChecklistBadge.Complete == nil { if e.complexity.ChecklistBadge.Complete == nil {
break break
@ -2126,6 +2175,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.SortTaskGroupPayload.Tasks(childComplexity), true return e.complexity.SortTaskGroupPayload.Tasks(childComplexity), true
case "Task.activity":
if e.complexity.Task.Activity == nil {
break
}
return e.complexity.Task.Activity(childComplexity), true
case "Task.assigned": case "Task.assigned":
if e.complexity.Task.Assigned == nil { if e.complexity.Task.Assigned == nil {
break break
@ -2217,6 +2273,55 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Task.TaskGroup(childComplexity), true return e.complexity.Task.TaskGroup(childComplexity), true
case "TaskActivity.causedBy":
if e.complexity.TaskActivity.CausedBy == nil {
break
}
return e.complexity.TaskActivity.CausedBy(childComplexity), true
case "TaskActivity.createdAt":
if e.complexity.TaskActivity.CreatedAt == nil {
break
}
return e.complexity.TaskActivity.CreatedAt(childComplexity), true
case "TaskActivity.data":
if e.complexity.TaskActivity.Data == nil {
break
}
return e.complexity.TaskActivity.Data(childComplexity), true
case "TaskActivity.id":
if e.complexity.TaskActivity.ID == nil {
break
}
return e.complexity.TaskActivity.ID(childComplexity), true
case "TaskActivity.type":
if e.complexity.TaskActivity.Type == nil {
break
}
return e.complexity.TaskActivity.Type(childComplexity), true
case "TaskActivityData.name":
if e.complexity.TaskActivityData.Name == nil {
break
}
return e.complexity.TaskActivityData.Name(childComplexity), true
case "TaskActivityData.value":
if e.complexity.TaskActivityData.Value == nil {
break
}
return e.complexity.TaskActivityData.Value(childComplexity), true
case "TaskBadges.checklist": case "TaskBadges.checklist":
if e.complexity.TaskBadges.Checklist == nil { if e.complexity.TaskBadges.Checklist == nil {
break break
@ -2796,6 +2901,38 @@ type TaskBadges {
checklist: ChecklistBadge checklist: ChecklistBadge
} }
type CausedBy {
id: ID!
fullName: String!
profileIcon: ProfileIcon
}
type TaskActivityData {
name: String!
value: String!
}
enum ActivityType {
TASK_ADDED
TASK_MOVED
TASK_MARKED_COMPLETE
TASK_MARKED_INCOMPLETE
TASK_DUE_DATE_CHANGED
TASK_DUE_DATE_ADDED
TASK_DUE_DATE_REMOVED
TASK_CHECKLIST_CHANGED
TASK_CHECKLIST_ADDED
TASK_CHECKLIST_REMOVED
}
type TaskActivity {
id: ID!
type: ActivityType!
data: [TaskActivityData!]!
causedBy: CausedBy!
createdAt: Time!
}
type Task { type Task {
id: ID! id: ID!
taskGroup: TaskGroup! taskGroup: TaskGroup!
@ -2810,6 +2947,7 @@ type Task {
labels: [TaskLabel!]! labels: [TaskLabel!]!
checklists: [TaskChecklist!]! checklists: [TaskChecklist!]!
badges: TaskBadges! badges: TaskBadges!
activity: [TaskActivity!]!
} }
type Organization { type Organization {
@ -4432,6 +4570,105 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
// region **************************** field.gotpl ***************************** // region **************************** field.gotpl *****************************
func (ec *executionContext) _CausedBy_id(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CausedBy",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(uuid.UUID)
fc.Result = res
return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
}
func (ec *executionContext) _CausedBy_fullName(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CausedBy",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.FullName, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _CausedBy_profileIcon(ctx context.Context, field graphql.CollectedField, obj *CausedBy) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CausedBy",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ProfileIcon, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*ProfileIcon)
fc.Result = res
return ec.marshalOProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx, field.Selections, res)
}
func (ec *executionContext) _ChecklistBadge_complete(ctx context.Context, field graphql.CollectedField, obj *ChecklistBadge) (ret graphql.Marshaler) { func (ec *executionContext) _ChecklistBadge_complete(ctx context.Context, field graphql.CollectedField, obj *ChecklistBadge) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -12775,6 +13012,278 @@ func (ec *executionContext) _Task_badges(ctx context.Context, field graphql.Coll
return ec.marshalNTaskBadges2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx, field.Selections, res) return ec.marshalNTaskBadges2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx, field.Selections, res)
} }
func (ec *executionContext) _Task_activity(ctx context.Context, field graphql.CollectedField, obj *db.Task) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Task",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Task().Activity(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]db.TaskActivity)
fc.Result = res
return ec.marshalNTaskActivity2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivityᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivity_id(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivity",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskActivity().ID(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(uuid.UUID)
fc.Result = res
return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivity_type(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivity",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskActivity().Type(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(ActivityType)
fc.Result = res
return ec.marshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivity_data(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivity",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskActivity().Data(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]TaskActivityData)
fc.Result = res
return ec.marshalNTaskActivityData2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityDataᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivity_causedBy(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivity",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskActivity().CausedBy(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*CausedBy)
fc.Result = res
return ec.marshalNCausedBy2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivity_createdAt(ctx context.Context, field graphql.CollectedField, obj *db.TaskActivity) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivity",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.CreatedAt, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(time.Time)
fc.Result = res
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivityData_name(ctx context.Context, field graphql.CollectedField, obj *TaskActivityData) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivityData",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Name, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskActivityData_value(ctx context.Context, field graphql.CollectedField, obj *TaskActivityData) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskActivityData",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Value, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskBadges_checklist(ctx context.Context, field graphql.CollectedField, obj *TaskBadges) (ret graphql.Marshaler) { func (ec *executionContext) _TaskBadges_checklist(ctx context.Context, field graphql.CollectedField, obj *TaskBadges) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -17153,6 +17662,40 @@ func (ec *executionContext) unmarshalInputUpdateUserRole(ctx context.Context, ob
// region **************************** object.gotpl **************************** // region **************************** object.gotpl ****************************
var causedByImplementors = []string{"CausedBy"}
func (ec *executionContext) _CausedBy(ctx context.Context, sel ast.SelectionSet, obj *CausedBy) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, causedByImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("CausedBy")
case "id":
out.Values[i] = ec._CausedBy_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "fullName":
out.Values[i] = ec._CausedBy_fullName(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "profileIcon":
out.Values[i] = ec._CausedBy_profileIcon(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var checklistBadgeImplementors = []string{"ChecklistBadge"} var checklistBadgeImplementors = []string{"ChecklistBadge"}
func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.SelectionSet, obj *ChecklistBadge) graphql.Marshaler { func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.SelectionSet, obj *ChecklistBadge) graphql.Marshaler {
@ -19265,6 +19808,135 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj
} }
return res return res
}) })
case "activity":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Task_activity(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var taskActivityImplementors = []string{"TaskActivity"}
func (ec *executionContext) _TaskActivity(ctx context.Context, sel ast.SelectionSet, obj *db.TaskActivity) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, taskActivityImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("TaskActivity")
case "id":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskActivity_id(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "type":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskActivity_type(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "data":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskActivity_data(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "causedBy":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskActivity_causedBy(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "createdAt":
out.Values[i] = ec._TaskActivity_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var taskActivityDataImplementors = []string{"TaskActivityData"}
func (ec *executionContext) _TaskActivityData(ctx context.Context, sel ast.SelectionSet, obj *TaskActivityData) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, taskActivityDataImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("TaskActivityData")
case "name":
out.Values[i] = ec._TaskActivityData_name(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "value":
out.Values[i] = ec._TaskActivityData_value(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -20324,6 +20996,15 @@ func (ec *executionContext) marshalNActionType2githubᚗcomᚋjordanknottᚋtask
return v return v
} }
func (ec *executionContext) unmarshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx context.Context, v interface{}) (ActivityType, error) {
var res ActivityType
return res, res.UnmarshalGQL(v)
}
func (ec *executionContext) marshalNActivityType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActivityType(ctx context.Context, sel ast.SelectionSet, v ActivityType) graphql.Marshaler {
return v
}
func (ec *executionContext) unmarshalNActorType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActorType(ctx context.Context, v interface{}) (ActorType, error) { func (ec *executionContext) unmarshalNActorType2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐActorType(ctx context.Context, v interface{}) (ActorType, error) {
var res ActorType var res ActorType
return res, res.UnmarshalGQL(v) return res, res.UnmarshalGQL(v)
@ -20347,6 +21028,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
return res return res
} }
func (ec *executionContext) marshalNCausedBy2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx context.Context, sel ast.SelectionSet, v CausedBy) graphql.Marshaler {
return ec._CausedBy(ctx, sel, &v)
}
func (ec *executionContext) marshalNCausedBy2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCausedBy(ctx context.Context, sel ast.SelectionSet, v *CausedBy) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._CausedBy(ctx, sel, v)
}
func (ec *executionContext) unmarshalNCreateTaskChecklist2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskChecklist(ctx context.Context, v interface{}) (CreateTaskChecklist, error) { func (ec *executionContext) unmarshalNCreateTaskChecklist2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskChecklist(ctx context.Context, v interface{}) (CreateTaskChecklist, error) {
return ec.unmarshalInputCreateTaskChecklist(ctx, v) return ec.unmarshalInputCreateTaskChecklist(ctx, v)
} }
@ -21530,6 +22225,88 @@ func (ec *executionContext) marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋtaskcaf
return ec._Task(ctx, sel, v) return ec._Task(ctx, sel, v)
} }
func (ec *executionContext) marshalNTaskActivity2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivity(ctx context.Context, sel ast.SelectionSet, v db.TaskActivity) graphql.Marshaler {
return ec._TaskActivity(ctx, sel, &v)
}
func (ec *executionContext) marshalNTaskActivity2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivityᚄ(ctx context.Context, sel ast.SelectionSet, v []db.TaskActivity) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNTaskActivity2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskActivity(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalNTaskActivityData2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityData(ctx context.Context, sel ast.SelectionSet, v TaskActivityData) graphql.Marshaler {
return ec._TaskActivityData(ctx, sel, &v)
}
func (ec *executionContext) marshalNTaskActivityData2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityDataᚄ(ctx context.Context, sel ast.SelectionSet, v []TaskActivityData) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNTaskActivityData2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskActivityData(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalNTaskBadges2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx context.Context, sel ast.SelectionSet, v TaskBadges) graphql.Marshaler { func (ec *executionContext) marshalNTaskBadges2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐTaskBadges(ctx context.Context, sel ast.SelectionSet, v TaskBadges) graphql.Marshaler {
return ec._TaskBadges(ctx, sel, &v) return ec._TaskBadges(ctx, sel, &v)
} }
@ -22458,6 +23235,17 @@ func (ec *executionContext) marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknott
return ec._ChecklistBadge(ctx, sel, v) return ec._ChecklistBadge(ctx, sel, v)
} }
func (ec *executionContext) marshalOProfileIcon2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v ProfileIcon) graphql.Marshaler {
return ec._ProfileIcon(ctx, sel, &v)
}
func (ec *executionContext) marshalOProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v *ProfileIcon) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._ProfileIcon(ctx, sel, v)
}
func (ec *executionContext) unmarshalOProjectsFilter2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProjectsFilter(ctx context.Context, v interface{}) (ProjectsFilter, error) { func (ec *executionContext) unmarshalOProjectsFilter2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐProjectsFilter(ctx context.Context, v interface{}) (ProjectsFilter, error) {
return ec.unmarshalInputProjectsFilter(ctx, v) return ec.unmarshalInputProjectsFilter(ctx, v)
} }

View File

@ -255,3 +255,15 @@ func GetActionType(actionType int32) ActionType {
panic("Not a valid entity type!") panic("Not a valid entity type!")
} }
} }
type MemberType string
const (
MemberTypeInvited MemberType = "INVITED"
MemberTypeJoined MemberType = "JOINED"
)
type MasterEntry struct {
MemberType MemberType
ID uuid.UUID
}

View File

@ -41,3 +41,7 @@ func GetMemberList(ctx context.Context, r db.Repository, user db.UserAccount) (*
return &MemberList{Teams: teams, Projects: projects}, nil return &MemberList{Teams: teams, Projects: projects}, nil
} }
type ActivityData struct {
Data map[string]string
}

View File

@ -22,6 +22,12 @@ type AssignTaskInput struct {
UserID uuid.UUID `json:"userID"` UserID uuid.UUID `json:"userID"`
} }
type CausedBy struct {
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
ProfileIcon *ProfileIcon `json:"profileIcon"`
}
type ChecklistBadge struct { type ChecklistBadge struct {
Complete int `json:"complete"` Complete int `json:"complete"`
Total int `json:"total"` Total int `json:"total"`
@ -374,6 +380,11 @@ type SortTaskGroupPayload struct {
Tasks []db.Task `json:"tasks"` Tasks []db.Task `json:"tasks"`
} }
type TaskActivityData struct {
Name string `json:"name"`
Value string `json:"value"`
}
type TaskBadges struct { type TaskBadges struct {
Checklist *ChecklistBadge `json:"checklist"` Checklist *ChecklistBadge `json:"checklist"`
} }
@ -615,6 +626,63 @@ func (e ActionType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String())) fmt.Fprint(w, strconv.Quote(e.String()))
} }
type ActivityType string
const (
ActivityTypeTaskAdded ActivityType = "TASK_ADDED"
ActivityTypeTaskMoved ActivityType = "TASK_MOVED"
ActivityTypeTaskMarkedComplete ActivityType = "TASK_MARKED_COMPLETE"
ActivityTypeTaskMarkedIncomplete ActivityType = "TASK_MARKED_INCOMPLETE"
ActivityTypeTaskDueDateChanged ActivityType = "TASK_DUE_DATE_CHANGED"
ActivityTypeTaskDueDateAdded ActivityType = "TASK_DUE_DATE_ADDED"
ActivityTypeTaskDueDateRemoved ActivityType = "TASK_DUE_DATE_REMOVED"
ActivityTypeTaskChecklistChanged ActivityType = "TASK_CHECKLIST_CHANGED"
ActivityTypeTaskChecklistAdded ActivityType = "TASK_CHECKLIST_ADDED"
ActivityTypeTaskChecklistRemoved ActivityType = "TASK_CHECKLIST_REMOVED"
)
var AllActivityType = []ActivityType{
ActivityTypeTaskAdded,
ActivityTypeTaskMoved,
ActivityTypeTaskMarkedComplete,
ActivityTypeTaskMarkedIncomplete,
ActivityTypeTaskDueDateChanged,
ActivityTypeTaskDueDateAdded,
ActivityTypeTaskDueDateRemoved,
ActivityTypeTaskChecklistChanged,
ActivityTypeTaskChecklistAdded,
ActivityTypeTaskChecklistRemoved,
}
func (e ActivityType) IsValid() bool {
switch e {
case ActivityTypeTaskAdded, ActivityTypeTaskMoved, ActivityTypeTaskMarkedComplete, ActivityTypeTaskMarkedIncomplete, ActivityTypeTaskDueDateChanged, ActivityTypeTaskDueDateAdded, ActivityTypeTaskDueDateRemoved, ActivityTypeTaskChecklistChanged, ActivityTypeTaskChecklistAdded, ActivityTypeTaskChecklistRemoved:
return true
}
return false
}
func (e ActivityType) String() string {
return string(e)
}
func (e *ActivityType) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ActivityType(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ActivityType", str)
}
return nil
}
func (e ActivityType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type ActorType string type ActorType string
const ( const (

View File

@ -135,6 +135,38 @@ type TaskBadges {
checklist: ChecklistBadge checklist: ChecklistBadge
} }
type CausedBy {
id: ID!
fullName: String!
profileIcon: ProfileIcon
}
type TaskActivityData {
name: String!
value: String!
}
enum ActivityType {
TASK_ADDED
TASK_MOVED
TASK_MARKED_COMPLETE
TASK_MARKED_INCOMPLETE
TASK_DUE_DATE_CHANGED
TASK_DUE_DATE_ADDED
TASK_DUE_DATE_REMOVED
TASK_CHECKLIST_CHANGED
TASK_CHECKLIST_ADDED
TASK_CHECKLIST_REMOVED
}
type TaskActivity {
id: ID!
type: ActivityType!
data: [TaskActivityData!]!
causedBy: CausedBy!
createdAt: Time!
}
type Task { type Task {
id: ID! id: ID!
taskGroup: TaskGroup! taskGroup: TaskGroup!
@ -149,6 +181,7 @@ type Task {
labels: [TaskLabel!]! labels: [TaskLabel!]!
checklists: [TaskChecklist!]! checklists: [TaskChecklist!]!
badges: TaskBadges! badges: TaskBadges!
activity: [TaskActivity!]!
} }
type Organization { type Organization {

View File

@ -7,7 +7,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"database/sql" "database/sql"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@ -17,12 +17,11 @@ import (
"github.com/jordanknott/taskcafe/internal/db" "github.com/jordanknott/taskcafe/internal/db"
"github.com/jordanknott/taskcafe/internal/logger" "github.com/jordanknott/taskcafe/internal/logger"
"github.com/lithammer/fuzzysearch/fuzzy" "github.com/lithammer/fuzzysearch/fuzzy"
gomail "gopkg.in/mail.v2"
hermes "github.com/matcornic/hermes/v2" hermes "github.com/matcornic/hermes/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
gomail "gopkg.in/mail.v2"
) )
func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.UUID, error) { func (r *labelColorResolver) ID(ctx context.Context, obj *db.LabelColor) (uuid.UUID, error) {
@ -363,6 +362,26 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.T
createdAt := time.Now().UTC() createdAt := time.Now().UTC()
logger.New(ctx).WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task") logger.New(ctx).WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task")
task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position}) task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position})
if err != nil {
logger.New(ctx).WithError(err).Error("issue while creating task")
return &db.Task{}, err
}
taskGroup, err := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID)
if err != nil {
logger.New(ctx).WithError(err).Error("issue while creating task")
return &db.Task{}, err
}
data := map[string]string{
"TaskGroup": taskGroup.Name,
}
d, err := json.Marshal(data)
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
TaskID: task.TaskID,
Data: d,
CreatedAt: createdAt,
ActivityTypeID: 1,
})
if err != nil { if err != nil {
logger.New(ctx).WithError(err).Error("issue while creating task") logger.New(ctx).WithError(err).Error("issue while creating task")
return &db.Task{}, err return &db.Task{}, err
@ -387,12 +406,44 @@ func (r *mutationResolver) UpdateTaskDescription(ctx context.Context, input Upda
} }
func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error) { func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error) {
userID, _ := GetUserID(ctx)
previousTask, err := r.Repository.GetTaskByID(ctx, input.TaskID) previousTask, err := r.Repository.GetTaskByID(ctx, input.TaskID)
if err != nil { if err != nil {
return &UpdateTaskLocationPayload{}, err return &UpdateTaskLocationPayload{}, err
} }
task, err := r.Repository.UpdateTaskLocation(ctx, db.UpdateTaskLocationParams{input.TaskID, input.TaskGroupID, input.Position}) task, _ := r.Repository.UpdateTaskLocation(ctx, db.UpdateTaskLocationParams{TaskID: input.TaskID, TaskGroupID: input.TaskGroupID, Position: input.Position})
if previousTask.TaskGroupID != input.TaskGroupID {
skipAndDelete := false
lastMove, err := r.Repository.GetLastMoveForTaskID(ctx, input.TaskID)
if err == nil {
if lastMove.Active && lastMove.PrevTaskGroupID == input.TaskGroupID.String() {
skipAndDelete = true
}
}
if skipAndDelete {
_ = r.Repository.SetInactiveLastMoveForTaskID(ctx, input.TaskID)
} else {
prevTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, previousTask.TaskGroupID)
curTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID)
data := map[string]string{
"PrevTaskGroup": prevTaskGroup.Name,
"PrevTaskGroupID": prevTaskGroup.TaskGroupID.String(),
"CurTaskGroup": curTaskGroup.Name,
"CurTaskGroupID": curTaskGroup.TaskGroupID.String(),
}
createdAt := time.Now().UTC()
d, _ := json.Marshal(data)
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
TaskID: task.TaskID,
Data: d,
CausedBy: userID,
CreatedAt: createdAt,
ActivityTypeID: 2,
})
}
}
return &UpdateTaskLocationPayload{Task: &task, PreviousTaskGroupID: previousTask.TaskGroupID}, err return &UpdateTaskLocationPayload{Task: &task, PreviousTaskGroupID: previousTask.TaskGroupID}, err
} }
@ -1558,6 +1609,72 @@ func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, e
return &TaskBadges{Checklist: &ChecklistBadge{Total: total, Complete: complete}}, nil return &TaskBadges{Checklist: &ChecklistBadge{Total: total, Complete: complete}}, nil
} }
func (r *taskResolver) Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error) {
activity, err := r.Repository.GetActivityForTaskID(ctx, obj.TaskID)
if err == sql.ErrNoRows {
return []db.TaskActivity{}, nil
}
return activity, err
}
func (r *taskActivityResolver) ID(ctx context.Context, obj *db.TaskActivity) (uuid.UUID, error) {
return obj.TaskActivityID, nil
}
func (r *taskActivityResolver) Type(ctx context.Context, obj *db.TaskActivity) (ActivityType, error) {
switch obj.ActivityTypeID {
case 1:
return ActivityTypeTaskAdded, nil
case 2:
return ActivityTypeTaskMoved, nil
case 3:
return ActivityTypeTaskMarkedComplete, nil
case 4:
return ActivityTypeTaskMarkedIncomplete, nil
case 5:
return ActivityTypeTaskDueDateChanged, nil
case 6:
return ActivityTypeTaskDueDateAdded, nil
case 7:
return ActivityTypeTaskDueDateRemoved, nil
case 8:
return ActivityTypeTaskChecklistChanged, nil
case 9:
return ActivityTypeTaskChecklistAdded, nil
case 10:
return ActivityTypeTaskChecklistRemoved, nil
default:
return ActivityTypeTaskAdded, errors.New("unknown type")
}
}
func (r *taskActivityResolver) Data(ctx context.Context, obj *db.TaskActivity) ([]TaskActivityData, error) {
var data map[string]string
_ = json.Unmarshal(obj.Data, &data)
activity := []TaskActivityData{}
for name, value := range data {
activity = append(activity, TaskActivityData{
Name: name,
Value: value,
})
}
return activity, nil
}
func (r *taskActivityResolver) CausedBy(ctx context.Context, obj *db.TaskActivity) (*CausedBy, error) {
user, err := r.Repository.GetUserAccountByID(ctx, obj.CausedBy)
var url *string
if user.ProfileAvatarUrl.Valid {
url = &user.ProfileAvatarUrl.String
}
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
return &CausedBy{
ID: obj.CausedBy,
FullName: user.FullName,
ProfileIcon: profileIcon,
}, err
}
func (r *taskChecklistResolver) ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error) { func (r *taskChecklistResolver) ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error) {
return obj.TaskChecklistID, nil return obj.TaskChecklistID, nil
} }
@ -1619,6 +1736,7 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
if user.ProfileAvatarUrl.Valid { if user.ProfileAvatarUrl.Valid {
url = &user.ProfileAvatarUrl.String url = &user.ProfileAvatarUrl.String
} }
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: obj.TeamID}) role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: obj.TeamID})
if err != nil { if err != nil {
logger.New(ctx).WithError(err).Error("get role for projet member by user ID") logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
@ -1634,7 +1752,6 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
return members, err return members, err
} }
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon, members = append(members, Member{ID: user.UserID, FullName: user.FullName, ProfileIcon: profileIcon,
Username: user.Username, Owned: ownedList, Member: memberList, Role: &db.Role{Code: role.Code, Name: role.Name}, Username: user.Username, Owned: ownedList, Member: memberList, Role: &db.Role{Code: role.Code, Name: role.Name},
}) })
@ -1724,6 +1841,9 @@ func (r *Resolver) RefreshToken() RefreshTokenResolver { return &refreshTokenRes
// Task returns TaskResolver implementation. // Task returns TaskResolver implementation.
func (r *Resolver) Task() TaskResolver { return &taskResolver{r} } func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
// TaskActivity returns TaskActivityResolver implementation.
func (r *Resolver) TaskActivity() TaskActivityResolver { return &taskActivityResolver{r} }
// TaskChecklist returns TaskChecklistResolver implementation. // TaskChecklist returns TaskChecklistResolver implementation.
func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} } func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} }
@ -1753,27 +1873,10 @@ type projectLabelResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
type refreshTokenResolver struct{ *Resolver } type refreshTokenResolver struct{ *Resolver }
type taskResolver struct{ *Resolver } type taskResolver struct{ *Resolver }
type taskActivityResolver struct{ *Resolver }
type taskChecklistResolver struct{ *Resolver } type taskChecklistResolver struct{ *Resolver }
type taskChecklistItemResolver struct{ *Resolver } type taskChecklistItemResolver struct{ *Resolver }
type taskGroupResolver struct{ *Resolver } type taskGroupResolver struct{ *Resolver }
type taskLabelResolver struct{ *Resolver } type taskLabelResolver struct{ *Resolver }
type teamResolver struct{ *Resolver } type teamResolver struct{ *Resolver }
type userAccountResolver struct{ *Resolver } type userAccountResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
type MemberType string
const (
MemberTypeInvited MemberType = "INVITED"
MemberTypeJoined MemberType = "JOINED"
)
type MasterEntry struct {
MemberType MemberType
ID uuid.UUID
}

View File

@ -135,6 +135,38 @@ type TaskBadges {
checklist: ChecklistBadge checklist: ChecklistBadge
} }
type CausedBy {
id: ID!
fullName: String!
profileIcon: ProfileIcon
}
type TaskActivityData {
name: String!
value: String!
}
enum ActivityType {
TASK_ADDED
TASK_MOVED
TASK_MARKED_COMPLETE
TASK_MARKED_INCOMPLETE
TASK_DUE_DATE_CHANGED
TASK_DUE_DATE_ADDED
TASK_DUE_DATE_REMOVED
TASK_CHECKLIST_CHANGED
TASK_CHECKLIST_ADDED
TASK_CHECKLIST_REMOVED
}
type TaskActivity {
id: ID!
type: ActivityType!
data: [TaskActivityData!]!
causedBy: CausedBy!
createdAt: Time!
}
type Task { type Task {
id: ID! id: ID!
taskGroup: TaskGroup! taskGroup: TaskGroup!
@ -149,6 +181,7 @@ type Task {
labels: [TaskLabel!]! labels: [TaskLabel!]!
checklists: [TaskChecklist!]! checklists: [TaskChecklist!]!
badges: TaskBadges! badges: TaskBadges!
activity: [TaskActivity!]!
} }
type Organization { type Organization {

View File

@ -0,0 +1,28 @@
CREATE TABLE task_activity_type (
task_activity_type_id int PRIMARY KEY,
code text NOT NULL,
template text NOT NULL
);
INSERT INTO task_activity_type (task_activity_type_id, code, template) VALUES
(1, 'task_added_to_task_group', 'added this task to {{ index .Data "TaskGroup" }}'),
(2, 'task_moved_to_task_group', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}'),
(3, 'task_mark_complete', 'marked this task complete'),
(4, 'task_mark_incomplete', 'marked this task incomplete'),
(5, 'task_due_date_changed', 'changed the due date to {{ index .Data "DueDate" }}'),
(6, 'task_due_date_added', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}'),
(7, 'task_due_date_removed', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}'),
(8, 'task_checklist_changed', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}'),
(9, 'task_checklist_added', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}'),
(10, 'task_checklist_removed', 'moved this task from {{ index .Data "PrevTaskGroup" }} to {{ index .Data "CurTaskGroup"}}');
CREATE TABLE task_activity (
task_activity_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
active boolean NOT NULL DEFAULT true,
task_id uuid NOT NULL REFERENCES task(task_id),
created_at timestamptz NOT NULL,
caused_by uuid NOT NULL,
activity_type_id int NOT NULL REFERENCES task_activity_type(task_activity_type_id),
data jsonb
);