@@ -139,7 +139,7 @@ function getNavItems(organizationId?: string): NavItem[] {
|
||||
title: "Tasks",
|
||||
icon: Inbox,
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/dashboard`
|
||||
? `/organizations/${organizationId}/tasks`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -27,12 +27,14 @@ import { ListRiskPage } from "./risks/ListRiskPage";
|
||||
import ShowRiskView from "./risks/ShowRiskView";
|
||||
import { ListVendorPage } from "./vendors/ListVendorPage";
|
||||
import { VendorPage } from "./vendors/VendorPage";
|
||||
import { ListTaskPage } from "./tasks/ListTaskPage";
|
||||
|
||||
export function OrganizationsRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path=":organizationId/*" element={<OrganizationLayout />}>
|
||||
<Route index element={<HomePage />} />
|
||||
<Route path="tasks" element={<ListTaskPage />} />
|
||||
<Route path="people" element={<PeopleListPage />} />
|
||||
<Route path="people/new" element={<NewPeoplePage />} />
|
||||
<Route path="people/:peopleId" element={<PeoplePage />} />
|
||||
|
||||
@@ -982,6 +982,7 @@ function MeasureViewContent({
|
||||
variables: {
|
||||
connections: [`${data.measure.tasks?.__id}`],
|
||||
input: {
|
||||
organizationId: organizationId!,
|
||||
measureId: data.measure.id,
|
||||
name: newTaskName,
|
||||
description: newTaskDescription,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<f037ead461a7a59d39f2e98140e4c72f>>
|
||||
* @generated SignedSource<<b53893b96884bbc49ad8f2dbdb22e2ea>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -13,8 +13,9 @@ export type TaskState = "DONE" | "TODO";
|
||||
export type CreateTaskInput = {
|
||||
assignedToId?: string | null | undefined;
|
||||
description: string;
|
||||
measureId: string;
|
||||
measureId?: string | null | undefined;
|
||||
name: string;
|
||||
organizationId: string;
|
||||
timeEstimate?: any | null | undefined;
|
||||
};
|
||||
export type MeasureViewCreateTaskMutation$variables = {
|
||||
|
||||
43
apps/console/src/pages/organizations/tasks/ListTaskPage.tsx
Normal file
43
apps/console/src/pages/organizations/tasks/ListTaskPage.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
import { Suspense } from "react";
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
import { useLocation } from "react-router";
|
||||
import { ErrorBoundaryWithLocation } from "../ErrorBoundary";
|
||||
|
||||
const ListTaskView = lazy(() => import("./ListTaskView"));
|
||||
|
||||
export function ListTaskViewSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton title="Tasks">
|
||||
<div className="space-y-6">
|
||||
<div className="rounded-xl border bg-level-1 p-4 space-y-4">
|
||||
<div className="h-5 w-32 subtle-bg animate-pulse rounded" />
|
||||
<div className="flex gap-2">
|
||||
<div className="h-10 flex-1 subtle-bg animate-pulse rounded" />
|
||||
<div className="h-10 w-32 subtle-bg animate-pulse rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-[72px] subtle-bg animate-pulse rounded-xl"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export function ListTaskPage() {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Suspense key={location.pathname} fallback={<ListTaskViewSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<ListTaskView />
|
||||
</ErrorBoundaryWithLocation>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
888
apps/console/src/pages/organizations/tasks/ListTaskView.tsx
Normal file
888
apps/console/src/pages/organizations/tasks/ListTaskView.tsx
Normal file
@@ -0,0 +1,888 @@
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import {
|
||||
useQueryLoader,
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useMutation,
|
||||
useRelayEnvironment,
|
||||
fetchQuery,
|
||||
} from "react-relay";
|
||||
import { useSearchParams, useParams } from "react-router";
|
||||
import { PageTemplate } from "@/components/PageTemplate";
|
||||
import { ListTaskViewSkeleton } from "./ListTaskPage";
|
||||
import { ListTaskViewQuery, TaskState } from "./__generated__/ListTaskViewQuery.graphql";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogClose,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
CircleDashed,
|
||||
Circle,
|
||||
Plus,
|
||||
Filter,
|
||||
Search,
|
||||
CornerDownRight,
|
||||
Flame,
|
||||
Building,
|
||||
X,
|
||||
ChevronDown,
|
||||
User,
|
||||
CheckCircle2,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ListTaskViewOrganizationQuery } from "./__generated__/ListTaskViewOrganizationQuery.graphql";
|
||||
|
||||
// Function to format ISO8601 duration to human-readable format
|
||||
const formatDuration = (isoDuration: string | null | undefined): string => {
|
||||
if (!isoDuration || !isoDuration.startsWith("P")) {
|
||||
return isoDuration || "";
|
||||
}
|
||||
|
||||
try {
|
||||
const durationRegex =
|
||||
/P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?/;
|
||||
const matches = isoDuration.match(durationRegex);
|
||||
|
||||
if (!matches) return isoDuration;
|
||||
|
||||
const years = matches[1] ? parseInt(matches[1]) : 0;
|
||||
const months = matches[2] ? parseInt(matches[2]) : 0;
|
||||
const days = matches[3] ? parseInt(matches[3]) : 0;
|
||||
const hours = matches[4] ? parseInt(matches[4]) : 0;
|
||||
const minutes = matches[5] ? parseInt(matches[5]) : 0;
|
||||
const seconds = matches[6] ? parseInt(matches[6]) : 0;
|
||||
|
||||
const parts = [];
|
||||
if (years) parts.push(`${years} ${years === 1 ? "year" : "years"}`);
|
||||
if (months) parts.push(`${months} ${months === 1 ? "month" : "months"}`);
|
||||
if (days) parts.push(`${days} ${days === 1 ? "day" : "days"}`);
|
||||
if (hours) parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`);
|
||||
if (minutes)
|
||||
parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
|
||||
if (seconds)
|
||||
parts.push(`${seconds} ${seconds === 1 ? "second" : "seconds"}`);
|
||||
|
||||
return parts.length > 0 ? parts.join(", ") : "No duration";
|
||||
} catch (error) {
|
||||
console.error("Error parsing duration:", error);
|
||||
return isoDuration;
|
||||
}
|
||||
};
|
||||
|
||||
// Define organization measures query
|
||||
const organizationMeasuresQuery = graphql`
|
||||
query ListTaskViewOrganizationMeasuresQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
measures(first: 100) @connection(key: "Organization__measures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Define the create task mutation
|
||||
const createTaskMutation = graphql`
|
||||
mutation ListTaskViewCreateTaskMutation(
|
||||
$input: CreateTaskInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createTask(input: $input) {
|
||||
taskEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
state
|
||||
timeEstimate
|
||||
measure {
|
||||
id
|
||||
name
|
||||
}
|
||||
assignedTo {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Define the update task mutation
|
||||
const updateTaskStateMutation = graphql`
|
||||
mutation ListTaskViewUpdateTaskStateMutation(
|
||||
$input: UpdateTaskInput!
|
||||
) {
|
||||
updateTask(input: $input) {
|
||||
task {
|
||||
id
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const listTaskViewQuery = graphql`
|
||||
query ListTaskViewQuery($organizationId: ID!) {
|
||||
node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
name
|
||||
tasks(first: 100) @connection(key: "ListTaskView_tasks") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
state
|
||||
timeEstimate
|
||||
measure {
|
||||
id
|
||||
name
|
||||
}
|
||||
assignedTo {
|
||||
id
|
||||
fullName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Define organization members query
|
||||
const organizationQuery = graphql`
|
||||
query ListTaskViewOrganizationQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
peoples(first: 100, orderBy: { direction: ASC, field: FULL_NAME })
|
||||
@connection(key: "ListTaskView_peoples") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
fullName
|
||||
primaryEmailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Badge {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'FIRE' | 'BANK' | string;
|
||||
}
|
||||
|
||||
interface Task {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
state: string;
|
||||
timeEstimate?: number | null;
|
||||
assignedTo?: {
|
||||
id: string;
|
||||
fullName: string;
|
||||
} | null;
|
||||
parent?: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | null;
|
||||
badges?: Badge[];
|
||||
}
|
||||
|
||||
function TaskCard({ task, onClick, onToggleState }: {
|
||||
task: Task;
|
||||
onClick?: (task: Task) => void;
|
||||
onToggleState?: (taskId: string, newState: TaskState) => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="flex justify-between items-center px-6 py-3 border-b border-gray-100 hover:bg-gray-50 cursor-pointer"
|
||||
onClick={() => onClick && onClick(task)}
|
||||
>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div
|
||||
className="flex items-center cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onToggleState) {
|
||||
const newState = task.state === "TODO" ? "DONE" : "TODO";
|
||||
onToggleState(task.id, newState as TaskState);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{task.state === "TODO" ? (
|
||||
<CircleDashed className="w-5 h-5 text-green-500" />
|
||||
) : task.state === "DONE" ? (
|
||||
<CheckCircle2 className="w-5 h-5 text-gray-500" />
|
||||
) : (
|
||||
<Circle className="w-5 h-5 text-gray-300" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm font-medium ${task.state === "DONE" ? "text-gray-500 line-through" : "text-gray-900"}`}>
|
||||
{task.name}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs px-1.5 py-0.5 rounded-lg flex items-center gap-1
|
||||
${task.state === "TODO"
|
||||
? "bg-green-50 text-green-700 border-green-200"
|
||||
: "bg-gray-50 text-gray-700 border-gray-200"}
|
||||
`}
|
||||
>
|
||||
{task.state === "TODO" ? "To Do" : "Done"}
|
||||
</Badge>
|
||||
{task.timeEstimate && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{formatDuration(task.timeEstimate.toString())}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{task.parent && (
|
||||
<div className="flex items-center gap-1 text-sm text-gray-500">
|
||||
<CornerDownRight className="w-3 h-3 text-gray-500" />
|
||||
<span>{task.parent.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex gap-2">
|
||||
{task.badges && task.badges.map((badge) => (
|
||||
<Badge
|
||||
key={badge.id}
|
||||
variant="outline"
|
||||
className="text-xs px-1.5 py-0.5 rounded-lg flex items-center gap-1 text-gray-600 border-gray-200/50"
|
||||
>
|
||||
{badge.type === "FIRE" ? (
|
||||
<Flame className="w-3 h-3 text-gray-600" />
|
||||
) : badge.type === "BANK" ? (
|
||||
<Building className="w-3 h-3 text-gray-600" />
|
||||
) : null}
|
||||
{badge.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
{task.assignedTo && (
|
||||
<Avatar className="h-6 w-6 border border-white shadow-sm">
|
||||
<div className="bg-green-100 h-full w-full rounded-full flex items-center justify-center text-[10px] font-medium text-green-800">
|
||||
{task.assignedTo.fullName.split(' ').map((n: string) => n[0]).join('')}
|
||||
</div>
|
||||
</Avatar>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ListTaskContent({
|
||||
queryRef,
|
||||
organizationQueryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<ListTaskViewQuery>;
|
||||
organizationQueryRef: PreloadedQuery<ListTaskViewOrganizationQuery>;
|
||||
}) {
|
||||
const data = usePreloadedQuery<ListTaskViewQuery>(listTaskViewQuery, queryRef);
|
||||
const organizationData = usePreloadedQuery<ListTaskViewOrganizationQuery>(
|
||||
organizationQuery,
|
||||
organizationQueryRef
|
||||
);
|
||||
|
||||
const organization = data.node;
|
||||
const [isNewTaskOpen, setIsNewTaskOpen] = useState(false);
|
||||
|
||||
// Get organization members
|
||||
const members = organizationData.organization?.peoples?.edges.map(edge => edge.node) || [];
|
||||
|
||||
// Fetch measures
|
||||
const environment = useRelayEnvironment();
|
||||
const [measures, setMeasures] = useState<Array<{id: string, name: string, category: string, description?: string, state?: string}>>([]);
|
||||
const [measuresLoading, setMeasuresLoading] = useState(false);
|
||||
const [measureSearchQuery, setMeasureSearchQuery] = useState("");
|
||||
|
||||
// Task form state
|
||||
const [taskTitle, setTaskTitle] = useState("");
|
||||
const [taskDescription, setTaskDescription] = useState("");
|
||||
const [taskStatus, setTaskStatus] = useState<"TODO" | "DONE">("TODO");
|
||||
const [assignee, setAssignee] = useState<{id: string, fullName: string} | null>(null);
|
||||
const [measureId, setMeasureId] = useState<string | null>(null);
|
||||
const [selectedMeasure, setSelectedMeasure] = useState<{id: string, name: string} | null>(null);
|
||||
const [timeEstimate, setTimeEstimate] = useState<number | null>(null);
|
||||
const [timeUnit, setTimeUnit] = useState<"minutes" | "hours" | "days" | "weeks">("hours");
|
||||
|
||||
// Create task mutation
|
||||
const [createTask, isCreatingTask] = useMutation(createTaskMutation);
|
||||
|
||||
// Update task state mutation
|
||||
const [updateTaskState] = useMutation(updateTaskStateMutation);
|
||||
|
||||
// Fetch measures from the organization
|
||||
useEffect(() => {
|
||||
if (organization?.id) {
|
||||
setMeasuresLoading(true);
|
||||
fetchQuery(
|
||||
environment,
|
||||
organizationMeasuresQuery,
|
||||
{ organizationId: organization.id }
|
||||
).subscribe({
|
||||
next: (data: any) => {
|
||||
if (data.organization?.measures?.edges) {
|
||||
const measuresList = data.organization.measures.edges.map((edge: any) => ({
|
||||
id: edge.node.id,
|
||||
name: edge.node.name,
|
||||
category: edge.node.category,
|
||||
description: edge.node.description,
|
||||
state: edge.node.state
|
||||
}));
|
||||
setMeasures(measuresList);
|
||||
}
|
||||
setMeasuresLoading(false);
|
||||
},
|
||||
error: () => {
|
||||
setMeasuresLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [environment, organization?.id]);
|
||||
|
||||
// Filter measures based on search query
|
||||
const filteredMeasures = measures.filter(measure =>
|
||||
measure.name.toLowerCase().includes(measureSearchQuery.toLowerCase()) ||
|
||||
(measure.description && measure.description.toLowerCase().includes(measureSearchQuery.toLowerCase()))
|
||||
);
|
||||
|
||||
const allTasks = data.node?.tasks?.edges.map((edge) => ({
|
||||
...edge.node,
|
||||
badges: [
|
||||
{ id: "1", name: "Policy violation", type: "FIRE" },
|
||||
{ id: "2", name: "SOC2", type: "BANK" },
|
||||
],
|
||||
parent: edge.node.measure ? { id: edge.node.measure.id, name: edge.node.measure.name } : null,
|
||||
})) || [];
|
||||
|
||||
const todoTasks = allTasks.filter(task => task.state === "TODO");
|
||||
const doneTasks = allTasks.filter(task => task.state === "DONE");
|
||||
|
||||
const handleTaskClick = (task: Task) => {
|
||||
console.log("Task clicked:", task);
|
||||
// Navigate to task detail page
|
||||
// window.location.href = `/organizations/${organizationId}/tasks/${task.id}`;
|
||||
};
|
||||
|
||||
// Function to convert timeEstimate to ISO8601 duration
|
||||
const convertToISODuration = (value: number | null, unit: string): string | null => {
|
||||
if (value === null || value <= 0) return null;
|
||||
|
||||
switch (unit) {
|
||||
case "minutes":
|
||||
return `PT${value}M`;
|
||||
case "hours":
|
||||
return `PT${value}H`;
|
||||
case "days":
|
||||
return `P${value}D`;
|
||||
case "weeks":
|
||||
return `P${value * 7}D`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateTask = () => {
|
||||
if (!taskTitle.trim()) return;
|
||||
|
||||
// Convert timeEstimate to ISO8601 duration
|
||||
const formattedTimeEstimate = convertToISODuration(timeEstimate, timeUnit);
|
||||
|
||||
createTask({
|
||||
variables: {
|
||||
input: {
|
||||
name: taskTitle,
|
||||
description: taskDescription,
|
||||
organizationId: organization?.id || "",
|
||||
measureId: selectedMeasure?.id || null,
|
||||
assignedToId: assignee?.id,
|
||||
timeEstimate: formattedTimeEstimate,
|
||||
},
|
||||
connections: [data.node?.tasks?.__id || ""],
|
||||
},
|
||||
onCompleted: () => {
|
||||
setIsNewTaskOpen(false);
|
||||
resetTaskForm();
|
||||
},
|
||||
onError: error => {
|
||||
console.error("Error creating task:", error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const resetTaskForm = () => {
|
||||
setTaskTitle("");
|
||||
setTaskDescription("");
|
||||
setTaskStatus("TODO");
|
||||
setAssignee(null);
|
||||
setSelectedMeasure(null);
|
||||
setMeasureId(null);
|
||||
setTimeEstimate(null);
|
||||
setTimeUnit("hours");
|
||||
setMeasureSearchQuery("");
|
||||
};
|
||||
|
||||
// Get initials from name
|
||||
const getInitials = (name: string) => {
|
||||
return name.split(' ').map(n => n[0]).join('');
|
||||
};
|
||||
|
||||
// Function to handle toggling task state
|
||||
const handleToggleTaskState = (taskId: string, newState: TaskState) => {
|
||||
updateTaskState({
|
||||
variables: {
|
||||
input: {
|
||||
taskId: taskId,
|
||||
state: newState
|
||||
}
|
||||
},
|
||||
optimisticResponse: {
|
||||
updateTask: {
|
||||
task: {
|
||||
id: taskId,
|
||||
state: newState,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title="Tasks"
|
||||
description="Track your assigned compliance tasks and keep progress on track."
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
className="bg-gray-900 text-white hover:bg-gray-800 gap-1.5"
|
||||
onClick={() => setIsNewTaskOpen(true)}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
New task
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<Card className="border border-gray-100 rounded-xl p-0 overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-gray-100">
|
||||
<Tabs defaultValue="todo" className="w-full">
|
||||
<div className="flex justify-between items-center w-full mb-6">
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger value="todo" className="border-b-2 border-transparent data-[state=active]:border-gray-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleDashed className="w-4 h-4 text-green-500" />
|
||||
<span>To Do</span>
|
||||
<div className="flex items-center justify-center bg-green-100 rounded-full h-6 w-6 text-xs text-green-800">
|
||||
{todoTasks.length}
|
||||
</div>
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="done" className="border-b-2 border-transparent data-[state=active]:border-gray-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<Circle className="w-4 h-4 text-gray-500" />
|
||||
<span>Done</span>
|
||||
<div className="flex items-center justify-center bg-gray-100 rounded-full h-6 w-6 text-xs text-gray-800">
|
||||
{doneTasks.length}
|
||||
</div>
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="all" className="border-b-2 border-transparent data-[state=active]:border-gray-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>All</span>
|
||||
<div className="flex items-center justify-center bg-gray-100 rounded-full h-6 w-6 text-xs">
|
||||
{allTasks.length}
|
||||
</div>
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="todo">
|
||||
{todoTasks.length > 0 ? (
|
||||
todoTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onClick={handleTaskClick}
|
||||
onToggleState={handleToggleTaskState}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
No todo tasks found. Create a new task to get started.
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="done">
|
||||
{doneTasks.length > 0 ? (
|
||||
doneTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onClick={handleTaskClick}
|
||||
onToggleState={handleToggleTaskState}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
No completed tasks found.
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="all">
|
||||
{allTasks.length > 0 ? (
|
||||
allTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onClick={handleTaskClick}
|
||||
onToggleState={handleToggleTaskState}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
No tasks found. Create a new task to get started.
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* New Task Modal */}
|
||||
<Dialog open={isNewTaskOpen} onOpenChange={setIsNewTaskOpen}>
|
||||
<DialogContent className="sm:max-w-[1080px] p-0 rounded-lg overflow-hidden border border-gray-200">
|
||||
{/* Modal Header */}
|
||||
<div className="flex justify-between items-center px-6 py-4 border-b border-[rgba(2,42,2,0.08)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-sm text-gray-500">List of your assets</div>
|
||||
<div className="text-gray-500">
|
||||
<CornerDownRight className="w-3 h-3" />
|
||||
</div>
|
||||
<div className="text-sm font-medium">New task</div>
|
||||
</div>
|
||||
<DialogClose className="rounded-full w-8 h-8 flex items-center justify-center hover:bg-gray-100"/>
|
||||
</div>
|
||||
|
||||
{/* Task Form - Two Column Layout */}
|
||||
<div className="flex">
|
||||
{/* Left Column - Task Details */}
|
||||
<div className="flex-1 border-r border-[rgba(2,42,2,0.08)] p-6">
|
||||
<Input
|
||||
placeholder="Task title"
|
||||
className="text-2xl font-semibold border-none px-0 mb-4 w-full"
|
||||
value={taskTitle}
|
||||
onChange={(e) => setTaskTitle(e.target.value)}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder="Add description..."
|
||||
className="resize-none border-none px-0 text-base w-full min-h-[400px]"
|
||||
value={taskDescription}
|
||||
onChange={(e) => setTaskDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Properties */}
|
||||
<div className="w-[400px] p-6">
|
||||
<h3 className="text-base font-medium text-gray-900 mb-4">Properties</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Status */}
|
||||
<div className="flex justify-between items-center border-b border-[rgba(2,42,2,0.08)] py-3">
|
||||
<Label className="text-sm font-medium text-gray-500">Status</Label>
|
||||
<div className="bg-[rgba(0,39,0,0.05)] px-[10px] py-[6px] rounded-lg flex items-center gap-1.5">
|
||||
<img
|
||||
src="/images/radio-unchecked.svg"
|
||||
alt="Radio"
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span className="text-sm font-medium">To do</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assignee */}
|
||||
<div className="flex justify-between items-center border-b border-[rgba(2,42,2,0.08)] py-3">
|
||||
<Label className="text-sm font-medium text-gray-500">Assignee</Label>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="bg-[rgba(0,39,0,0.05)] px-[10px] py-[6px] rounded-lg flex items-center gap-1.5 cursor-pointer">
|
||||
{assignee ? (
|
||||
<>
|
||||
<Avatar className="h-4 w-4">
|
||||
<div className="bg-blue-100 h-full w-full rounded-full flex items-center justify-center text-[7px] font-medium text-blue-800">
|
||||
{getInitials(assignee.fullName)}
|
||||
</div>
|
||||
</Avatar>
|
||||
<span className="text-sm font-medium">{assignee.fullName}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<User className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-sm font-medium">Unassigned</span>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown className="h-3 w-3 text-gray-500 ml-1" />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[220px] max-h-[300px] overflow-y-auto">
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setAssignee(null)}
|
||||
>
|
||||
<User className="h-4 w-4 text-gray-500" />
|
||||
<span>Unassigned</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{members.map(member => (
|
||||
<DropdownMenuItem
|
||||
key={member.id}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => setAssignee({id: member.id, fullName: member.fullName})}
|
||||
>
|
||||
<Avatar className="h-4 w-4">
|
||||
<div className="bg-blue-100 h-full w-full rounded-full flex items-center justify-center text-[7px] font-medium text-blue-800">
|
||||
{getInitials(member.fullName)}
|
||||
</div>
|
||||
</Avatar>
|
||||
<span>{member.fullName}</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{/* Measure */}
|
||||
<div className="flex justify-between items-center border-b border-[rgba(2,42,2,0.08)] py-3">
|
||||
<Label className="text-sm font-medium text-gray-500">Measure</Label>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="bg-[rgba(0,39,0,0.05)] px-[10px] py-[6px] rounded-lg flex items-center gap-1.5 cursor-pointer">
|
||||
{selectedMeasure ? (
|
||||
<span className="text-sm font-medium">{selectedMeasure.name}</span>
|
||||
) : (
|
||||
<span className="text-sm font-medium">No measure</span>
|
||||
)}
|
||||
<ChevronDown className="h-3 w-3 text-gray-500 ml-1" />
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[320px] max-h-[300px] overflow-y-auto">
|
||||
<div className="p-2">
|
||||
<div className="relative mb-2">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-500" />
|
||||
<Input
|
||||
placeholder="Search measures..."
|
||||
value={measureSearchQuery}
|
||||
onChange={(e) => setMeasureSearchQuery(e.target.value)}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => {
|
||||
setSelectedMeasure(null);
|
||||
setMeasureId(null);
|
||||
}}
|
||||
>
|
||||
<span>No measure</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{measuresLoading ? (
|
||||
<div className="p-3 text-center text-sm text-gray-500">
|
||||
Loading measures...
|
||||
</div>
|
||||
) : filteredMeasures.length > 0 ? (
|
||||
filteredMeasures.map(measure => (
|
||||
<DropdownMenuItem
|
||||
key={measure.id}
|
||||
className="flex items-start gap-2 cursor-pointer py-2"
|
||||
onClick={() => {
|
||||
setSelectedMeasure({id: measure.id, name: measure.name});
|
||||
setMeasureId(measure.id);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{measure.name}</span>
|
||||
{measure.category && (
|
||||
<span className="text-xs text-gray-500">{measure.category}</span>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
) : (
|
||||
<div className="p-3 text-center text-sm text-gray-500">
|
||||
No measures found
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{/* Time estimate */}
|
||||
<div className="flex justify-between items-center border-b border-[rgba(2,42,2,0.08)] py-3">
|
||||
<Label className="text-sm font-medium text-gray-500">Time estimate</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
{timeEstimate === null ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 px-2"
|
||||
onClick={() => setTimeEstimate(0)}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
value={timeEstimate}
|
||||
onChange={(e) => setTimeEstimate(parseInt(e.target.value) || 0)}
|
||||
className="w-16 h-8 text-sm"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 flex items-center gap-1"
|
||||
>
|
||||
<span>{timeUnit}</span>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTimeUnit("minutes")}>
|
||||
minutes
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTimeUnit("hours")}>
|
||||
hours
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTimeUnit("days")}>
|
||||
days
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTimeUnit("weeks")}>
|
||||
weeks
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 p-0"
|
||||
onClick={() => setTimeEstimate(null)}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal Footer */}
|
||||
<div className="flex justify-between items-center px-6 py-4 border-t border-[rgba(2,42,2,0.08)]">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 flex items-center gap-1.5 border-[rgba(2,42,2,0.08)]"
|
||||
>
|
||||
<img src="/images/attachment.svg" alt="Attachment" className="w-4 h-4" />
|
||||
Upload evidence
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 border-[rgba(2,42,2,0.08)]"
|
||||
onClick={() => setIsNewTaskOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="h-8 bg-gray-900 text-white"
|
||||
onClick={handleCreateTask}
|
||||
disabled={!taskTitle.trim()}
|
||||
>
|
||||
Create task
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ListTaskView() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<ListTaskViewQuery>(listTaskViewQuery);
|
||||
const [organizationQueryRef, loadOrganizationQuery] =
|
||||
useQueryLoader<ListTaskViewOrganizationQuery>(organizationQuery);
|
||||
|
||||
const { organizationId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({ organizationId: organizationId! });
|
||||
loadOrganizationQuery({ organizationId: organizationId! });
|
||||
}, [loadQuery, loadOrganizationQuery, organizationId]);
|
||||
|
||||
if (!queryRef || !organizationQueryRef) {
|
||||
return <ListTaskViewSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ListTaskViewSkeleton />}>
|
||||
<ListTaskContent queryRef={queryRef} organizationQueryRef={organizationQueryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
238
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewCreateTaskMutation.graphql.ts
generated
Normal file
238
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewCreateTaskMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* @generated SignedSource<<84a35602a155b0f1fa8504cf32c10932>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type TaskState = "DONE" | "TODO";
|
||||
export type CreateTaskInput = {
|
||||
assignedToId?: string | null | undefined;
|
||||
description: string;
|
||||
measureId?: string | null | undefined;
|
||||
name: string;
|
||||
organizationId: string;
|
||||
timeEstimate?: any | null | undefined;
|
||||
};
|
||||
export type ListTaskViewCreateTaskMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateTaskInput;
|
||||
};
|
||||
export type ListTaskViewCreateTaskMutation$data = {
|
||||
readonly createTask: {
|
||||
readonly taskEdge: {
|
||||
readonly node: {
|
||||
readonly assignedTo: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
} | null | undefined;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly measure: {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
} | null | undefined;
|
||||
readonly name: string;
|
||||
readonly state: TaskState;
|
||||
readonly timeEstimate: any | null | undefined;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ListTaskViewCreateTaskMutation = {
|
||||
response: ListTaskViewCreateTaskMutation$data;
|
||||
variables: ListTaskViewCreateTaskMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TaskEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "taskEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Task",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "timeEstimate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "measure",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "assignedTo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ListTaskViewCreateTaskMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateTaskPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createTask",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "ListTaskViewCreateTaskMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateTaskPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createTask",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "taskEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "7b9680f637411402ff51239a43b44a14",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ListTaskViewCreateTaskMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ListTaskViewCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n state\n timeEstimate\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "43ccaf49d31ec84dcf3b6b3f2e3a851c";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* @generated SignedSource<<5d866d20a606b1fb34d64ad47aa76f78>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
export type ListTaskViewOrganizationMeasuresQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type ListTaskViewOrganizationMeasuresQuery$data = {
|
||||
readonly organization: {
|
||||
readonly id: string;
|
||||
readonly measures?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ListTaskViewOrganizationMeasuresQuery = {
|
||||
response: ListTaskViewOrganizationMeasuresQuery$data;
|
||||
variables: ListTaskViewOrganizationMeasuresQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
v5 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ListTaskViewOrganizationMeasuresQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "measures",
|
||||
"args": null,
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__Organization__measures_connection",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ListTaskViewOrganizationMeasuresQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "measures",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": "measures(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "Organization__measures",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measures"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "1986791d30c729854184fc15725ff5f0",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organization",
|
||||
"measures"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ListTaskViewOrganizationMeasuresQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ListTaskViewOrganizationMeasuresQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n measures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3a4008ec2a42d13418f83e9ddd460b34";
|
||||
|
||||
export default node;
|
||||
267
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewOrganizationQuery.graphql.ts
generated
Normal file
267
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewOrganizationQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* @generated SignedSource<<c922e0dcd99fb4bc9d1d26954781b945>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ListTaskViewOrganizationQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type ListTaskViewOrganizationQuery$data = {
|
||||
readonly organization: {
|
||||
readonly id: string;
|
||||
readonly peoples?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly primaryEmailAddress: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ListTaskViewOrganizationQuery = {
|
||||
response: ListTaskViewOrganizationQuery$data;
|
||||
variables: ListTaskViewOrganizationQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "FULL_NAME"
|
||||
}
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PeopleEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
},
|
||||
(v3/*: any*/)
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ListTaskViewOrganizationQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "peoples",
|
||||
"args": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__ListTaskView_peoples_connection",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": "__ListTaskView_peoples_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ListTaskViewOrganizationQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "peoples",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
],
|
||||
"handle": "connection",
|
||||
"key": "ListTaskView_peoples",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "peoples"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "a2ecca8e88fa80fc9828ddc2a04d18ac",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organization",
|
||||
"peoples"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ListTaskViewOrganizationQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ListTaskViewOrganizationQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "736b443f4010e86687954adcaed6f3ab";
|
||||
|
||||
export default node;
|
||||
328
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewQuery.graphql.ts
generated
Normal file
328
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* @generated SignedSource<<7732676a00f7ed0f6817555b68540aca>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type TaskState = "DONE" | "TODO";
|
||||
export type ListTaskViewQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type ListTaskViewQuery$data = {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly name?: string;
|
||||
readonly tasks?: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly assignedTo: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
} | null | undefined;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly measure: {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
} | null | undefined;
|
||||
readonly name: string;
|
||||
readonly state: TaskState;
|
||||
readonly timeEstimate: any | null | undefined;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ListTaskViewQuery = {
|
||||
response: ListTaskViewQuery$data;
|
||||
variables: ListTaskViewQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TaskEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Task",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "timeEstimate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "measure",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "assignedTo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ListTaskViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": "tasks",
|
||||
"args": null,
|
||||
"concreteType": "TaskConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__ListTaskView_tasks_connection",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ListTaskViewQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "TaskConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "tasks",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"storageKey": "tasks(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "ListTaskView_tasks",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "tasks"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "52b577b08c305249855293949d6e2339",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"node",
|
||||
"tasks"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "ListTaskViewQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ListTaskViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n name\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n timeEstimate\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "5ca091444c547744068f2b2a83cb31df";
|
||||
|
||||
export default node;
|
||||
118
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewUpdateTaskStateMutation.graphql.ts
generated
Normal file
118
apps/console/src/pages/organizations/tasks/__generated__/ListTaskViewUpdateTaskStateMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @generated SignedSource<<7098e326d01cc6831f293726096a7275>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type TaskState = "DONE" | "TODO";
|
||||
export type UpdateTaskInput = {
|
||||
description?: string | null | undefined;
|
||||
name?: string | null | undefined;
|
||||
state?: TaskState | null | undefined;
|
||||
taskId: string;
|
||||
timeEstimate?: any | null | undefined;
|
||||
};
|
||||
export type ListTaskViewUpdateTaskStateMutation$variables = {
|
||||
input: UpdateTaskInput;
|
||||
};
|
||||
export type ListTaskViewUpdateTaskStateMutation$data = {
|
||||
readonly updateTask: {
|
||||
readonly task: {
|
||||
readonly id: string;
|
||||
readonly state: TaskState;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type ListTaskViewUpdateTaskStateMutation = {
|
||||
response: ListTaskViewUpdateTaskStateMutation$data;
|
||||
variables: ListTaskViewUpdateTaskStateMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "UpdateTaskPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "updateTask",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Task",
|
||||
"kind": "LinkedField",
|
||||
"name": "task",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ListTaskViewUpdateTaskStateMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ListTaskViewUpdateTaskStateMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "ddeeb9b6aa991efa6e305491ef360537",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ListTaskViewUpdateTaskStateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ListTaskViewUpdateTaskStateMutation(\n $input: UpdateTaskInput!\n) {\n updateTask(input: $input) {\n task {\n id\n state\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "46d32aeba7c467fd48291f43baaf0b6a";
|
||||
|
||||
export default node;
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
type (
|
||||
Measure struct {
|
||||
ID gid.GID `db:"id"`
|
||||
TenantID gid.TenantID `db:"tenant_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Category string `db:"category"`
|
||||
Name string `db:"name"`
|
||||
@@ -82,7 +81,6 @@ WITH msrs AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -146,7 +144,6 @@ WITH mtgtns AS (
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -191,7 +188,6 @@ func (m *Measures) LoadByOrganizationID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -237,7 +233,6 @@ func (m *Measure) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
category,
|
||||
name,
|
||||
@@ -311,7 +306,6 @@ ON CONFLICT (organization_id, reference_id) DO UPDATE SET
|
||||
category = @category,
|
||||
updated_at = @updated_at
|
||||
RETURNING
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
category,
|
||||
8
pkg/coredata/migrations/20250505T171400Z.sql
Normal file
8
pkg/coredata/migrations/20250505T171400Z.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE tasks ADD COLUMN organization_id TEXT REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
UPDATE tasks
|
||||
SET organization_id = m.organization_id::text
|
||||
FROM measures m
|
||||
WHERE tasks.measure_id = m.id;
|
||||
|
||||
ALTER TABLE tasks ALTER COLUMN organization_id SET NOT NULL;
|
||||
@@ -166,8 +166,9 @@ WHERE %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"organization_id": organizationID}
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,16 +29,17 @@ import (
|
||||
|
||||
type (
|
||||
Task struct {
|
||||
ID gid.GID `db:"id"`
|
||||
MeasureID gid.GID `db:"measure_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
State TaskState `db:"state"`
|
||||
ReferenceID string `db:"reference_id"`
|
||||
TimeEstimate *time.Duration `db:"time_estimate"`
|
||||
AssignedToID *gid.GID `db:"assigned_to"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
MeasureID *gid.GID `db:"measure_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
State TaskState `db:"state"`
|
||||
ReferenceID string `db:"reference_id"`
|
||||
TimeEstimate *time.Duration `db:"time_estimate"`
|
||||
AssignedToID *gid.GID `db:"assigned_to"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Tasks []*Task
|
||||
@@ -62,6 +63,7 @@ func (c *Task) LoadByID(
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -109,6 +111,7 @@ INSERT INTO
|
||||
tasks (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -122,6 +125,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@task_id,
|
||||
@organization_id,
|
||||
@measure_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -135,17 +139,18 @@ VALUES (
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"organization_id": c.OrganizationID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
return err
|
||||
@@ -161,6 +166,7 @@ INSERT INTO
|
||||
tasks (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -174,6 +180,7 @@ INSERT INTO
|
||||
VALUES (
|
||||
@tenant_id,
|
||||
@task_id,
|
||||
@organization_id,
|
||||
@measure_id,
|
||||
@name,
|
||||
@description,
|
||||
@@ -190,6 +197,7 @@ ON CONFLICT (measure_id, reference_id) DO UPDATE SET
|
||||
updated_at = @updated_at
|
||||
RETURNING
|
||||
id,
|
||||
organization_id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
@@ -202,17 +210,18 @@ RETURNING
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"task_id": c.ID,
|
||||
"organization_id": c.OrganizationID,
|
||||
"measure_id": c.MeasureID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"reference_id": c.ReferenceID,
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"assigned_to": c.AssignedToID,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
@@ -229,6 +238,54 @@ RETURNING
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Tasks) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[TaskOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
reference_id,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
tasks
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query tasks: %w", err)
|
||||
}
|
||||
|
||||
tasks, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Task])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect tasks: %w", err)
|
||||
}
|
||||
|
||||
*c = tasks
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Tasks) LoadByMeasureID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -240,6 +297,7 @@ func (c *Tasks) LoadByMeasureID(
|
||||
SELECT
|
||||
id,
|
||||
measure_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
state,
|
||||
@@ -288,7 +346,8 @@ SET
|
||||
description = @description,
|
||||
state = @state,
|
||||
time_estimate = @time_estimate,
|
||||
updated_at = @updated_at
|
||||
updated_at = @updated_at,
|
||||
assigned_to = @assigned_to
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
`
|
||||
@@ -301,6 +360,7 @@ WHERE %s
|
||||
"state": c.State,
|
||||
"time_estimate": c.TimeEstimate,
|
||||
"updated_at": c.UpdatedAt,
|
||||
"assigned_to": c.AssignedToID,
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -309,102 +369,6 @@ WHERE %s
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Task) AssignTo(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
assignTo gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE tasks SET
|
||||
assigned_to = @assigned_to,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
RETURNING
|
||||
id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
reference_id,
|
||||
state,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"task_id": c.ID,
|
||||
"assigned_to": assignTo,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query tasks: %w", err)
|
||||
}
|
||||
|
||||
task, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Task])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect tasks: %w", err)
|
||||
}
|
||||
|
||||
*c = task
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Task) Unassign(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE tasks SET
|
||||
assigned_to = NULL,
|
||||
updated_at = @updated_at
|
||||
WHERE %s
|
||||
AND id = @task_id
|
||||
RETURNING
|
||||
id,
|
||||
measure_id,
|
||||
name,
|
||||
description,
|
||||
reference_id,
|
||||
state,
|
||||
time_estimate,
|
||||
assigned_to,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"task_id": c.ID,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query tasks: %w", err)
|
||||
}
|
||||
|
||||
task, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Task])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect tasks: %w", err)
|
||||
}
|
||||
|
||||
*c = task
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Task) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -119,8 +119,12 @@ func (s EvidenceService) Request(
|
||||
return fmt.Errorf("cannot load task: %w", err)
|
||||
}
|
||||
|
||||
if task.MeasureID == nil {
|
||||
return fmt.Errorf("task %q has no measure", req.TaskID)
|
||||
}
|
||||
|
||||
evidence.TaskID = req.TaskID
|
||||
evidence.MeasureID = task.MeasureID
|
||||
evidence.MeasureID = *task.MeasureID
|
||||
} else if req.MeasureID != nil {
|
||||
evidence.MeasureID = *req.MeasureID
|
||||
} else {
|
||||
@@ -298,7 +302,11 @@ func (s EvidenceService) UploadTaskEvidence(
|
||||
return fmt.Errorf("cannot load task %q: %w", req.TaskID, err)
|
||||
}
|
||||
|
||||
evidence.MeasureID = task.MeasureID
|
||||
if task.MeasureID == nil {
|
||||
return fmt.Errorf("task %q has no measure", req.TaskID)
|
||||
}
|
||||
|
||||
evidence.MeasureID = *task.MeasureID
|
||||
|
||||
if err := evidence.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert evidence: %w", err)
|
||||
|
||||
@@ -168,14 +168,15 @@ func (s MeasureService) Import(
|
||||
taskID := gid.New(organizationID.TenantID(), coredata.TaskEntityType)
|
||||
|
||||
task := &coredata.Task{
|
||||
ID: taskID,
|
||||
MeasureID: measure.ID,
|
||||
Name: req.Measures[i].Tasks[j].Name,
|
||||
Description: req.Measures[i].Tasks[j].Description,
|
||||
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
|
||||
State: coredata.TaskStateTodo,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: taskID,
|
||||
OrganizationID: organizationID,
|
||||
MeasureID: &measure.ID,
|
||||
Name: req.Measures[i].Tasks[j].Name,
|
||||
Description: req.Measures[i].Tasks[j].Description,
|
||||
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
|
||||
State: coredata.TaskStateTodo,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := task.Upsert(ctx, tx, s.svc.scope); err != nil {
|
||||
@@ -32,11 +32,12 @@ type (
|
||||
}
|
||||
|
||||
CreateTaskRequest struct {
|
||||
MeasureID gid.GID
|
||||
Name string
|
||||
Description string
|
||||
TimeEstimate *time.Duration
|
||||
AssignedToID *gid.GID
|
||||
OrganizationID gid.GID
|
||||
MeasureID *gid.GID
|
||||
Name string
|
||||
Description string
|
||||
TimeEstimate *time.Duration
|
||||
AssignedToID *gid.GID
|
||||
}
|
||||
|
||||
UpdateTaskRequest struct {
|
||||
@@ -61,21 +62,23 @@ func (s TaskService) Create(
|
||||
}
|
||||
|
||||
task := &coredata.Task{
|
||||
ID: taskID,
|
||||
MeasureID: req.MeasureID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
TimeEstimate: req.TimeEstimate,
|
||||
AssignedToID: req.AssignedToID,
|
||||
State: coredata.TaskStateTodo,
|
||||
ReferenceID: "custom-task-" + referenceID.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
ID: taskID,
|
||||
OrganizationID: req.OrganizationID,
|
||||
MeasureID: req.MeasureID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
TimeEstimate: req.TimeEstimate,
|
||||
AssignedToID: req.AssignedToID,
|
||||
State: coredata.TaskStateTodo,
|
||||
ReferenceID: "custom-task-" + referenceID.String(),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
|
||||
if err := task.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert task: %w", err)
|
||||
}
|
||||
@@ -119,7 +122,18 @@ func (s TaskService) Assign(
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return task.AssignTo(ctx, conn, s.svc.scope, assignedToID)
|
||||
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
|
||||
return fmt.Errorf("cannot load task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
task.AssignedToID = &assignedToID
|
||||
task.UpdatedAt = time.Now()
|
||||
|
||||
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot assign task %q to %q: %w", taskID, assignedToID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -133,12 +147,23 @@ func (s TaskService) Unassign(
|
||||
ctx context.Context,
|
||||
taskID gid.GID,
|
||||
) (*coredata.Task, error) {
|
||||
task := &coredata.Task{ID: taskID}
|
||||
task := &coredata.Task{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return task.Unassign(ctx, conn, s.svc.scope)
|
||||
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
|
||||
return fmt.Errorf("cannot load task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
task.AssignedToID = nil
|
||||
task.UpdatedAt = time.Now()
|
||||
|
||||
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot unassign task %q: %w", taskID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -152,7 +177,8 @@ func (s TaskService) Update(
|
||||
ctx context.Context,
|
||||
req UpdateTaskRequest,
|
||||
) (*coredata.Task, error) {
|
||||
task := &coredata.Task{ID: req.TaskID}
|
||||
|
||||
task := &coredata.Task{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
@@ -212,6 +238,26 @@ func (s TaskService) Delete(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s TaskService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.TaskOrderField],
|
||||
) (*page.Page[*coredata.Task, coredata.TaskOrderField], error) {
|
||||
var tasks coredata.Tasks
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return tasks.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(tasks, cursor), nil
|
||||
}
|
||||
|
||||
func (s TaskService) ListForMeasureID(
|
||||
ctx context.Context,
|
||||
measureID gid.GID,
|
||||
|
||||
@@ -461,6 +461,14 @@ type Organization implements Node {
|
||||
orderBy: RiskOrder
|
||||
): RiskConnection! @goField(forceResolver: true)
|
||||
|
||||
tasks(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: TaskOrder
|
||||
): TaskConnection! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -498,6 +506,8 @@ type Vendor implements Node {
|
||||
name: String!
|
||||
description: String
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
complianceReports(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -538,10 +548,8 @@ type VendorComplianceReport implements Node {
|
||||
reportDate: Datetime!
|
||||
validUntil: Datetime
|
||||
reportName: String!
|
||||
|
||||
fileUrl: String! @goField(forceResolver: true)
|
||||
fileSize: Int!
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -551,6 +559,8 @@ type Framework implements Node {
|
||||
name: String!
|
||||
description: String!
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
controls(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -569,6 +579,8 @@ type Control implements Node {
|
||||
name: String!
|
||||
description: String!
|
||||
|
||||
framework: Framework! @goField(forceResolver: true)
|
||||
|
||||
measures(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -640,6 +652,9 @@ type Task implements Node {
|
||||
timeEstimate: Duration
|
||||
assignedTo: People @goField(forceResolver: true)
|
||||
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
measure: Measure @goField(forceResolver: true)
|
||||
|
||||
evidences(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
@@ -663,6 +678,9 @@ type Evidence implements Node {
|
||||
url: String
|
||||
description: String!
|
||||
|
||||
task: Task @goField(forceResolver: true)
|
||||
measure: Measure! @goField(forceResolver: true)
|
||||
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
@@ -673,6 +691,7 @@ type Policy implements Node {
|
||||
description: String!
|
||||
currentPublishedVersion: Int
|
||||
owner: People! @goField(forceResolver: true)
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
versions(
|
||||
first: Int
|
||||
@@ -710,6 +729,7 @@ type Risk implements Node {
|
||||
note: String!
|
||||
|
||||
owner: People @goField(forceResolver: true)
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
measures(
|
||||
first: Int
|
||||
@@ -1140,7 +1160,8 @@ input ImportMeasureInput {
|
||||
}
|
||||
|
||||
input CreateTaskInput {
|
||||
measureId: ID!
|
||||
organizationId: ID!
|
||||
measureId: ID
|
||||
name: String!
|
||||
description: String!
|
||||
timeEstimate: Duration
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@ type Control struct {
|
||||
ReferenceID string `json:"referenceId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -215,11 +216,12 @@ type CreateRiskPolicyMappingPayload struct {
|
||||
}
|
||||
|
||||
type CreateTaskInput struct {
|
||||
MeasureID gid.GID `json:"measureId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
MeasureID *gid.GID `json:"measureId,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
|
||||
}
|
||||
|
||||
type CreateTaskPayload struct {
|
||||
@@ -389,6 +391,8 @@ type Evidence struct {
|
||||
Filename string `json:"filename"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Task *Task `json:"task,omitempty"`
|
||||
Measure *Measure `json:"measure"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -407,12 +411,13 @@ type EvidenceEdge struct {
|
||||
}
|
||||
|
||||
type Framework struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Framework) IsNode() {}
|
||||
@@ -509,6 +514,7 @@ type Organization struct {
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Risks *RiskConnection `json:"risks"`
|
||||
Tasks *TaskConnection `json:"tasks"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
@@ -567,6 +573,7 @@ type Policy struct {
|
||||
Description string `json:"description"`
|
||||
CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"`
|
||||
Owner *People `json:"owner"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Versions *PolicyVersionConnection `json:"versions"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -702,6 +709,7 @@ type Risk struct {
|
||||
ResidualSeverity int `json:"residualSeverity"`
|
||||
Note string `json:"note"`
|
||||
Owner *People `json:"owner,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Measures *MeasureConnection `json:"measures"`
|
||||
Policies *PolicyConnection `json:"policies"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
@@ -742,6 +750,8 @@ type Task struct {
|
||||
State coredata.TaskState `json:"state"`
|
||||
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
|
||||
AssignedTo *People `json:"assignedTo,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Measure *Measure `json:"measure,omitempty"`
|
||||
Evidences *EvidenceConnection `json:"evidences"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
@@ -943,6 +953,7 @@ type Vendor struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Organization *Organization `json:"organization"`
|
||||
ComplianceReports *VendorComplianceReportConnection `json:"complianceReports"`
|
||||
RiskAssessments *VendorRiskAssessmentConnection `json:"riskAssessments"`
|
||||
BusinessOwner *People `json:"businessOwner,omitempty"`
|
||||
|
||||
@@ -20,6 +20,23 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
// Framework is the resolver for the framework field.
|
||||
func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
control, err := svc.Controls.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get control: %w", err))
|
||||
}
|
||||
|
||||
framework, err := svc.Frameworks.Get(ctx, control.FrameworkID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get framework: %w", err))
|
||||
}
|
||||
|
||||
return types.NewFramework(framework), nil
|
||||
}
|
||||
|
||||
// Measures is the resolver for the measures field.
|
||||
func (r *controlResolver) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -87,6 +104,61 @@ func (r *evidenceResolver) FileURL(ctx context.Context, obj *types.Evidence) (*s
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Task is the resolver for the task field.
|
||||
func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*types.Task, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
evidence, err := svc.Evidences.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load evidence: %w", err)
|
||||
}
|
||||
|
||||
if evidence.TaskID == nil {
|
||||
return nil, fmt.Errorf("evidence is not associated with a task")
|
||||
}
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, *evidence.TaskID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load task: %w", err)
|
||||
}
|
||||
|
||||
return types.NewTask(task), nil
|
||||
}
|
||||
|
||||
// Measure is the resolver for the measure field.
|
||||
func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*types.Measure, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
evidence, err := svc.Evidences.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load evidence: %w", err)
|
||||
}
|
||||
|
||||
measure, err := svc.Measures.Get(ctx, evidence.MeasureID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load measure: %w", err)
|
||||
}
|
||||
|
||||
return types.NewMeasure(measure), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
framework, err := svc.Frameworks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, framework.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -689,10 +761,11 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
|
||||
svc := GetTenantService(ctx, r.proboSvc, input.MeasureID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Create(ctx, probo.CreateTaskRequest{
|
||||
MeasureID: input.MeasureID,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
TimeEstimate: input.TimeEstimate,
|
||||
MeasureID: input.MeasureID,
|
||||
OrganizationID: input.OrganizationID,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
TimeEstimate: input.TimeEstimate,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create task: %w", err))
|
||||
@@ -1416,6 +1489,31 @@ func (r *organizationResolver) Risks(ctx context.Context, obj *types.Organizatio
|
||||
return types.NewRiskConnection(page), nil
|
||||
}
|
||||
|
||||
// Tasks is the resolver for the tasks field.
|
||||
func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.TaskOrderField]{
|
||||
Field: coredata.TaskOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.TaskOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := svc.Tasks.ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organization tasks: %w", err))
|
||||
}
|
||||
|
||||
return types.NewTaskConnection(page), nil
|
||||
}
|
||||
|
||||
// Owner is the resolver for the owner field.
|
||||
func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.People, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1434,6 +1532,23 @@ func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.P
|
||||
return types.NewPeople(owner), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *policyResolver) Organization(ctx context.Context, obj *types.Policy) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
policy, err := svc.Policies.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get policy: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, policy.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Versions is the resolver for the versions field.
|
||||
func (r *policyResolver) Versions(ctx context.Context, obj *types.Policy, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyVersionOrderBy, filter *types.PolicyVersionFilter) (*types.PolicyVersionConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1727,6 +1842,23 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
|
||||
return types.NewPeople(owner), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
risk, err := svc.Risks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get risk: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, risk.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Measures is the resolver for the measures field.
|
||||
func (r *riskResolver) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1823,6 +1955,40 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get task: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, task.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Measure is the resolver for the measure field.
|
||||
func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Measure, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
task, err := svc.Tasks.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get task: %w", err))
|
||||
}
|
||||
|
||||
measure, err := svc.Measures.Get(ctx, *task.MeasureID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get measure: %w", err))
|
||||
}
|
||||
|
||||
return types.NewMeasure(measure), nil
|
||||
}
|
||||
|
||||
// Evidences is the resolver for the evidences field.
|
||||
func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
@@ -1859,6 +2025,23 @@ func (r *userResolver) People(ctx context.Context, obj *types.User, organization
|
||||
return types.NewPeople(people), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*types.Organization, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
vendor, err := svc.Vendors.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get vendor: %w", err))
|
||||
}
|
||||
|
||||
organization, err := svc.Organizations.Get(ctx, vendor.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// ComplianceReports is the resolver for the complianceReports field.
|
||||
func (r *vendorResolver) ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) {
|
||||
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
|
||||
|
||||
Reference in New Issue
Block a user