Compare commits

..

6 Commits

45 changed files with 1618 additions and 1945 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ Thank you for showing an interest in contributing to Plane! All kinds of contrib
## Submitting an issue
Before submitting a new issue, please search the [issues](https://github.com/makeplane/plane/issues) tab. Maybe an issue or discussion already exists and might inform you of workarounds. Otherwise, you can give new information.
Before submitting a new issue, please search the [issues](https://github.com/makeplane/plane/issues) tab. Maybe an issue or discussion already exists and might inform you of workarounds. Otherwise, you can give new informplaneation.
While we want to fix all the [issues](https://github.com/makeplane/plane/issues), before fixing a bug we need to be able to reproduce and confirm it. Please provide us with a minimal reproduction scenario using a repository or [Gist](https://gist.github.com/). Having a live, reproducible scenario gives us the information without asking questions back & forth with additional questions like:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "admin",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.23.0"
"version": "0.22.0"
}
@@ -74,6 +74,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
# Get all active workspace members
workspace_members = self.get_queryset()
if workspace_member.role > 5:
serializer = WorkspaceMemberAdminSerializer(
workspace_members,
-96
View File
@@ -1,96 +0,0 @@
# Third party imports
from celery import shared_task
from opentelemetry import trace
# Module imports
from plane.license.models import Instance
from plane.db.models import (
User,
Workspace,
Project,
Issue,
Module,
Cycle,
CycleIssue,
ModuleIssue,
Page,
WorkspaceMember,
)
@shared_task
def instance_traces():
# Get the tracer
tracer = trace.get_tracer(__name__)
# Check if the instance is registered
instance = Instance.objects.first()
# If instance is None then return
if instance is None:
return
if instance.is_telemetry_enabled:
# Instance details
with tracer.start_as_current_span("instance_details") as span:
# Count of all models
workspace_count = Workspace.objects.count()
user_count = User.objects.count()
project_count = Project.objects.count()
issue_count = Issue.objects.count()
module_count = Module.objects.count()
cycle_count = Cycle.objects.count()
cycle_issue_count = CycleIssue.objects.count()
module_issue_count = ModuleIssue.objects.count()
page_count = Page.objects.count()
# Set span attributes
span.set_attribute("instance_id", instance.instance_id)
span.set_attribute("instance_name", instance.instance_name)
span.set_attribute("current_version", instance.current_version)
span.set_attribute("latest_version", instance.latest_version)
span.set_attribute(
"is_telemetry_enabled", instance.is_telemetry_enabled
)
span.set_attribute("user_count", user_count)
span.set_attribute("workspace_count", workspace_count)
span.set_attribute("project_count", project_count)
span.set_attribute("issue_count", issue_count)
span.set_attribute("module_count", module_count)
span.set_attribute("cycle_count", cycle_count)
span.set_attribute("cycle_issue_count", cycle_issue_count)
span.set_attribute("module_issue_count", module_issue_count)
span.set_attribute("page_count", page_count)
# Workspace details
for workspace in Workspace.objects.all():
# Count of all models
project_count = Project.objects.filter(workspace=workspace).count()
issue_count = Issue.objects.filter(workspace=workspace).count()
module_count = Module.objects.filter(workspace=workspace).count()
cycle_count = Cycle.objects.filter(workspace=workspace).count()
cycle_issue_count = CycleIssue.objects.filter(
workspace=workspace
).count()
module_issue_count = ModuleIssue.objects.filter(
workspace=workspace
).count()
page_count = Page.objects.filter(workspace=workspace).count()
member_count = WorkspaceMember.objects.filter(
workspace=workspace
).count()
# Set span attributes
with tracer.start_as_current_span("workspace_details") as span:
span.set_attribute("workspace_id", str(workspace.id))
span.set_attribute("workspace_slug", workspace.slug)
span.set_attribute("project_count", project_count)
span.set_attribute("issue_count", issue_count)
span.set_attribute("module_count", module_count)
span.set_attribute("cycle_count", cycle_count)
span.set_attribute("cycle_issue_count", cycle_issue_count)
span.set_attribute("module_issue_count", module_issue_count)
span.set_attribute("page_count", page_count)
span.set_attribute("member_count", member_count)
return
@@ -9,10 +9,7 @@ from django.conf import settings
# Module imports
from plane.license.models import Instance
from plane.db.models import (
User,
)
from plane.license.bgtasks.tracer import instance_traces
from plane.db.models import User
class Command(BaseCommand):
@@ -24,24 +21,16 @@ class Command(BaseCommand):
"machine_signature", type=str, help="Machine signature"
)
def read_package_json(self):
with open("package.json", "r") as file:
# Load JSON content from the file
data = json.load(file)
payload = {
"instance_key": settings.INSTANCE_KEY,
"version": data.get("version", 0.1),
"user_count": User.objects.filter(is_bot=False).count(),
}
return payload
def handle(self, *args, **options):
# Check if the instance is registered
instance = Instance.objects.first()
# If instance is None then register this instance
if instance is None:
with open("package.json", "r") as file:
# Load JSON content from the file
data = json.load(file)
machine_signature = options.get(
"machine_signature", "machine-signature"
)
@@ -49,7 +38,12 @@ class Command(BaseCommand):
if not machine_signature:
raise CommandError("Machine signature is required")
payload = self.read_package_json()
payload = {
"instance_key": settings.INSTANCE_KEY,
"version": data.get("version", 0.1),
"machine_signature": machine_signature,
"user_count": User.objects.filter(is_bot=False).count(),
}
instance = Instance.objects.create(
instance_name="Plane Community Edition",
@@ -66,15 +60,4 @@ class Command(BaseCommand):
self.stdout.write(
self.style.SUCCESS("Instance already registered")
)
payload = self.read_package_json()
# Update the instance details
instance.last_checked_at = timezone.now()
instance.user_count = payload.get("user_count", 0)
instance.current_version = payload.get("version")
instance.latest_version = payload.get("version")
instance.save()
# Call the instance traces task
instance_traces.delay()
return
return
-25
View File
@@ -16,17 +16,6 @@ from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from corsheaders.defaults import default_headers
# OpenTelemetry
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.instrumentation.django import DjangoInstrumentor
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Secret Key
@@ -35,19 +24,6 @@ SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = int(os.environ.get("DEBUG", "0"))
# Initialize Django instrumentation
DjangoInstrumentor().instrument()
# Configure the tracer provider
service_name = os.environ.get("SERVICE_NAME", "plane-ce-api")
resource = Resource.create({"service.name": service_name})
trace.set_tracer_provider(TracerProvider(resource=resource))
# Configure the OTLP exporter
otel_endpoint = os.environ.get("OTLP_ENDPOINT", "https://telemetry.plane.so")
otlp_exporter = OTLPSpanExporter(endpoint=otel_endpoint)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# Allowed Hosts
ALLOWED_HOSTS = ["*"]
@@ -303,7 +279,6 @@ CELERY_IMPORTS = (
"plane.bgtasks.file_asset_task",
"plane.bgtasks.email_notification_task",
"plane.bgtasks.api_logs_task",
"plane.license.bgtasks.tracer",
# management tasks
"plane.bgtasks.dummy_data_task",
)
-5
View File
@@ -62,8 +62,3 @@ zxcvbn==4.4.28
pytz==2024.1
# jwt
PyJWT==2.8.0
# OpenTelemetry
opentelemetry-api==1.27.0
opentelemetry-sdk==1.27.0
opentelemetry-instrumentation-django==0.48b0
opentelemetry-exporter-otlp==1.27.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "live",
"version": "0.23.0",
"version": "0.22.0",
"description": "",
"main": "./src/server.ts",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"repository": "https://github.com/makeplane/plane.git",
"version": "0.23.0",
"version": "0.22.0",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/constants",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"main": "./index.ts"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/editor",
"version": "0.23.0",
"version": "0.22.0",
"description": "Core Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@plane/eslint-config",
"private": true,
"version": "0.23.0",
"version": "0.22.0",
"files": [
"library.js",
"next.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/helpers",
"version": "0.23.0",
"version": "0.22.0",
"description": "Helper functions shared across multiple apps internally",
"private": true,
"main": "./dist/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tailwind-config-custom",
"version": "0.23.0",
"version": "0.22.0",
"description": "common tailwind configuration across monorepo",
"main": "index.js",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"types": "./src/index.d.ts",
"main": "./src/index.d.ts"
+2 -1
View File
@@ -77,7 +77,8 @@ export type TIssueParams =
| "show_empty_groups"
| "cursor"
| "per_page"
| "issue_type";
| "issue_type"
| "layout";
export type TCalendarLayouts = "month" | "week";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/typescript-config",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"files": [
"base.json",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.23.0",
"version": "0.22.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@@ -1,7 +1,7 @@
import React, { useRef, useState } from "react";
import { usePopper } from "react-popper";
import { Combobox } from "@headlessui/react";
import { Check, ChevronDown, Info, Search } from "lucide-react";
import { Check, ChevronDown, Search } from "lucide-react";
import { createPortal } from "react-dom";
// plane helpers
import { useOutsideClickDetector } from "@plane/helpers";
@@ -11,8 +11,6 @@ import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";
import { cn } from "../../helpers";
// types
import { ICustomSearchSelectProps } from "./helper";
// local components
import { Tooltip } from "../tooltip";
export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
const {
@@ -97,10 +95,11 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
<button
ref={setReferenceElement}
type="button"
className={`flex w-full items-center justify-between gap-1 text-xs ${disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${customButtonClassName}`}
className={`flex w-full items-center justify-between gap-1 text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${customButtonClassName}`}
onClick={toggleDropdown}
>
{customButton}
@@ -171,32 +170,17 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
"w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none",
{
"bg-custom-background-80": active,
"text-custom-text-400 opacity-60 cursor-not-allowed": option.disabled,
}
)
}
onClick={() => {
if (!multiple) closeDropdown();
}}
disabled={option.disabled}
>
{({ selected }) => (
<>
<span className="flex-grow truncate">{option.content}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
{option.tooltip && (
<>
{
typeof option.tooltip === "string" ? (
<Tooltip tooltipContent={option.tooltip}>
<Info className="h-3.5 w-3.5 flex-shrink-0 cursor-pointer text-custom-text-200" />
</Tooltip>
) : (
option.tooltip
)
}
</>
)}
</>
)}
</Combobox.Option>
+6 -8
View File
@@ -44,14 +44,12 @@ interface CustomSearchSelectProps {
onChange: any;
onClose?: () => void;
options:
| {
value: any;
query: string;
content: React.ReactNode;
disabled?: boolean;
tooltip?: string | React.ReactNode;
}[]
| undefined;
| {
value: any;
query: string;
content: React.ReactNode;
}[]
| undefined;
}
interface SingleValueProps {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
@@ -1,23 +1,12 @@
import { Control } from "react-hook-form";
// types
import { TBulkIssueProperties, TIssue } from "@plane/types";
import { TIssue } from "@plane/types";
export type TIssueFields = TIssue & TBulkIssueProperties;
export type TIssueTypeDropdownVariant = "xs" | "sm";
export type TIssueTypeSelectProps<T extends Partial<TIssueFields>> = {
control: Control<T>;
type TIssueTypeSelectProps = {
control: Control<TIssue>;
projectId: string | null;
disabled?: boolean;
variant?: TIssueTypeDropdownVariant;
placeholder?: string;
isRequired?: boolean;
renderChevron?: boolean;
dropDownContainerClassName?: string;
showMandatoryFieldInfo?: boolean; // Show info about mandatory fields
handleFormChange?: () => void;
handleFormChange: () => void;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const IssueTypeSelect = <T extends Partial<TIssueFields>>(props: TIssueTypeSelectProps<T>) => <></>;
export const IssueTypeSelect: React.FC<TIssueTypeSelectProps> = () => <></>;
@@ -4,6 +4,7 @@ import { useParams } from "next/navigation";
import useSWR from "swr";
// mobx store
// components
import { LogoSpinner } from "@/components/common";
import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot, IssuePeekOverview } from "@/components/issues";
import { EIssuesStoreType } from "@/constants/issue";
// ui
@@ -16,7 +17,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
// hooks
const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
useSWR(
const { isLoading } = useSWR(
workspaceSlug && projectId ? `ARCHIVED_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
async () => {
if (workspaceSlug && projectId) {
@@ -26,7 +27,17 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const issueFilters = issuesFilter?.getIssueFilters(projectId?.toString());
if (!workspaceSlug || !projectId) return <></>;
if (isLoading && !issueFilters)
return (
<div className="h-full w-full flex items-center justify-center">
<LogoSpinner />
</div>
);
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.ARCHIVED}>
<ArchivedIssueAppliedFiltersRoot />
@@ -5,6 +5,7 @@ import { useParams } from "next/navigation";
import useSWR from "swr";
// hooks
// components
import { LogoSpinner } from "@/components/common";
import { TransferIssues, TransferIssuesModal } from "@/components/cycles";
import {
CycleAppliedFiltersRoot,
@@ -50,7 +51,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
// state
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
useSWR(
const { isLoading } = useSWR(
workspaceSlug && projectId && cycleId
? `CYCLE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${cycleId.toString()}`
: null,
@@ -62,7 +63,8 @@ export const CycleLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const issueFilters = issuesFilter?.getIssueFilters(cycleId?.toString());
const activeLayout = issueFilters?.displayFilters?.layout;
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase() ?? "draft";
@@ -75,6 +77,13 @@ export const CycleLayoutRoot: React.FC = observer(() => {
if (!workspaceSlug || !projectId || !cycleId) return <></>;
if (isLoading && !issueFilters)
return (
<div className="h-full w-full flex items-center justify-center">
<LogoSpinner />
</div>
);
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.CYCLE}>
<TransferIssuesModal handleClose={() => setTransferIssuesModal(false)} isOpen={transferIssuesModal} />
@@ -2,6 +2,7 @@ import React from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
import { LogoSpinner } from "@/components/common";
import { IssuePeekOverview } from "@/components/issues/peek-overview";
import { EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
// hooks
@@ -30,7 +31,7 @@ export const DraftIssueLayoutRoot: React.FC = observer(() => {
// hooks
const { issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
useSWR(
const { isLoading } = useSWR(
workspaceSlug && projectId ? `DRAFT_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
async () => {
if (workspaceSlug && projectId) {
@@ -40,10 +41,18 @@ export const DraftIssueLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
const issueFilters = issuesFilter?.getIssueFilters(projectId?.toString());
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
if (!workspaceSlug || !projectId) return <></>;
if (isLoading && !issueFilters)
return (
<div className="h-full w-full flex items-center justify-center">
<LogoSpinner />
</div>
);
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.DRAFT}>
<div className="relative flex h-full w-full flex-col overflow-hidden">
@@ -5,6 +5,7 @@ import useSWR from "swr";
// mobx store
// components
import { Row, ERowVariant } from "@plane/ui";
import { LogoSpinner } from "@/components/common";
import {
IssuePeekOverview,
ModuleAppliedFiltersRoot,
@@ -43,7 +44,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
// hooks
const { issuesFilter } = useIssues(EIssuesStoreType.MODULE);
useSWR(
const { isLoading } = useSWR(
workspaceSlug && projectId && moduleId
? `MODULE_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}_${moduleId.toString()}`
: null,
@@ -55,9 +56,18 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const issueFilters = issuesFilter?.getIssueFilters(moduleId?.toString());
if (!workspaceSlug || !projectId || !moduleId) return <></>;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout || undefined;
if (isLoading && !issueFilters)
return (
<div className="h-full w-full flex items-center justify-center">
<LogoSpinner />
</div>
);
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.MODULE}>
@@ -6,6 +6,7 @@ import { useParams } from "next/navigation";
import useSWR from "swr";
// components
import { Spinner } from "@plane/ui";
import { LogoSpinner } from "@/components/common";
import {
ListLayout,
CalendarLayout,
@@ -44,7 +45,7 @@ export const ProjectLayoutRoot: FC = observer(() => {
// hooks
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
useSWR(
const { isLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_ISSUES_${workspaceSlug}_${projectId}` : null,
async () => {
if (workspaceSlug && projectId) {
@@ -54,10 +55,18 @@ export const ProjectLayoutRoot: FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const issueFilters = issuesFilter?.getIssueFilters(projectId?.toString());
const activeLayout = issueFilters?.displayFilters?.layout;
if (!workspaceSlug || !projectId) return <></>;
if (isLoading && !issueFilters)
return (
<div className="h-full w-full flex items-center justify-center">
<LogoSpinner />
</div>
);
return (
<IssuesStoreContext.Provider value={EIssuesStoreType.PROJECT}>
<div className="relative flex h-full w-full flex-col overflow-hidden">
@@ -53,11 +53,12 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const issueFilters = issuesFilter?.getIssueFilters(viewId?.toString());
const activeLayout = issueFilters?.displayFilters?.layout;
if (!workspaceSlug || !projectId || !viewId) return <></>;
if (isLoading) {
if (isLoading && !issueFilters) {
return (
<div className="relative flex h-screen w-full items-center justify-center">
<LogoSpinner />
@@ -280,7 +280,6 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
projectId={projectId}
disabled={!!data?.sourceIssueId}
handleFormChange={handleFormChange}
renderChevron
/>
)}
</div>
+4 -4
View File
@@ -148,7 +148,7 @@ export class Storage {
};
syncWorkspace = async () => {
if (document.hidden || !rootStore.user.localDBEnabled) return; // return if the window gets hidden
if (document.hidden || !rootStore.user.localDBEnabled || !this.db) return; // return if the window gets hidden
await Sentry.startSpan({ name: "LOAD_WS", attributes: { slug: this.workspaceSlug } }, async () => {
await loadWorkSpaceData(this.workspaceSlug);
});
@@ -173,7 +173,7 @@ export class Storage {
};
syncIssues = async (projectId: string) => {
if (document.hidden || !rootStore.user.localDBEnabled) return false; // return if the window gets hidden
if (document.hidden || !rootStore.user.localDBEnabled || !this.db) return false; // return if the window gets hidden
try {
const sync = Sentry.startSpan({ name: `SYNC_ISSUES` }, () => this._syncIssues(projectId));
@@ -191,7 +191,7 @@ export class Storage {
log("### Sync started");
let status = this.getStatus(projectId);
if (status === "loading" || status === "syncing") {
logInfo(`Project ${projectId} is already loading or syncing`);
log(`Project ${projectId} is already loading or syncing`);
return;
}
const syncPromise = this.getSync(projectId);
@@ -290,7 +290,7 @@ export class Storage {
!rootStore.user.localDBEnabled
) {
if (rootStore.user.localDBEnabled) {
logInfo(`Project ${projectId} is loading, falling back to server`);
log(`Project ${projectId} is loading, falling back to server`);
}
const issueService = new IssueService();
return await issueService.getIssuesFromServer(workspaceSlug, projectId, queries);
+5 -5
View File
@@ -8,7 +8,7 @@ import { issueSchema } from "./schemas";
export const PROJECT_OFFLINE_STATUS: Record<string, boolean> = {};
export const addIssue = async (issue: any) => {
if (document.hidden || !rootStore.user.localDBEnabled) return;
if (document.hidden || !rootStore.user.localDBEnabled || !persistence.db) return;
persistence.db.exec("BEGIN TRANSACTION;");
stageIssueInserts(issue);
@@ -16,7 +16,7 @@ export const addIssue = async (issue: any) => {
};
export const addIssuesBulk = async (issues: any, batchSize = 100) => {
if (!rootStore.user.localDBEnabled) return;
if (!rootStore.user.localDBEnabled || !persistence.db) return;
for (let i = 0; i < issues.length; i += batchSize) {
const batch = issues.slice(i, i + batchSize);
@@ -32,7 +32,7 @@ export const addIssuesBulk = async (issues: any, batchSize = 100) => {
}
};
export const deleteIssueFromLocal = async (issue_id: any) => {
if (!rootStore.user.localDBEnabled) return;
if (!rootStore.user.localDBEnabled || !persistence.db) return;
const deleteQuery = `delete from issues where id='${issue_id}'`;
const deleteMetaQuery = `delete from issue_meta where issue_id='${issue_id}'`;
@@ -44,7 +44,7 @@ export const deleteIssueFromLocal = async (issue_id: any) => {
};
// @todo: Update deletes the issue description from local. Implement a separate update.
export const updateIssue = async (issue: TIssue & { is_local_update: number }) => {
if (document.hidden || !rootStore.user.localDBEnabled) return;
if (document.hidden || !rootStore.user.localDBEnabled || !persistence.db) return;
const issue_id = issue.id;
// delete the issue and its meta data
@@ -53,7 +53,7 @@ export const updateIssue = async (issue: TIssue & { is_local_update: number }) =
};
export const syncDeletesToLocal = async (workspaceId: string, projectId: string, queries: any) => {
if (!rootStore.user.localDBEnabled) return;
if (!rootStore.user.localDBEnabled || !persistence.db) return;
const issueService = new IssueService();
const response = await issueService.getDeletedIssues(workspaceId, projectId, queries);
+1 -3
View File
@@ -1,9 +1,7 @@
// import { SQL } from "./sqlite";
import { persistence } from "../storage.sqlite";
export const runQuery = async (sql: string) => {
const data = await persistence.db.exec({
const data = await persistence.db?.exec({
sql,
rowMode: "object",
returnValue: "resultRows",
+15 -3
View File
@@ -4,8 +4,20 @@ import { issueSchema } from "./schemas";
import { wrapDateTime } from "./utils";
export const translateQueryParams = (queries: any) => {
const { group_by, sub_group_by, labels, assignees, state, cycle, module, priority, type, issue_type, ...otherProps } =
queries;
const {
group_by,
layout,
sub_group_by,
labels,
assignees,
state,
cycle,
module,
priority,
type,
issue_type,
...otherProps
} = queries;
const order_by = queries.order_by;
if (state) otherProps.state_id = state;
@@ -33,7 +45,7 @@ export const translateQueryParams = (queries: any) => {
}
// Fix invalid orderby when switching from spreadsheet layout
if ((group_by || sub_group_by) && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
if (layout === "spreadsheet" && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
otherProps.order_by = "sort_order";
}
// For each property value, replace None with empty string
+1 -1
View File
@@ -15,7 +15,7 @@ export const logError = (e: any) => {
e = parseSQLite3Error(e);
}
Sentry.captureException(e);
console.log(e);
console.error(e);
};
export const logInfo = console.info;
@@ -33,6 +33,7 @@ export interface IArchivedIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(projectId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: (
@@ -31,6 +31,7 @@ export interface ICycleIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(cycleId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
updateFilters: (
@@ -33,6 +33,7 @@ export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(projectId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: (
@@ -13,7 +13,7 @@ import {
TStaticViewTypes,
} from "@plane/types";
// constants
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType } from "@/constants/issue";
// helpers
import { getComputedDisplayFilters, getComputedDisplayProperties } from "@/helpers/issue.helper";
// lib
@@ -114,6 +114,8 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
: nonEmptyArrayValue;
});
if (displayFilters?.layout) issueFiltersParams.layout = displayFilters?.layout;
return issueFiltersParams;
};
@@ -31,6 +31,7 @@ export interface IModuleIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(moduleId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
updateFilters: (
@@ -31,6 +31,7 @@ export interface IProjectViewIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(viewId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>;
updateFilters: (
@@ -264,9 +265,16 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
const currentUserId = this.rootIssueStore.currentUserId;
if (currentUserId)
this.handleIssuesLocalFilters.set(EIssuesStoreType.PROJECT_VIEW, type, workspaceSlug, viewId, currentUserId, {
kanban_filters: _filters.kanbanFilters,
});
this.handleIssuesLocalFilters.set(
EIssuesStoreType.PROJECT_VIEW,
type,
workspaceSlug,
viewId,
currentUserId,
{
kanban_filters: _filters.kanbanFilters,
}
);
runInAction(() => {
Object.keys(updatedKanbanFilters).forEach((_key) => {
@@ -31,6 +31,7 @@ export interface IProjectIssuesFilter extends IBaseIssueFilterStore {
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(projectId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: (
-2
View File
@@ -20,8 +20,6 @@ const nextConfig = {
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Embedder-Policy", value: "credentialless" },
],
},
];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.23.0",
"version": "0.22.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
+1466 -1694
View File
File diff suppressed because it is too large Load Diff