Compare commits

..
54 changed files with 243 additions and 505 deletions
+2 -2
View File
@@ -83,7 +83,7 @@ jobs:
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: ./aio
file: ./aio/Dockerfile-base-full
@@ -124,7 +124,7 @@ jobs:
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: ./aio
file: ./aio/Dockerfile-base-slim
+2 -2
View File
@@ -128,7 +128,7 @@ jobs:
uses: actions/checkout@v4
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./aio/Dockerfile-app
@@ -188,7 +188,7 @@ jobs:
uses: actions/checkout@v4
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./aio/Dockerfile-app
+1 -1
View File
@@ -367,7 +367,7 @@ jobs:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2.1.0
uses: softprops/action-gh-release@v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
+4 -4
View File
@@ -29,11 +29,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -46,7 +46,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -59,6 +59,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -79,7 +79,7 @@ jobs:
uses: actions/checkout@v4
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./aio/Dockerfile-app
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v4.1.1
with:
fetch-depth: 0 # Fetch all history for all branches and tags
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
contents: read
steps:
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v4.1.1
with:
persist-credentials: false
fetch-depth: 0
@@ -69,7 +69,6 @@ class IntakeIssueDetailSerializer(BaseSerializer):
"duplicate_issue_detail",
"source",
"issue",
"created_by",
]
read_only_fields = [
"project",
-48
View File
@@ -48,7 +48,6 @@ from plane.db.models import (
)
from plane.utils.analytics_plot import burndown_plot
from plane.bgtasks.recent_visited_task import recent_visited_task
from plane.utils.user_timezone_converter import user_timezone_converter
# Module imports
from .. import BaseAPIView, BaseViewSet
@@ -201,15 +200,6 @@ class CycleViewSet(BaseViewSet):
)
if data:
datetime_fields = [
"created_at",
"updated_at",
"start_date",
"end_date",
]
data = user_timezone_converter(
data, datetime_fields, request.user.user_timezone
)
return Response(data, status=status.HTTP_200_OK)
data = queryset.values(
@@ -238,15 +228,6 @@ class CycleViewSet(BaseViewSet):
"version",
"created_by",
)
datetime_fields = [
"created_at",
"updated_at",
"start_date",
"end_date",
]
data = user_timezone_converter(
data, datetime_fields, request.user.user_timezone
)
return Response(data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
@@ -295,15 +276,6 @@ class CycleViewSet(BaseViewSet):
)
.first()
)
datetime_fields = [
"created_at",
"updated_at",
"start_date",
"end_date",
]
cycle = user_timezone_converter(
cycle, datetime_fields, request.user.user_timezone
)
# Send the model activity
model_activity.delay(
@@ -393,16 +365,6 @@ class CycleViewSet(BaseViewSet):
"created_by",
).first()
datetime_fields = [
"created_at",
"updated_at",
"start_date",
"end_date",
]
cycle = user_timezone_converter(
cycle, datetime_fields, request.user.user_timezone
)
# Send the model activity
model_activity.delay(
model_name="cycle",
@@ -478,16 +440,6 @@ class CycleViewSet(BaseViewSet):
status=status.HTTP_404_NOT_FOUND,
)
datetime_fields = [
"created_at",
"updated_at",
"start_date",
"end_date",
]
data = user_timezone_converter(
data, datetime_fields, request.user.user_timezone
)
queryset = queryset.first()
recent_visited_task.delay(
+30 -8
View File
@@ -1,5 +1,6 @@
# Python imports
import json
import base64
# Django imports
from django.contrib.postgres.aggregates import ArrayAgg
@@ -480,9 +481,7 @@ class IssueViewSet(BaseViewSet):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
issue = (
Issue.objects.filter(
project_id=self.kwargs.get("project_id")
)
Issue.objects.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
@@ -517,7 +516,7 @@ class IssueViewSet(BaseViewSet):
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
.filter(pk=pk)
.annotate(
label_ids=Coalesce(
@@ -852,9 +851,21 @@ class IssuePaginatedViewSet(BaseViewSet):
)
).distinct()
def process_paginated_result(self, fields, results, timezone):
def process_paginated_result(
self, fields, results, timezone, description_binary_required=False
):
paginated_data = results.values(*fields)
# handling the description binary field
if description_binary_required:
for item in paginated_data:
if item["description_binary"]:
item["description_binary"] = base64.b64encode(
item["description_binary"]
).decode("utf-8")
else:
item["description_binary"] = None
# converting the datetime fields in paginated data
datetime_fields = ["created_at", "updated_at"]
paginated_data = user_timezone_converter(
@@ -867,6 +878,9 @@ class IssuePaginatedViewSet(BaseViewSet):
def list(self, request, slug, project_id):
cursor = request.GET.get("cursor", None)
is_description_required = request.GET.get("description", "false")
is_description_binary_required = request.GET.get(
"description_binary", "false"
)
updated_at = request.GET.get("updated_at__gt", None)
# required fields
@@ -902,6 +916,9 @@ class IssuePaginatedViewSet(BaseViewSet):
if str(is_description_required).lower() == "true":
required_fields.append("description_html")
if str(is_description_binary_required).lower() == "true":
required_fields.append("description_binary")
# querying issues
base_queryset = Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id
@@ -966,12 +983,19 @@ class IssuePaginatedViewSet(BaseViewSet):
),
)
is_description_binary_required = False
if required_fields.__contains__("description_binary"):
is_description_binary_required = True
paginated_data = paginate(
base_queryset=base_queryset,
queryset=queryset,
cursor=cursor,
on_result=lambda results: self.process_paginated_result(
required_fields, results, request.user.user_timezone
required_fields,
results,
request.user.user_timezone,
is_description_binary_required,
),
)
@@ -1077,7 +1101,6 @@ class IssueDetailEndpoint(BaseAPIView):
class IssueBulkUpdateDateEndpoint(BaseAPIView):
def validate_dates(
self, current_start, current_target, new_start, new_target
):
@@ -1093,7 +1116,6 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def post(self, request, slug, project_id):
updates = request.data.get("updates", [])
issue_ids = [update["id"] for update in updates]
+2 -2
View File
@@ -35,6 +35,8 @@ 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})
@@ -44,8 +46,6 @@ 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)
# Initialize Django instrumentation
DjangoInstrumentor().instrument()
# Allowed Hosts
+4 -4
View File
@@ -63,7 +63,7 @@ pytz==2024.1
# jwt
PyJWT==2.8.0
# OpenTelemetry
opentelemetry-api==1.28.1
opentelemetry-sdk==1.28.1
opentelemetry-instrumentation-django==0.49b1
opentelemetry-exporter-otlp==1.28.1
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
@@ -22,7 +22,7 @@
"devDependencies": {
"prettier": "latest",
"prettier-plugin-tailwindcss": "^0.5.4",
"turbo": "^2.3.0"
"turbo": "^2.1.1"
},
"packageManager": "yarn@1.22.22",
"name": "plane"
+4 -12
View File
@@ -27,18 +27,10 @@
}
}
.table-wrapper table {
th {
font-weight: 500;
text-align: left;
}
tr[background="none"],
tr:not([background]) {
th {
background-color: rgba(var(--color-background-90));
}
}
.table-wrapper table th {
font-weight: 500;
text-align: left;
background-color: rgba(var(--color-background-90));
}
.table-wrapper table .selectedCell {
+2 -2
View File
@@ -40,8 +40,8 @@ export type TIssueOrderByOptions =
| "-issue_cycle__cycle__name"
| "target_date"
| "-target_date"
| "estimate_point__key"
| "-estimate_point__key"
| "estimate_point"
| "-estimate_point"
| "start_date"
| "-start_date"
| "link_count"
-1
View File
@@ -1 +0,0 @@
export * from "./maintenance-message";
@@ -1,6 +0,0 @@
export const MaintenanceMessage = () => (
<h1 className="text-xl font-medium text-custom-text-100 text-center md:text-left">
Plane didn&apos;t start up. This could be because one or more Plane services failed to start. <br /> Choose View
Logs from setup.sh and Docker logs to be sure.
</h1>
);
+5
View File
@@ -0,0 +1,5 @@
"use client";
import { FC, Fragment } from "react";
export const MaintenanceMode: FC = () => <Fragment />;
+2 -16
View File
@@ -5,11 +5,7 @@ import { computedFn } from "mobx-utils";
// components
import { ChartDataType, IBlockUpdateDependencyData, IGanttBlock, TGanttViews } from "@/components/gantt-chart";
import { currentViewDataWithView } from "@/components/gantt-chart/data";
import {
getDateFromPositionOnGantt,
getItemPositionWidth,
getPositionFromDate,
} from "@/components/gantt-chart/views/helpers";
import { getDateFromPositionOnGantt, getItemPositionWidth } from "@/components/gantt-chart/views/helpers";
// helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// store
@@ -51,7 +47,6 @@ export interface IBaseTimelineStore {
initGantt: () => void;
getDateFromPositionOnGantt: (position: number, offsetDays: number) => Date | undefined;
getPositionFromDateOnGantt: (date: string | Date, offSetWidth: number) => number | undefined;
}
export class BaseTimeLineStore implements IBaseTimelineStore {
@@ -191,7 +186,7 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
start_date: blockData?.start_date ?? undefined,
target_date: blockData?.target_date ?? undefined,
};
if (this.currentViewData && (this.currentViewData?.data?.startDate || this.currentViewData?.data?.dayWidth)) {
if (this.currentViewData && this.currentViewData?.data?.startDate && this.currentViewData?.data?.dayWidth) {
block.position = getItemPositionWidth(this.currentViewData, block);
}
@@ -232,15 +227,6 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
return Math.round(position / this.currentViewData.data.dayWidth);
};
/**
* returns position of the date on chart
*/
getPositionFromDateOnGantt = computedFn((date: string | Date, offSetWidth: number) => {
if (!this.currentViewData) return;
return getPositionFromDate(this.currentViewData, date, offSetWidth);
});
/**
* returns the date at which the position corresponds to on the timeline chart
*/
@@ -68,7 +68,7 @@ export const BlockRow: React.FC<Props> = observer((props) => {
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!block || !block.data || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
const isBlockVisibleOnChart = block.start_date || block.target_date;
const isBlockVisibleOnChart = block.start_date && block.target_date;
const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id);
const isBlockFocused = selectionHelpers.getIsEntityActive(block.id);
const isBlockHoveredOn = isBlockActive(block.id);
@@ -46,11 +46,10 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
const { isMoving, handleBlockDrag } = useGanttResizable(block, resizableRef, ganttContainerRef, updateBlockDates);
const isBlockVisibleOnChart = block?.start_date || block?.target_date;
const isBlockComplete = block?.start_date && block?.target_date;
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!block || (!showAllBlocks && !isBlockVisibleOnChart)) return null;
if (!block || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
const isBlockVisibleOnChart = block.start_date && block.target_date;
if (!block.data) return null;
@@ -64,7 +63,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
ref={resizableRef}
style={{
height: `${BLOCK_HEIGHT}px`,
marginLeft: `${block.position?.marginLeft}px`,
transform: `translateX(${block.position?.marginLeft}px)`,
width: `${block.position?.width}px`,
}}
>
@@ -89,7 +88,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
handleBlockDrag={handleBlockDrag}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove && !!isBlockComplete}
enableBlockMove={enableBlockMove}
isMoving={isMoving}
ganttContainerRef={ganttContainerRef}
/>
@@ -28,7 +28,7 @@ import { IssueBulkOperationsRoot } from "@/plane-web/components/issues";
import { useBulkOperationStatus } from "@/plane-web/hooks/use-bulk-operation-status";
//
import { GanttChartRowList } from "../blocks/block-row-list";
import { DEFAULT_BLOCK_WIDTH, GANTT_SELECT_GROUP, HEADER_HEIGHT } from "../constants";
import { GANTT_SELECT_GROUP, HEADER_HEIGHT } from "../constants";
import { getItemPositionWidth } from "../views";
import { TimelineDragHelper } from "./timeline-drag-helper";
@@ -108,20 +108,14 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const approxRangeLeft = scrollLeft;
const approxRangeRight = scrollWidth - (scrollLeft + clientWidth);
const calculatedRangeRight = itemsContainerWidth - (scrollLeft + clientWidth);
if (approxRangeRight < clientWidth || calculatedRangeRight < clientWidth) {
updateCurrentViewRenderPayload("right", currentView);
}
if (approxRangeLeft < clientWidth) {
updateCurrentViewRenderPayload("left", currentView);
}
if (approxRangeRight < clientWidth) updateCurrentViewRenderPayload("right", currentView);
if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView);
};
const handleScrollToBlock = (block: IGanttBlock) => {
const scrollContainer = ganttContainerRef.current as HTMLDivElement;
const scrollToEndDate = !block.start_date && block.target_date;
const scrollToDate = block.start_date ? getDate(block.start_date) : getDate(block.target_date);
const scrollToDate = getDate(block.start_date);
let chartData;
if (!scrollContainer || !currentViewData || !scrollToDate) return;
@@ -135,8 +129,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const updatedPosition = getItemPositionWidth(chartData ?? currentViewData, block);
setTimeout(() => {
if (updatedPosition)
scrollContainer.scrollLeft = updatedPosition.marginLeft - 4 - (scrollToEndDate ? DEFAULT_BLOCK_WIDTH : 0);
if (updatedPosition) scrollContainer.scrollLeft = updatedPosition.marginLeft - 4;
});
};
@@ -196,7 +189,6 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
style={{
width: `${itemsContainerWidth}px`,
transform: `translateY(${HEADER_HEIGHT}px)`,
paddingBottom: `${HEADER_HEIGHT}px`,
}}
>
<GanttChartRowList
@@ -25,7 +25,7 @@ export const MonthChartView: FC<any> = observer(() => {
const marginLeftDays = getNumberOfDaysBetweenTwoDates(monthsStartDate, weeksStartDate);
return (
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
{currentViewData && (
<div className="relative flex flex-col outline-[0.25px] outline outline-custom-border-200">
{/** Header Div */}
@@ -15,7 +15,7 @@ export const QuarterChartView: FC<any> = observer(() => {
const quarterBlocks: IQuarterMonthBlock[] = groupMonthsToQuarters(monthBlocks);
return (
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
{currentViewData &&
quarterBlocks?.map((quarterBlock, rootIndex) => (
<div
@@ -13,7 +13,7 @@ export const WeekChartView: FC<any> = observer(() => {
const weekBlocks: IWeekBlock[] = renderView;
return (
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
{currentViewData &&
weekBlocks?.map((block, rootIndex) => (
<div
@@ -6,6 +6,4 @@ export const GANTT_BREADCRUMBS_HEIGHT = 40;
export const SIDEBAR_WIDTH = 360;
export const DEFAULT_BLOCK_WIDTH = 60;
export const GANTT_SELECT_GROUP = "gantt-issues";
@@ -71,7 +71,7 @@ export const useGanttResizable = (
// calculate new marginLeft and update the initial marginLeft to the newly calculated one
marginLeft = Math.round(mouseX / dayWidth) * dayWidth;
// get Dimensions from dom's style
const prevMarginLeft = parseFloat(resizableDiv.style.marginLeft.slice(0, -2));
const prevMarginLeft = parseFloat(resizableDiv.style.transform.slice(11, -3));
const prevWidth = parseFloat(resizableDiv.style.width.slice(0, -2));
// calculate new width
const marginDelta = prevMarginLeft - marginLeft;
@@ -88,7 +88,7 @@ export const useGanttResizable = (
if (width < dayWidth) return;
resizableDiv.style.width = `${width}px`;
resizableDiv.style.marginLeft = `${marginLeft}px`;
resizableDiv.style.transform = `translateX(${marginLeft}px)`;
const deltaLeft = Math.round((marginLeft - (block.position?.marginLeft ?? 0)) / dayWidth) * dayWidth;
const deltaWidth = Math.round((width - (block.position?.width ?? 0)) / dayWidth) * dayWidth;
@@ -34,7 +34,7 @@ export const GanttDnDHOC = observer((props: Props) => {
draggable({
element,
canDrag: () => isDragEnabled,
getInitialData: () => ({ id, dragInstanceId: "GANTT_REORDER" }),
getInitialData: () => ({ id }),
onDragStart: () => {
setIsDragging(true);
},
@@ -44,7 +44,7 @@ export const GanttDnDHOC = observer((props: Props) => {
}),
dropTargetForElements({
element,
canDrop: ({ source }) => source?.data?.id !== id && source?.data?.dragInstanceId === "GANTT_REORDER",
canDrop: ({ source }) => source?.data?.id !== id,
getData: ({ input, element }) => {
const data = { id };
@@ -27,8 +27,8 @@ export const IssuesSidebarBlock = observer((props: Props) => {
const { updateActiveBlockId, isBlockActive, getNumberOfDaysFromPosition } = useTimeLineChartStore();
const { getIsIssuePeeked } = useIssueDetail();
const isBlockComplete = !!block?.start_date && !!block?.target_date;
const duration = isBlockComplete ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
const isBlockVisibleOnChart = !!block?.start_date && !!block?.target_date;
const duration = isBlockVisibleOnChart ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
if (!block?.data) return null;
@@ -22,8 +22,8 @@ export const ModulesSidebarBlock: React.FC<Props> = observer((props) => {
if (!block) return <></>;
const isBlockComplete = !!block.start_date && !!block.target_date;
const duration = isBlockComplete ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
const isBlockVisibleOnChart = !!block.start_date && !!block.target_date;
const duration = isBlockVisibleOnChart ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
return (
<div
@@ -1,6 +1,8 @@
import { addDaysToDate, findTotalDaysInRange, getDate } from "@/helpers/date-time.helper";
import { DEFAULT_BLOCK_WIDTH } from "../constants";
import { ChartDataType, IGanttBlock } from "../types";
import { IMonthBlock, IMonthView, monthView } from "./month-view";
import { quarterView } from "./quarter-view";
import { IWeekBlock, weekView } from "./week-view";
/**
* Generates Date by using Day, month and Year
@@ -82,7 +84,7 @@ export const getDateFromPositionOnGantt = (position: number, chartData: ChartDat
*/
export const getItemPositionWidth = (chartData: ChartDataType, itemData: IGanttBlock) => {
let scrollPosition: number = 0;
let scrollWidth: number = DEFAULT_BLOCK_WIDTH;
let scrollWidth: number = 0;
const { startDate: chartStartDate } = chartData.data;
const { start_date, target_date } = itemData;
@@ -90,42 +92,24 @@ export const getItemPositionWidth = (chartData: ChartDataType, itemData: IGanttB
const itemStartDate = getDate(start_date);
const itemTargetDate = getDate(target_date);
chartStartDate.setHours(0, 0, 0, 0);
itemStartDate?.setHours(0, 0, 0, 0);
itemTargetDate?.setHours(0, 0, 0, 0);
if (!itemStartDate || !itemTargetDate) return;
if (!itemStartDate && !itemTargetDate) return;
chartStartDate.setHours(0, 0, 0, 0);
itemStartDate.setHours(0, 0, 0, 0);
itemTargetDate.setHours(0, 0, 0, 0);
// get number of days from chart start date to block's start date
const positionDaysDifference = Math.round(findTotalDaysInRange(chartStartDate, itemStartDate, false) ?? 0);
if (!positionDaysDifference) return;
// get scroll position from the number of days and width of each day
scrollPosition = itemStartDate
? getPositionFromDate(chartData, itemStartDate, 0)
: getPositionFromDate(chartData, itemTargetDate!, -1 * DEFAULT_BLOCK_WIDTH);
scrollPosition = positionDaysDifference * chartData.data.dayWidth;
if (itemStartDate && itemTargetDate) {
// get width of block
const widthTimeDifference: number = itemStartDate.getTime() - itemTargetDate.getTime();
const widthDaysDifference: number = Math.abs(Math.floor(widthTimeDifference / (1000 * 60 * 60 * 24)));
scrollWidth = (widthDaysDifference + 1) * chartData.data.dayWidth;
}
// get width of block
const widthTimeDifference: number = itemStartDate.getTime() - itemTargetDate.getTime();
const widthDaysDifference: number = Math.abs(Math.floor(widthTimeDifference / (1000 * 60 * 60 * 24)));
scrollWidth = (widthDaysDifference + 1) * chartData.data.dayWidth;
return { marginLeft: scrollPosition, width: scrollWidth };
};
export const getPositionFromDate = (chartData: ChartDataType, date: string | Date, offsetWidth: number) => {
const currDate = getDate(date);
const { startDate: chartStartDate } = chartData.data;
if (!currDate || !chartStartDate) return 0;
chartStartDate.setHours(0, 0, 0, 0);
currDate.setHours(0, 0, 0, 0);
// get number of days from chart start date to block's start date
const positionDaysDifference = Math.round(findTotalDaysInRange(chartStartDate, currDate, false) ?? 0);
if (!positionDaysDifference) return 0;
// get scroll position from the number of days and width of each day
return positionDaysDifference * chartData.data.dayWidth + offsetWidth;
};
-1
View File
@@ -1,2 +1 @@
export * from "./not-ready-view";
export * from "./maintenance-view";
@@ -1,34 +0,0 @@
"use client";
import { FC } from "react";
import Image from "next/image";
// ui
import { Button } from "@plane/ui";
// layouts
import DefaultLayout from "@/layouts/default-layout";
// components
import { MaintenanceMessage } from "@/plane-web/components/instance";
// images
import maintenanceModeImage from "@/public/maintenance-mode.webp";
export const MaintenanceView: FC = () => (
<DefaultLayout>
<div className="relative container mx-auto h-full w-full flex flex-col md:flex-row gap-2 items-center justify-center gap-y-5 bg-custom-background-100 text-center">
<div className="relative w-full">
<Image
src={maintenanceModeImage}
height="176"
width="288"
alt="ProjectSettingImg"
className="w-full h-full object-fill object-center"
/>
</div>
<div className="w-full space-y-4 relative flex flex-col justify-center md:justify-start items-center md:items-start">
<MaintenanceMessage />
<Button variant="outline-primary" onClick={() => window.location.reload()}>
Reload
</Button>
</div>
</div>
</DefaultLayout>
);
@@ -27,7 +27,6 @@ export const useLinkOperations = (workspaceSlug: string, projectId: string, issu
type: TOAST_TYPE.ERROR,
title: "Link not created",
});
throw error;
}
},
update: async (linkId: string, data: Partial<TIssueLink>) => {
@@ -45,7 +44,6 @@ export const useLinkOperations = (workspaceSlug: string, projectId: string, issu
type: TOAST_TYPE.ERROR,
title: "Link not updated",
});
throw error;
}
},
remove: async (linkId: string) => {
@@ -7,6 +7,8 @@ import { Controller, useForm } from "react-hook-form";
import type { TIssueLinkEditableFields } from "@plane/types";
// plane ui
import { Button, Input, ModalCore } from "@plane/ui";
// helpers
import { checkURLValidity } from "@/helpers/string.helper";
// hooks
import { useIssueDetail } from "@/hooks/store";
// types
@@ -46,18 +48,14 @@ export const IssueLinkCreateUpdateModal: FC<TIssueLinkCreateEditModal> = observe
const onClose = () => {
setIssueLinkData(null);
reset();
if (handleOnClose) handleOnClose();
};
const handleFormSubmit = async (formData: TIssueLinkCreateFormFieldOptions) => {
const parsedUrl = formData.url.startsWith("http") ? formData.url : `http://${formData.url}`;
try {
if (!formData || !formData.id) await linkOperations.create({ title: formData.title, url: parsedUrl });
else await linkOperations.update(formData.id, { title: formData.title, url: parsedUrl });
onClose();
} catch (error) {
console.error("error", error);
}
if (!formData || !formData.id) await linkOperations.create({ title: formData.title, url: formData.url });
else await linkOperations.update(formData.id as string, { title: formData.title, url: formData.url });
onClose();
};
useEffect(() => {
@@ -79,6 +77,7 @@ export const IssueLinkCreateUpdateModal: FC<TIssueLinkCreateEditModal> = observe
name="url"
rules={{
required: "URL is required",
validate: (value) => checkURLValidity(value) || "URL is invalid",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
@@ -58,7 +58,6 @@ export const IssueLinkRoot: FC<TIssueLinkRoot> = (props) => {
type: TOAST_TYPE.ERROR,
title: "Link not created",
});
throw error;
}
},
update: async (linkId: string, data: Partial<TIssueLink>) => {
@@ -77,7 +76,6 @@ export const IssueLinkRoot: FC<TIssueLinkRoot> = (props) => {
type: TOAST_TYPE.ERROR,
title: "Link not updated",
});
throw error;
}
},
remove: async (linkId: string) => {
@@ -4,8 +4,6 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// ui
import { Tooltip, ControlLink } from "@plane/ui";
// components
import { SIDEBAR_WIDTH } from "@/components/gantt-chart/constants";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
@@ -15,8 +13,7 @@ import useIssuePeekOverviewRedirection from "@/hooks/use-issue-peek-overview-red
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues";
//
import { getBlockViewDetails } from "../utils";
// local types
import { GanttStoreType } from "./base-gantt-root";
type Props = {
@@ -42,37 +39,36 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
const stateDetails =
issueDetails && getProjectStates(issueDetails?.project_id)?.find((state) => state?.id == issueDetails?.state_id);
const { message, blockStyle } = getBlockViewDetails(issueDetails, stateDetails?.color ?? "");
const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile);
return (
<Tooltip
isMobile={isMobile}
tooltipContent={
<div className="space-y-1">
<h5>{issueDetails?.name}</h5>
<div>{message}</div>
</div>
}
position="top-left"
disabled={!message}
<div
id={`issue-${issueId}`}
className="relative flex h-full w-full cursor-pointer items-center rounded"
style={{
backgroundColor: stateDetails?.color,
}}
onClick={handleIssuePeekOverview}
>
<div
id={`issue-${issueId}`}
className="relative flex h-full w-full cursor-pointer items-center rounded"
style={blockStyle}
onClick={handleIssuePeekOverview}
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<Tooltip
isMobile={isMobile}
tooltipContent={
<div className="space-y-1">
<h5>{issueDetails?.name}</h5>
<div>
{renderFormattedDate(issueDetails?.start_date ?? "")} to{" "}
{renderFormattedDate(issueDetails?.target_date ?? "")}
</div>
</div>
}
position="top-left"
>
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<div
className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100"
style={{ left: `${SIDEBAR_WIDTH}px` }}
>
<div className="relative w-full overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100">
{issueDetails?.name}
</div>
</div>
</Tooltip>
</Tooltip>
</div>
);
});
@@ -96,11 +92,7 @@ export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
// derived values
const issueDetails = getIssueById(issueId);
const handleIssuePeekOverview = (e: any) => {
e.stopPropagation(true);
e.preventDefault();
handleRedirection(workspaceSlug, issueDetails, isMobile);
};
const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile);
return (
<ControlLink
@@ -1,6 +1,5 @@
"use client";
import { CSSProperties } from "react";
import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
import clone from "lodash/clone";
import concat from "lodash/concat";
@@ -33,7 +32,6 @@ import { Logo } from "@/components/common";
import { ISSUE_PRIORITIES, EIssuesStoreType } from "@/constants/issue";
import { STATE_GROUPS } from "@/constants/state";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { getFileURL } from "@/helpers/file.helper";
// store
import { ICycleStore } from "@/store/cycle.store";
@@ -674,39 +672,3 @@ export function getApproximateCardHeight(displayProperties: IIssueDisplayPropert
return cardHeight;
}
/**
* This Method is used to get Block view details, that returns block style and tooltip message
* @param block
* @param backgroundColor
* @returns
*/
export const getBlockViewDetails = (
block: { start_date: string | undefined | null; target_date: string | undefined | null } | undefined | null,
backgroundColor: string
) => {
const isBlockVisibleOnChart = block?.start_date || block?.target_date;
const isBlockComplete = block?.start_date && block?.target_date;
let message;
const blockStyle: CSSProperties = {
backgroundColor,
};
if (isBlockVisibleOnChart && !isBlockComplete) {
if (block?.start_date) {
message = `From ${renderFormattedDate(block.start_date)}`;
blockStyle.maskImage = `linear-gradient(to right, ${backgroundColor} 50%, transparent 95%)`;
} else if (block?.target_date) {
message = `Till ${renderFormattedDate(block.target_date)}`;
blockStyle.maskImage = `linear-gradient(to left, ${backgroundColor} 50%, transparent 95%)`;
}
} else if (isBlockComplete) {
message = `${renderFormattedDate(block?.start_date)} to ${renderFormattedDate(block?.target_date)}`;
}
return {
message,
blockStyle,
};
};
@@ -135,13 +135,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<span>Created by</span>
</div>
<div className="w-full h-full flex items-center gap-1.5 rounded px-2 py-0.5 text-sm justify-between cursor-not-allowed">
<ButtonAvatars
showTooltip
userIds={createdByDetails?.display_name.includes("-intake") ? null : createdByDetails?.id}
/>
<span className="flex-grow truncate text-xs leading-5">
{createdByDetails?.display_name.includes("-intake") ? "Plane" : createdByDetails?.display_name}
</span>
<ButtonAvatars showTooltip userIds={createdByDetails?.id} />
<span className="flex-grow truncate text-xs leading-5">{createdByDetails?.display_name}</span>
</div>
</div>
)}
@@ -3,14 +3,13 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
// hooks
// ui
import { Tooltip, ModuleStatusIcon } from "@plane/ui";
// components
import { SIDEBAR_WIDTH } from "@/components/gantt-chart/constants";
import { getBlockViewDetails } from "@/components/issues/issue-layouts/utils";
// constants
// helpers
import { MODULE_STATUS } from "@/constants/module";
// hooks
import { renderFormattedDate } from "@/helpers/date-time.helper";
// constants
import { useModule } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { usePlatformOS } from "@/hooks/use-platform-os";
@@ -31,40 +30,31 @@ export const ModuleGanttBlock: React.FC<Props> = observer((props) => {
// hooks
const { isMobile } = usePlatformOS();
const { message, blockStyle } = getBlockViewDetails(
moduleDetails,
MODULE_STATUS.find((s) => s.value === moduleDetails?.status)?.color ?? ""
);
return (
<Tooltip
isMobile={isMobile}
tooltipContent={
<div className="space-y-1">
<h5>{moduleDetails?.name}</h5>
<div>{message}</div>
</div>
<div
className="relative flex h-full w-full items-center rounded"
style={{ backgroundColor: MODULE_STATUS.find((s) => s.value === moduleDetails?.status)?.color }}
onClick={() =>
router.push(`/${workspaceSlug?.toString()}/projects/${moduleDetails?.project_id}/modules/${moduleDetails?.id}`)
}
position="top-left"
>
<div
className="relative flex h-full w-full cursor-pointer items-center rounded"
style={blockStyle}
onClick={() =>
router.push(
`/${workspaceSlug?.toString()}/projects/${moduleDetails?.project_id}/modules/${moduleDetails?.id}`
)
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<Tooltip
isMobile={isMobile}
tooltipContent={
<div className="space-y-1">
<h5>{moduleDetails?.name}</h5>
<div>
{renderFormattedDate(moduleDetails?.start_date ?? "")} to{" "}
{renderFormattedDate(moduleDetails?.target_date ?? "")}
</div>
</div>
}
position="top-left"
>
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<div
className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100"
style={{ left: `${SIDEBAR_WIDTH}px` }}
>
{moduleDetails?.name}
</div>
</div>
</Tooltip>
<div className="relative w-full truncate px-2.5 py-1 text-sm text-custom-text-100">{moduleDetails?.name}</div>
</Tooltip>
</div>
);
});
@@ -6,6 +6,8 @@ import { Controller, useForm } from "react-hook-form";
import type { ILinkDetails, ModuleLink } from "@plane/types";
// plane ui
import { Button, Input, ModalCore, setToast, TOAST_TYPE } from "@plane/ui";
// helpers
import { checkURLValidity } from "@/helpers/string.helper";
type Props = {
createLink: (formData: ModuleLink) => Promise<void>;
@@ -37,10 +39,9 @@ export const CreateUpdateModuleLinkModal: FC<Props> = (props) => {
};
const handleFormSubmit = async (formData: ModuleLink) => {
const parsedUrl = formData.url.startsWith("http") ? formData.url : `http://${formData.url}`;
const payload = {
title: formData.title,
url: parsedUrl,
url: formData.url,
};
try {
@@ -91,6 +92,7 @@ export const CreateUpdateModuleLinkModal: FC<Props> = (props) => {
name="url"
rules={{
required: "URL is required",
validate: (value) => checkURLValidity(value) || "URL is invalid",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
+2 -2
View File
@@ -79,9 +79,9 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
},
estimate: {
title: "Estimate",
ascendingOrderKey: "estimate_point__key",
ascendingOrderKey: "estimate_point",
ascendingOrderTitle: "Low",
descendingOrderKey: "-estimate_point__key",
descendingOrderKey: "-estimate_point",
descendingOrderTitle: "High",
icon: Triangle,
Column: SpreadsheetEstimateColumn,
+4 -2
View File
@@ -3,9 +3,11 @@ import { observer } from "mobx-react";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common";
import { InstanceNotReady, MaintenanceView } from "@/components/instance";
import { InstanceNotReady } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks/store";
// plane web components
import { MaintenanceMode } from "@/plane-web/components/maintenance-mode";
type TInstanceWrapper = {
children: ReactNode;
@@ -30,7 +32,7 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
</div>
);
if (instanceSWRError) return <MaintenanceView />;
if (instanceSWRError) return <MaintenanceMode />;
// something went wrong while in the request
if (error && error?.status === "error") return <>{children}</>;
-1
View File
@@ -300,7 +300,6 @@ export class Storage {
const { cursor, group_by, sub_group_by } = queries;
const query = issueFilterQueryConstructor(this.workspaceSlug, projectId, queries);
log("#### Query", query);
const countQuery = issueFilterCountQueryConstructor(this.workspaceSlug, projectId, queries);
const start = performance.now();
let issuesRaw: any[] = [];
+4 -7
View File
@@ -87,10 +87,10 @@ export const getStates = async (workspaceSlug: string) => {
export const getEstimatePoints = async (workspaceSlug: string) => {
const estimateService = new EstimateService();
const estimates = await estimateService.fetchWorkspaceEstimates(workspaceSlug);
let objects: IEstimatePoint[] = [];
const objects: IEstimatePoint[] = [];
(estimates || []).forEach((estimate: IEstimate) => {
if (estimate?.points) {
objects = objects.concat(estimate.points);
objects.concat(estimate.points);
}
});
return objects;
@@ -104,9 +104,6 @@ export const getMembers = async (workspaceSlug: string) => {
};
export const loadWorkSpaceData = async (workspaceSlug: string) => {
if (!persistence.db || !persistence.db.exec) {
return;
}
log("Loading workspace data");
const promises = [];
promises.push(getLabels(workspaceSlug));
@@ -115,7 +112,7 @@ export const loadWorkSpaceData = async (workspaceSlug: string) => {
promises.push(getStates(workspaceSlug));
promises.push(getEstimatePoints(workspaceSlug));
promises.push(getMembers(workspaceSlug));
const [labels, modules, cycles, states, estimates, members] = await Promise.all(promises);
const [labels, modules, cycles, states, estimates, memebers] = await Promise.all(promises);
const start = performance.now();
await persistence.db.exec("BEGIN;");
@@ -124,7 +121,7 @@ export const loadWorkSpaceData = async (workspaceSlug: string) => {
await batchInserts(cycles, "cycles", cycleSchema);
await batchInserts(states, "states", stateSchema);
await batchInserts(estimates, "estimate_points", estimatePointSchema);
await batchInserts(members, "members", memberSchema);
await batchInserts(memebers, "members", memberSchema);
await persistence.db.exec("COMMIT;");
const end = performance.now();
log("Time taken to load workspace data", end - start);
+8 -6
View File
@@ -18,8 +18,6 @@ export const SPECIAL_ORDER_BY = {
"-issue_cycle__cycle__name": "cycles",
state__name: "states",
"-state__name": "states",
estimate_point__key: "estimate_point",
"-estimate_point__key": "estimate_point",
};
export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: string, queries: any) => {
const {
@@ -50,6 +48,8 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
`;
log("###", sql);
return sql;
}
if (group_by) {
@@ -64,6 +64,8 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
WHERE rank <= ${per_page}
`;
log("###", sql);
return sql;
}
@@ -76,10 +78,8 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
sql += `SELECT fi.* , `;
if (order_by.includes("assignee")) {
sql += ` s.first_name as ${name} `;
} else if (order_by.includes("estimate")) {
sql += ` s.key as ${name} `;
} else {
sql += ` s.name as ${name} `;
sql += ` s.name as ${name} `;
}
sql += `FROM fi `;
if (order_by && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
@@ -87,7 +87,7 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
sql += `
LEFT JOIN cycles s on fi.cycle_id = s.id`;
}
if (order_by.includes("estimate_point__key")) {
if (order_by.includes("estimate_point")) {
sql += `
LEFT JOIN estimate_points s on fi.estimate_point = s.id`;
}
@@ -120,6 +120,7 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
`;
sql += ` group by i.id ${orderByString} LIMIT ${pageSize} OFFSET ${offset * 1 + page * pageSize};`;
log("######$$$", sql);
return sql;
}
@@ -148,6 +149,7 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
// Add offset and paging to query
sql += ` LIMIT ${pageSize} OFFSET ${offset * 1 + page * pageSize};`;
log("$$$", sql);
return sql;
};
+1 -4
View File
@@ -45,7 +45,7 @@ export const translateQueryParams = (queries: any) => {
}
// Fix invalid orderby when switching from spreadsheet layout
if (layout !== "spreadsheet" && 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
@@ -336,9 +336,6 @@ const getSingleFilterFields = (queries: any) => {
if (state_group) {
fields.add("states.'group' as state_group");
}
if (order_by?.includes("estimate_point__key")) {
fields.add("estimate_point");
}
return Array.from(fields);
};
+1 -1
View File
@@ -388,7 +388,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
else this.loader = "mutation-loading";
if (loadingType) this.loader = loadingType;
const status = this.inboxFilters?.status;
const status = this.inboxFilters?.status && uniq([...this.inboxFilters.status, EInboxIssueStatus.SNOOZED]);
const queryParams = this.inboxIssueQueryParams(
{ ...this.inboxFilters, status },
this.inboxSorting,
@@ -36,7 +36,6 @@ import { EIssueLayoutTypes, ISSUE_PRIORITIES } from "@/constants/issue";
// helpers
import { convertToISODateString } from "@/helpers/date-time.helper";
// local-db
import { SPECIAL_ORDER_BY } from "@/local-db/utils/query-constructor";
import { updatePersistentLayer } from "@/local-db/utils/utils";
// services
import { CycleService } from "@/services/cycle.service";
@@ -165,8 +164,8 @@ const ISSUE_ORDERBY_KEY: Record<TIssueOrderByOptions, keyof TIssue> = {
"-issue_cycle__cycle__name": "cycle_id",
target_date: "target_date",
"-target_date": "target_date",
estimate_point__key: "estimate_point",
"-estimate_point__key": "estimate_point",
estimate_point: "estimate_point",
"-estimate_point": "estimate_point",
start_date: "start_date",
"-start_date": "start_date",
link_count: "link_count",
@@ -283,19 +282,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
const displayFilters = this.issueFilterStore?.issueFilters?.displayFilters;
if (!displayFilters) return;
const layout = displayFilters.layout;
const orderBy = displayFilters.order_by;
// Temporary code to fix no load order by
if (
this.rootIssueStore.rootStore.user.localDBEnabled &&
layout !== EIssueLayoutTypes.SPREADSHEET &&
orderBy &&
Object.keys(SPECIAL_ORDER_BY).includes(orderBy)
) {
return "sort_order";
}
return displayFilters?.order_by;
}
@@ -1715,14 +1701,13 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
* @returns string | string[] of sortable fields to be used for sorting
*/
populateIssueDataForSorting(
dataType: "state_id" | "label_ids" | "assignee_ids" | "module_ids" | "cycle_id" | "estimate_point",
dataType: "state_id" | "label_ids" | "assignee_ids" | "module_ids" | "cycle_id",
dataIds: string | string[] | null | undefined,
projectId: string | undefined | null,
order?: "asc" | "desc"
) {
if (!dataIds) return;
const dataValues: (string | number)[] = [];
const dataValues: string[] = [];
const isDataIdsArray = Array.isArray(dataIds);
const dataIdsArray = isDataIdsArray ? dataIds : [dataIds];
@@ -1772,26 +1757,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
}
break;
}
case "estimate_point": {
// return if project Id does not exist
if (!projectId) break;
// get the estimate ID for the current Project
const currentProjectEstimateId =
this.rootIssueStore.rootStore.projectEstimate.currentActiveEstimateIdByProjectId(projectId);
// return if current Estimate Id for the project is not available
if (!currentProjectEstimateId) break;
// get Estimate based on Id
const estimate = this.rootIssueStore.rootStore.projectEstimate.estimateById(currentProjectEstimateId);
// If Estimate is not available, then return
if (!estimate) break;
// Get Estimate Value
const estimateKey = estimate?.estimatePointById(dataIds as string)?.key;
// If Value string i not available or empty then return
if (estimateKey === undefined) break;
dataValues.push(estimateKey);
}
}
return isDataIdsArray ? (order ? orderBy(dataValues, undefined, [order]) : dataValues) : dataValues;
@@ -1806,17 +1771,11 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return getIssueIds(orderBy(array, "sort_order"));
case "state__name":
return getIssueIds(
orderBy(array, (issue) =>
this.populateIssueDataForSorting("state_id", issue?.["state_id"], issue?.["project_id"])
)
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue?.["state_id"]))
);
case "-state__name":
return getIssueIds(
orderBy(
array,
(issue) => this.populateIssueDataForSorting("state_id", issue?.["state_id"], issue?.["project_id"]),
["desc"]
)
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue?.["state_id"]), ["desc"])
);
// dates
case "created_at":
@@ -1867,23 +1826,15 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
case "-attachment_count":
return getIssueIds(orderBy(array, "attachment_count", ["desc"]));
case "estimate_point__key":
case "estimate_point":
return getIssueIds(
orderBy(array, [
getSortOrderToFilterEmptyValues.bind(null, "estimate_point"),
(issue) =>
this.populateIssueDataForSorting("estimate_point", issue?.["estimate_point"], issue?.["project_id"]),
])
orderBy(array, [getSortOrderToFilterEmptyValues.bind(null, "estimate_point"), "estimate_point"])
); //preferring sorting based on empty values to always keep the empty values below
case "-estimate_point__key":
case "-estimate_point":
return getIssueIds(
orderBy(
array,
[
getSortOrderToFilterEmptyValues.bind(null, "estimate_point"),
(issue) =>
this.populateIssueDataForSorting("estimate_point", issue?.["estimate_point"], issue?.["project_id"]),
], //preferring sorting based on empty values to always keep the empty values below
[getSortOrderToFilterEmptyValues.bind(null, "estimate_point"), "estimate_point"], //preferring sorting based on empty values to always keep the empty values below
["asc", "desc"]
)
);
@@ -1903,8 +1854,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return getIssueIds(
orderBy(array, [
getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], "asc"),
])
);
case "-labels__name":
@@ -1913,8 +1863,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
array,
[
getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], "asc"),
],
["asc", "desc"]
)
@@ -1924,8 +1873,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return getIssueIds(
orderBy(array, [
getSortOrderToFilterEmptyValues.bind(null, "module_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], "asc"),
])
);
case "-issue_module__module__name":
@@ -1934,8 +1882,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
array,
[
getSortOrderToFilterEmptyValues.bind(null, "module_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], "asc"),
],
["asc", "desc"]
)
@@ -1945,7 +1892,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return getIssueIds(
orderBy(array, [
getSortOrderToFilterEmptyValues.bind(null, "cycle_id"), //preferring sorting based on empty values to always keep the empty values below
(issue) => this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], "asc"),
])
);
case "-issue_cycle__cycle__name":
@@ -1954,8 +1901,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
array,
[
getSortOrderToFilterEmptyValues.bind(null, "cycle_id"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], "asc"),
],
["asc", "desc"]
)
@@ -1965,8 +1911,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
return getIssueIds(
orderBy(array, [
getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], "asc"),
])
);
case "-assignees__first_name":
@@ -1975,8 +1920,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
array,
[
getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
(issue) =>
this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], issue?.["project_id"], "asc"),
(issue) => this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], "asc"),
],
["asc", "desc"]
)
-1
View File
@@ -1 +0,0 @@
export * from "ce/components/instance";
@@ -1 +0,0 @@
export const MaintenanceMessage = () => <></>;
+1
View File
@@ -0,0 +1 @@
export * from "ce/components/maintenance-mode";
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

+40 -65
View File
@@ -5647,9 +5647,9 @@ cross-fetch@^3.1.5:
node-fetch "^2.6.12"
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82"
integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
@@ -11287,16 +11287,7 @@ streamx@^2.15.0, streamx@^2.20.0:
optionalDependencies:
bare-events "^2.2.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -11384,14 +11375,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0:
dependencies:
safe-buffer "~5.2.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11928,47 +11912,47 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
turbo-darwin-64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.3.0.tgz#cf82cf4a816a267c65a71d2d3ec1baef5c6b0f78"
integrity sha512-pji+D49PhFItyQjf2QVoLZw2d3oRGo8gJgKyOiRzvip78Rzie74quA8XNwSg/DuzM7xx6gJ3p2/LylTTlgZXxQ==
turbo-darwin-64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.2.3.tgz#f0ced75ed031091e52851cbe8bb05d21a161a22b"
integrity sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==
turbo-darwin-arm64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.0.tgz#3e058a4e41130abce9df49a1fb5e271af85a1d99"
integrity sha512-AJrGIL9BO41mwDF/IBHsNGwvtdyB911vp8f5mbNo1wG66gWTvOBg7WCtYQBvCo11XTenTfXPRSsAb7w3WAZb6w==
turbo-darwin-arm64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.2.3.tgz#0b4741383ab5070d8383891a65861a8869cc7202"
integrity sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==
turbo-linux-64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.3.0.tgz#0aefff6047faed0ffdbf0980d5dd4f11ace51d65"
integrity sha512-jZqW6vc2sPJT3M/3ZmV1Cg4ecQVPqsbHncG/RnogHpBu783KCSXIndgxvUQNm9qfgBYbZDBnP1md63O4UTElhw==
turbo-linux-64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.2.3.tgz#2b339db50c12bc52ce99139c156d5555717a209d"
integrity sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==
turbo-linux-arm64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.3.0.tgz#a00db7c7a88400cc0357bfeac2beb383a35e255e"
integrity sha512-HUbDLJlvd/hxuyCNO0BmEWYQj0TugRMvSQeG8vHJH+Lq8qOgDAe7J0K73bFNbZejZQxW3C3XEiZFB3pnpO78+A==
turbo-linux-arm64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.2.3.tgz#a4daf6e0872a4e2652e2d05d68ad18cee5b10e94"
integrity sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==
turbo-windows-64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.3.0.tgz#f082688f17c73d345efbdc43fb589b1df70cd53f"
integrity sha512-c5rxrGNTYDWX9QeMzWLFE9frOXnKjHGEvQMp1SfldDlbZYsloX9UKs31TzUThzfTgTiz8NYuShaXJ2UvTMnV/g==
turbo-windows-64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.2.3.tgz#d44b3385948bd0f2ef5c2d53391f142bdd467b18"
integrity sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==
turbo-windows-arm64@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.3.0.tgz#42d77fe99f72b4862bb4cbbb0cb5dca73427270a"
integrity sha512-7qfUuYhfIVb1AZgs89DxhXK+zZez6O2ocmixEQ4hXZK7ytnBt5vaz2zGNJJKFNYIL5HX1C3tuHolnpNgDNCUIg==
turbo-windows-arm64@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.2.3.tgz#d0625ec53f467013a6f259f87f7fc4ae8670aaa4"
integrity sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==
turbo@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.3.0.tgz#01e7841fafdd870564e1ad376b42dbc8a71d52b3"
integrity sha512-/uOq5o2jwRPyaUDnwBpOR5k9mQq4c3wziBgWNWttiYQPmbhDtrKYPRBxTvA2WpgQwRIbt8UM612RMN8n/TvmHA==
turbo@^2.1.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.2.3.tgz#0f45612d62526c98c75da0682aa8c26b902b5e07"
integrity sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==
optionalDependencies:
turbo-darwin-64 "2.3.0"
turbo-darwin-arm64 "2.3.0"
turbo-linux-64 "2.3.0"
turbo-linux-arm64 "2.3.0"
turbo-windows-64 "2.3.0"
turbo-windows-arm64 "2.3.0"
turbo-darwin-64 "2.2.3"
turbo-darwin-arm64 "2.2.3"
turbo-linux-64 "2.2.3"
turbo-linux-arm64 "2.2.3"
turbo-windows-64 "2.2.3"
turbo-windows-arm64 "2.2.3"
tween-functions@^1.2.0:
version "1.2.0"
@@ -12623,16 +12607,7 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==