fix: activity tracking description (#8268)
* feat: add no_activity flag to control issue activity tracking during partial updates * refactor: rename no_activity flag to skip_activity for clarity in issue activity tracking * enhance description input handling with migration update support * feat: implement skip_activity flag to conditionally log issue updates during partial updates * refactor: skip-activity * feat: add migration description update check to conditionally log issue updates --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
parent
f0bc2bd3bd
commit
a9e9cb2983
9 changed files with 83 additions and 54 deletions
|
|
@ -322,6 +322,9 @@ class IntakeIssueViewSet(BaseViewSet):
|
||||||
|
|
||||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||||
def partial_update(self, request, slug, project_id, pk):
|
def partial_update(self, request, slug, project_id, pk):
|
||||||
|
skip_activity = request.data.pop("skip_activity", False)
|
||||||
|
is_description_update = request.data.get("description_html") is not None
|
||||||
|
|
||||||
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
intake_id = Intake.objects.filter(workspace__slug=slug, project_id=project_id).first()
|
||||||
intake_issue = IntakeIssue.objects.get(
|
intake_issue = IntakeIssue.objects.get(
|
||||||
issue_id=pk,
|
issue_id=pk,
|
||||||
|
|
@ -418,7 +421,11 @@ class IntakeIssueViewSet(BaseViewSet):
|
||||||
# Both serializers are valid, now save them
|
# Both serializers are valid, now save them
|
||||||
if issue_serializer:
|
if issue_serializer:
|
||||||
issue_serializer.save()
|
issue_serializer.save()
|
||||||
|
|
||||||
|
# Check if the update is a migration description update
|
||||||
|
is_migration_description_update = skip_activity and is_description_update
|
||||||
# Log all the updates
|
# Log all the updates
|
||||||
|
if not is_migration_description_update:
|
||||||
if issue is not None:
|
if issue is not None:
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="issue.activity.updated",
|
type="issue.activity.updated",
|
||||||
|
|
|
||||||
|
|
@ -611,6 +611,10 @@ class IssueViewSet(BaseViewSet):
|
||||||
def partial_update(self, request, slug, project_id, pk=None):
|
def partial_update(self, request, slug, project_id, pk=None):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
queryset = self.apply_annotations(queryset)
|
queryset = self.apply_annotations(queryset)
|
||||||
|
|
||||||
|
skip_activity = request.data.pop("skip_activity", False)
|
||||||
|
is_description_update = request.data.get("description_html") is not None
|
||||||
|
|
||||||
issue = (
|
issue = (
|
||||||
queryset.annotate(
|
queryset.annotate(
|
||||||
label_ids=Coalesce(
|
label_ids=Coalesce(
|
||||||
|
|
@ -659,6 +663,10 @@ class IssueViewSet(BaseViewSet):
|
||||||
serializer = IssueCreateSerializer(issue, data=request.data, partial=True, context={"project_id": project_id})
|
serializer = IssueCreateSerializer(issue, data=request.data, partial=True, context={"project_id": project_id})
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
# Check if the update is a migration description update
|
||||||
|
is_migration_description_update = skip_activity and is_description_update
|
||||||
|
# Log all the updates
|
||||||
|
if not is_migration_description_update:
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="issue.activity.updated",
|
type="issue.activity.updated",
|
||||||
requested_data=requested_data,
|
requested_data=requested_data,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const workspaceService = new WorkspaceService();
|
||||||
type TFormData = {
|
type TFormData = {
|
||||||
id: string;
|
id: string;
|
||||||
description_html: string;
|
description_html: string;
|
||||||
|
isMigrationUpdate: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -56,7 +57,7 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* @description Submit handler, the actual function which will be called when the form is submitted
|
* @description Submit handler, the actual function which will be called when the form is submitted
|
||||||
*/
|
*/
|
||||||
onSubmit: (value: string) => Promise<void>;
|
onSubmit: (value: string, isMigrationUpdate?: boolean) => Promise<void>;
|
||||||
/**
|
/**
|
||||||
* @description Placeholder, if not provided, the placeholder will be the default placeholder
|
* @description Placeholder, if not provided, the placeholder will be the default placeholder
|
||||||
*/
|
*/
|
||||||
|
|
@ -108,6 +109,7 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
|
||||||
const [localDescription, setLocalDescription] = useState<TFormData>({
|
const [localDescription, setLocalDescription] = useState<TFormData>({
|
||||||
id: entityId,
|
id: entityId,
|
||||||
description_html: initialValue?.trim() ?? "",
|
description_html: initialValue?.trim() ?? "",
|
||||||
|
isMigrationUpdate: false,
|
||||||
});
|
});
|
||||||
// ref to track if there are unsaved changes
|
// ref to track if there are unsaved changes
|
||||||
const hasUnsavedChanges = useRef(false);
|
const hasUnsavedChanges = useRef(false);
|
||||||
|
|
@ -119,17 +121,18 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// form info
|
// form info
|
||||||
const { handleSubmit, reset, control } = useForm<TFormData>({
|
const { handleSubmit, reset, control, setValue } = useForm<TFormData>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: entityId,
|
id: entityId,
|
||||||
description_html: initialValue?.trim() ?? "",
|
description_html: initialValue?.trim() ?? "",
|
||||||
|
isMigrationUpdate: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// submit handler
|
// submit handler
|
||||||
const handleDescriptionFormSubmit = useCallback(
|
const handleDescriptionFormSubmit = useCallback(
|
||||||
async (formData: TFormData) => {
|
async (formData: TFormData) => {
|
||||||
await onSubmit(formData.description_html);
|
await onSubmit(formData.description_html, formData.isMigrationUpdate);
|
||||||
},
|
},
|
||||||
[onSubmit]
|
[onSubmit]
|
||||||
);
|
);
|
||||||
|
|
@ -140,10 +143,12 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
|
||||||
reset({
|
reset({
|
||||||
id: entityId,
|
id: entityId,
|
||||||
description_html: initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
|
description_html: initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
|
||||||
|
isMigrationUpdate: false,
|
||||||
});
|
});
|
||||||
setLocalDescription({
|
setLocalDescription({
|
||||||
id: entityId,
|
id: entityId,
|
||||||
description_html: initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
|
description_html: initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
|
||||||
|
isMigrationUpdate: false,
|
||||||
});
|
});
|
||||||
// Reset unsaved changes flag when form is reset
|
// Reset unsaved changes flag when form is reset
|
||||||
hasUnsavedChanges.current = false;
|
hasUnsavedChanges.current = false;
|
||||||
|
|
@ -206,9 +211,10 @@ export const DescriptionInput = observer(function DescriptionInput(props: Props)
|
||||||
workspaceId={workspaceDetails.id}
|
workspaceId={workspaceDetails.id}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
dragDropEnabled
|
dragDropEnabled
|
||||||
onChange={(_description, description_html) => {
|
onChange={(_description, description_html, options) => {
|
||||||
setIsSubmitting("submitting");
|
setIsSubmitting("submitting");
|
||||||
onChange(description_html);
|
onChange(description_html);
|
||||||
|
setValue("isMigrationUpdate", options?.isMigrationUpdate ?? false);
|
||||||
hasUnsavedChanges.current = true;
|
hasUnsavedChanges.current = true;
|
||||||
debouncedFormSave();
|
debouncedFormSave();
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,11 @@ export const InboxIssueMainContent = observer(function InboxIssueMainContent(pro
|
||||||
entityId={issue.id}
|
entityId={issue.id}
|
||||||
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
||||||
initialValue={issue.description_html ?? "<p></p>"}
|
initialValue={issue.description_html ?? "<p></p>"}
|
||||||
onSubmit={async (value) => {
|
onSubmit={async (value, isMigrationUpdate) => {
|
||||||
if (!issue.id || !issue.project_id) return;
|
if (!issue.id || !issue.project_id) return;
|
||||||
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
||||||
description_html: value,
|
description_html: value,
|
||||||
|
...(isMigrationUpdate ? { skip_activity: "true" } : {}),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
projectId={issue.project_id}
|
projectId={issue.project_id}
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,11 @@ export const IssueMainContent = observer(function IssueMainContent(props: Props)
|
||||||
entityId={issue.id}
|
entityId={issue.id}
|
||||||
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
||||||
initialValue={issue.description_html}
|
initialValue={issue.description_html}
|
||||||
onSubmit={async (value) => {
|
onSubmit={async (value, isMigrationUpdate) => {
|
||||||
if (!issue.id || !issue.project_id) return;
|
if (!issue.id || !issue.project_id) return;
|
||||||
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
||||||
description_html: value,
|
description_html: value,
|
||||||
|
...(isMigrationUpdate ? { skip_activity: "true" } : {}),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
projectId={issue.project_id}
|
projectId={issue.project_id}
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,11 @@ export const PeekOverviewIssueDetails = observer(function PeekOverviewIssueDetai
|
||||||
entityId={issue.id}
|
entityId={issue.id}
|
||||||
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
fileAssetType={EFileAssetType.ISSUE_DESCRIPTION}
|
||||||
initialValue={issueDescription}
|
initialValue={issueDescription}
|
||||||
onSubmit={async (value) => {
|
onSubmit={async (value, isMigrationUpdate) => {
|
||||||
if (!issue.id || !issue.project_id) return;
|
if (!issue.id || !issue.project_id) return;
|
||||||
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
await issueOperations.update(workspaceSlug, issue.project_id, issue.id, {
|
||||||
description_html: value,
|
description_html: value,
|
||||||
|
...(isMigrationUpdate ? { skip_activity: "true" } : {}),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
setIsSubmitting={(value) => setIsSubmitting(value)}
|
setIsSubmitting={(value) => setIsSubmitting(value)}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export const createIdsForView = (view: EditorView, options: UniqueIDOptions) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
tr.setMeta("addToHistory", false);
|
tr.setMeta("addToHistory", false);
|
||||||
|
tr.setMeta("uniqueIdOnlyChange", true);
|
||||||
|
|
||||||
view.dispatch(tr);
|
view.dispatch(tr);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,11 @@ export const useEditor = (props: TEditorHookProps) => {
|
||||||
onTransaction: () => {
|
onTransaction: () => {
|
||||||
onTransaction?.();
|
onTransaction?.();
|
||||||
},
|
},
|
||||||
onUpdate: ({ editor }) => onChange?.(editor.getJSON(), editor.getHTML()),
|
onUpdate: ({ editor, transaction }) => {
|
||||||
|
// Check if this update is only due to migration update
|
||||||
|
const isMigrationUpdate = transaction?.getMeta("uniqueIdOnlyChange") === true;
|
||||||
|
onChange?.(editor.getJSON(), editor.getHTML(), { isMigrationUpdate });
|
||||||
|
},
|
||||||
onDestroy: () => handleEditorReady?.(false),
|
onDestroy: () => handleEditorReady?.(false),
|
||||||
onFocus: onEditorFocus,
|
onFocus: onEditorFocus,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ export type IEditorProps = {
|
||||||
mentionHandler: TMentionHandler;
|
mentionHandler: TMentionHandler;
|
||||||
onAssetChange?: (assets: TEditorAsset[]) => void;
|
onAssetChange?: (assets: TEditorAsset[]) => void;
|
||||||
onEditorFocus?: () => void;
|
onEditorFocus?: () => void;
|
||||||
onChange?: (json: object, html: string) => void;
|
onChange?: (json: object, html: string, { isMigrationUpdate }?: { isMigrationUpdate?: boolean }) => void;
|
||||||
onEnterKeyPress?: (e?: any) => void;
|
onEnterKeyPress?: (e?: any) => void;
|
||||||
onTransaction?: () => void;
|
onTransaction?: () => void;
|
||||||
placeholder?: string | ((isFocused: boolean, value: string) => string);
|
placeholder?: string | ((isFocused: boolean, value: string) => string);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue