forked from github/plane
fix: track events updated, extra parameters added, added events for issues, pages, states, cycles (#2875)
* fix: event tracking method updated to store, chore: updated and added events for workspace, projects and create issue * fix: posthog auth event tracking --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
2980c7b00d
commit
11d57a5bf0
@ -35,7 +35,7 @@ from plane.settings.redis import redis_instance
|
||||
from plane.bgtasks.magic_link_code_task import magic_link
|
||||
from plane.license.models import InstanceConfiguration
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
from plane.bgtasks.event_tracking_task import auth_events
|
||||
|
||||
def get_tokens_for_user(user):
|
||||
refresh = RefreshToken.for_user(user)
|
||||
@ -162,30 +162,17 @@ class SignUpEndpoint(BaseAPIView):
|
||||
workspace_member_invites.delete()
|
||||
project_member_invites.delete()
|
||||
|
||||
try:
|
||||
# Send Analytics
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": "email",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
||||
},
|
||||
"event_type": "SIGN_UP",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium="EMAIL",
|
||||
first_time=True
|
||||
)
|
||||
|
||||
access_token, refresh_token = get_tokens_for_user(user)
|
||||
|
||||
@ -305,30 +292,17 @@ class SignInEndpoint(BaseAPIView):
|
||||
# Delete all the invites
|
||||
workspace_member_invites.delete()
|
||||
project_member_invites.delete()
|
||||
try:
|
||||
# Send Analytics
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": "email",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
||||
},
|
||||
"event_type": "SIGN_IN",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium="EMAIL",
|
||||
first_time=False
|
||||
)
|
||||
|
||||
access_token, refresh_token = get_tokens_for_user(user)
|
||||
data = {
|
||||
@ -476,32 +450,25 @@ class MagicSignInEndpoint(BaseAPIView):
|
||||
if str(token) == str(user_token):
|
||||
if User.objects.filter(email=email).exists():
|
||||
user = User.objects.get(email=email)
|
||||
try:
|
||||
# Send event to Jitsu for tracking
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": "code",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get(
|
||||
"HTTP_USER_AGENT"
|
||||
),
|
||||
},
|
||||
"event_type": "SIGN_IN",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
if not user.is_active:
|
||||
return Response(
|
||||
{
|
||||
"error": "Your account has been deactivated. Please contact your site administrator."
|
||||
},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium="MAGIC_LINK",
|
||||
first_time=False
|
||||
)
|
||||
|
||||
else:
|
||||
user = User.objects.create(
|
||||
email=email,
|
||||
@ -509,32 +476,18 @@ class MagicSignInEndpoint(BaseAPIView):
|
||||
password=make_password(uuid.uuid4().hex),
|
||||
is_password_autoset=True,
|
||||
)
|
||||
try:
|
||||
# Send event to Jitsu for tracking
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": "code",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get(
|
||||
"HTTP_USER_AGENT"
|
||||
),
|
||||
},
|
||||
"event_type": "SIGN_UP",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium="MAGIC_LINK",
|
||||
first_time=True
|
||||
)
|
||||
|
||||
user.is_active = True
|
||||
user.last_active = timezone.now()
|
||||
|
@ -29,6 +29,7 @@ from plane.db.models import (
|
||||
ProjectMemberInvite,
|
||||
ProjectMember,
|
||||
)
|
||||
from plane.bgtasks.event_tracking_task import auth_events
|
||||
from .base import BaseAPIView
|
||||
from plane.license.models import InstanceConfiguration
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
@ -285,29 +286,18 @@ class OauthEndpoint(BaseAPIView):
|
||||
"last_login_at": timezone.now(),
|
||||
},
|
||||
)
|
||||
try:
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": f"oauth-{medium}",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
||||
},
|
||||
"event_type": "SIGN_IN",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium=medium.upper(),
|
||||
first_time=False
|
||||
)
|
||||
|
||||
access_token, refresh_token = get_tokens_for_user(user)
|
||||
|
||||
@ -428,29 +418,17 @@ class OauthEndpoint(BaseAPIView):
|
||||
workspace_member_invites.delete()
|
||||
project_member_invites.delete()
|
||||
|
||||
try:
|
||||
if settings.ANALYTICS_BASE_API:
|
||||
_ = requests.post(
|
||||
settings.ANALYTICS_BASE_API,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
|
||||
},
|
||||
json={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"event_data": {
|
||||
"medium": f"oauth-{medium}",
|
||||
},
|
||||
"user": {"email": email, "id": str(user.id)},
|
||||
"device_ctx": {
|
||||
"ip": request.META.get("REMOTE_ADDR"),
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
||||
},
|
||||
"event_type": "SIGN_UP",
|
||||
},
|
||||
)
|
||||
except RequestException as e:
|
||||
capture_exception(e)
|
||||
# Send event
|
||||
if settings.POSTHOG_API_KEY and settings.POSTHOG_HOST:
|
||||
auth_events.delay(
|
||||
user=user.id,
|
||||
email=email,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT"),
|
||||
ip=request.META.get("REMOTE_ADDR"),
|
||||
event_name="SIGN_IN",
|
||||
medium=medium.upper(),
|
||||
first_time=True
|
||||
)
|
||||
|
||||
SocialLoginConnection.objects.update_or_create(
|
||||
medium=medium,
|
||||
|
31
apiserver/plane/bgtasks/event_tracking_task.py
Normal file
31
apiserver/plane/bgtasks/event_tracking_task.py
Normal file
@ -0,0 +1,31 @@
|
||||
import uuid
|
||||
|
||||
from posthog import Posthog
|
||||
from django.conf import settings
|
||||
|
||||
#third party imports
|
||||
from celery import shared_task
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
|
||||
@shared_task
|
||||
def auth_events(user, email, user_agent, ip, event_name, medium, first_time):
|
||||
print(user, email, user_agent, ip, event_name, medium, first_time)
|
||||
try:
|
||||
posthog = Posthog(settings.POSTHOG_API_KEY, host=settings.POSTHOG_HOST)
|
||||
posthog.capture(
|
||||
email,
|
||||
event=event_name,
|
||||
properties={
|
||||
"event_id": uuid.uuid4().hex,
|
||||
"user": {"email": email, "id": str(user)},
|
||||
"device_ctx": {
|
||||
"ip": ip,
|
||||
"user_agent": user_agent,
|
||||
},
|
||||
"medium": medium,
|
||||
"first_time": first_time
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
@ -322,4 +322,8 @@ ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
|
||||
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False)
|
||||
|
||||
# Use Minio settings
|
||||
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
||||
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
||||
|
||||
# Posthog settings
|
||||
POSTHOG_API_KEY = os.environ.get("POSTHOG_API_KEY", False)
|
||||
POSTHOG_HOST = os.environ.get("POSTHOG_HOST", False)
|
@ -36,3 +36,4 @@ scout-apm==2.26.1
|
||||
openpyxl==3.1.2
|
||||
beautifulsoup4==4.12.2
|
||||
dj-database-url==2.1.0
|
||||
posthog==3.0.2
|
@ -14,6 +14,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
|
||||
return (
|
||||
@ -22,6 +23,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("COMMAND_PALETTE")
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
@ -62,6 +62,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||
toggleCreateIssueModal,
|
||||
toggleCreateProjectModal,
|
||||
},
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
|
||||
// router
|
||||
@ -273,6 +274,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("COMMAND_PALETTE");
|
||||
toggleCreateIssueModal(true);
|
||||
}}
|
||||
className="focus:bg-custom-background-80"
|
||||
@ -290,6 +292,7 @@ export const CommandModal: React.FC = observer(() => {
|
||||
<Command.Item
|
||||
onSelect={() => {
|
||||
closePalette();
|
||||
setTrackElement("COMMAND_PALETTE");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
|
@ -33,6 +33,7 @@ export const CommandPalette: FC = observer(() => {
|
||||
commandPalette,
|
||||
theme: { toggleSidebar },
|
||||
user: { currentUser },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
const {
|
||||
toggleCommandPaletteModal,
|
||||
@ -112,8 +113,10 @@ export const CommandPalette: FC = observer(() => {
|
||||
}
|
||||
} else {
|
||||
if (keyPressed === "c") {
|
||||
setTrackElement("SHORTCUT_KEY");
|
||||
toggleCreateIssueModal(true);
|
||||
} else if (keyPressed === "p") {
|
||||
setTrackElement("SHORTCUT_KEY");
|
||||
toggleCreateProjectModal(true);
|
||||
} else if (keyPressed === "h") {
|
||||
toggleShortcutModal(true);
|
||||
|
@ -33,7 +33,7 @@ export interface ICyclesBoardCard {
|
||||
export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
const { cycle, workspaceSlug, projectId } = props;
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const { cycle: cycleStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// states
|
||||
@ -119,6 +119,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDeleteModal(true);
|
||||
setTrackElement("CYCLE_PAGE_BOARD_LAYOUT");
|
||||
};
|
||||
|
||||
const openCycleOverview = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
@ -252,7 +253,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
||||
<CustomMenu.MenuItem onClick={handleDeleteCycle}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span>Delete module</span>
|
||||
<span>Delete cycle</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
</>
|
||||
|
@ -38,7 +38,7 @@ type TCyclesListItem = {
|
||||
export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
const { cycle, workspaceSlug, projectId } = props;
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const { cycle: cycleStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// states
|
||||
@ -119,6 +119,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDeleteModal(true);
|
||||
setTrackElement("CYCLE_PAGE_LIST_LAYOUT");
|
||||
};
|
||||
|
||||
const openCycleOverview = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
|
@ -19,7 +19,7 @@ export interface ICyclesList {
|
||||
export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -57,7 +57,11 @@ export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
<button
|
||||
type="button"
|
||||
className="text-custom-primary-100 text-sm outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLES_PAGE_EMPTY-STATE");
|
||||
commandPaletteStore.toggleCreateCycleModal(true)
|
||||
}
|
||||
}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
|
@ -24,7 +24,7 @@ interface ICycleDelete {
|
||||
export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
const { isOpen, handleClose, cycle, workspaceSlug, projectId } = props;
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const { cycle: cycleStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// states
|
||||
@ -36,11 +36,25 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
setLoader(true);
|
||||
if (cycle?.id)
|
||||
try {
|
||||
await cycleStore.removeCycle(workspaceSlug, projectId, cycle?.id);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
await cycleStore.removeCycle(workspaceSlug, projectId, cycle?.id).then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"CYCLE_DELETE",
|
||||
{
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
postHogEventTracker(
|
||||
"CYCLE_DELETE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (cycleId) router.replace(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
||||
|
@ -24,7 +24,7 @@ const cycleService = new CycleService();
|
||||
export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
const { isOpen, handleClose, data, workspaceSlug, projectId } = props;
|
||||
// store
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const { cycle: cycleStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
// states
|
||||
const [activeProject, setActiveProject] = useState<string>(projectId);
|
||||
// toast
|
||||
@ -35,12 +35,19 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
const selectedProjectId = payload.project ?? projectId.toString();
|
||||
await cycleStore
|
||||
.createCycle(workspaceSlug, selectedProjectId, payload)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle created successfully.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"CYCLE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
@ -48,6 +55,12 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
|
||||
title: "Error!",
|
||||
message: "Error in creating cycle. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"CYCLE_CREATE",
|
||||
{
|
||||
state: "FAILED",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||
|
||||
const { cycle: cycleDetailsStore } = useMobxStore();
|
||||
const { cycle: cycleDetailsStore, trackEvent: { setTrackElement, postHogEventTracker } } = useMobxStore();
|
||||
|
||||
const cycleDetails = cycleDetailsStore.cycle_details[cycleId] ?? undefined;
|
||||
|
||||
@ -74,8 +74,27 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
|
||||
cycleService
|
||||
.patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data)
|
||||
.then(() => mutate(CYCLE_DETAILS(cycleId as string)))
|
||||
.catch((e) => console.log(e));
|
||||
.then((res) => {
|
||||
mutate(CYCLE_DETAILS(cycleId as string));
|
||||
postHogEventTracker(
|
||||
"CYCLE_UPDATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
postHogEventTracker(
|
||||
"CYCLE_UPDATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleCopyText = () => {
|
||||
@ -284,10 +303,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
cycleDetails.total_issues === 0
|
||||
? "0 Issue"
|
||||
: cycleDetails.total_issues === cycleDetails.completed_issues
|
||||
? cycleDetails.total_issues > 1
|
||||
? `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
? cycleDetails.total_issues > 1
|
||||
? `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -317,7 +336,11 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
</button>
|
||||
{!isCompleted && (
|
||||
<CustomMenu width="lg" placement="bottom-end" ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setCycleDeleteModal(true)}>
|
||||
<CustomMenu.MenuItem onClick={() => {
|
||||
setTrackElement("CYCLE_PAGE_SIDEBAR");
|
||||
setCycleDeleteModal(true)
|
||||
}
|
||||
}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span>Delete cycle</span>
|
||||
@ -367,6 +390,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
value={watch("start_date") ? watch("start_date") : cycleDetails?.start_date}
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
setTrackElement("CYCLE_PAGE_SIDEBAR_START_DATE_BUTTON");
|
||||
handleStartDateChange(val);
|
||||
}
|
||||
}}
|
||||
@ -402,6 +426,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
value={watch("end_date") ? watch("end_date") : cycleDetails?.end_date}
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
setTrackElement("CYCLE_PAGE_SIDEBAR_END_DATE_BUTTON");
|
||||
handleEndDateChange(val);
|
||||
}
|
||||
}}
|
||||
|
@ -40,7 +40,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
projectLabel: { projectLabels },
|
||||
projectState: projectStateStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
|
||||
trackEvent: { setTrackElement },
|
||||
cycleIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
@ -190,7 +190,11 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button onClick={() => {
|
||||
setTrackElement("CYCLE_PAGE_HEADER")
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
} size="sm" prependIcon={<Plus />}>
|
||||
Add Issue
|
||||
</Button>
|
||||
<button
|
||||
|
@ -14,7 +14,7 @@ export const CyclesHeader: FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
|
||||
return (
|
||||
@ -51,7 +51,11 @@ export const CyclesHeader: FC = observer(() => {
|
||||
variant="primary"
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLES_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateCycleModal(true);
|
||||
}
|
||||
}
|
||||
>
|
||||
Add Cycle
|
||||
</Button>
|
||||
|
@ -38,6 +38,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
projectLabel: { projectLabels },
|
||||
moduleIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
@ -190,7 +191,11 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button onClick={() => {
|
||||
setTrackElement("MODULE_PAGE_HEADER")
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
} size="sm" prependIcon={<Plus />}>
|
||||
Add Issue
|
||||
</Button>
|
||||
<button
|
||||
|
@ -31,6 +31,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
projectState: projectStateStore,
|
||||
inbox: inboxStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
// issue filters
|
||||
projectIssuesFilter: { issueFilters, updateFilters },
|
||||
projectIssues: {},
|
||||
@ -198,7 +199,11 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
Analytics
|
||||
</Button>
|
||||
<Button onClick={() => commandPaletteStore.toggleCreateIssueModal(true)} size="sm" prependIcon={<Plus />}>
|
||||
<Button onClick={() => {
|
||||
setTrackElement("PROJECT_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
} size="sm" prependIcon={<Plus />}>
|
||||
Add Issue
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@ export const ProjectsHeader = observer(() => {
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: {setTrackElement} } = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
|
||||
|
||||
@ -41,7 +41,10 @@ export const ProjectsHeader = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button prependIcon={<Plus />} size="sm" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
|
||||
<Button prependIcon={<Plus />} size="sm" onClick={() => {
|
||||
setTrackElement("PROJECTS_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
}}>
|
||||
Add Project
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -8,9 +8,11 @@ import githubWhiteImage from "/public/logos/github-white.png";
|
||||
// components
|
||||
import { ProductUpdatesModal } from "components/common";
|
||||
import { Breadcrumbs } from "@plane/ui";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const WorkspaceDashboardHeader = () => {
|
||||
const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
|
||||
const { trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
|
@ -46,7 +46,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, inboxId } = router.query;
|
||||
|
||||
const { inboxIssueDetails: inboxIssueDetailsStore } = useMobxStore();
|
||||
const { inboxIssueDetails: inboxIssueDetailsStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
|
||||
const {
|
||||
control,
|
||||
@ -70,6 +70,21 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`);
|
||||
handleClose();
|
||||
} else reset(defaultValues);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -172,7 +187,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
onClick={() => setCreateMore((prevData) => !prevData)}
|
||||
>
|
||||
<span className="text-xs">Create more</span>
|
||||
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
|
||||
<ToggleSwitch value={createMore} onChange={() => { }} size="md" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>
|
||||
|
@ -16,7 +16,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
const {
|
||||
@ -190,7 +190,10 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}
|
||||
onClick={() => {
|
||||
setTrackElement("JIRA_IMPORT_DETAIL");
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
}}
|
||||
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
|
||||
>
|
||||
<Plus className="h-4 w-4 text-custom-text-200" />
|
||||
|
@ -26,7 +26,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
|
||||
const { cycleIssue: cycleIssueStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { cycleIssue: cycleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -62,7 +62,10 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("CYCLE_EMPTY_STATE")
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
|
@ -15,7 +15,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { commandPalette: commandPaletteStore, project: projectStore } = useMobxStore();
|
||||
const { commandPalette: commandPaletteStore, project: projectStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
|
||||
@ -29,7 +29,10 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Project",
|
||||
onClick: () => commandPaletteStore.toggleCreateProjectModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE")
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@ -40,7 +43,10 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE")
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -22,7 +22,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
|
||||
const { moduleIssue: moduleIssueStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { moduleIssue: moduleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -58,7 +58,10 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("MODULE_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
|
@ -8,7 +8,7 @@ import { EmptyState } from "components/common";
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
@ -19,7 +19,10 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("VIEW_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -8,7 +8,7 @@ import { EmptyState } from "components/common";
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
@ -19,7 +19,10 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => commandPaletteStore.toggleCreateIssueModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("PROJECT_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -72,6 +72,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
cycleIssue: cycleIssueStore,
|
||||
moduleIssue: moduleIssueStore,
|
||||
user: userStore,
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
|
||||
const user = userStore.currentUser;
|
||||
@ -208,16 +209,27 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
title: "Success!",
|
||||
message: "Issue created successfully.",
|
||||
});
|
||||
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
}).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Issue could not be created. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (!createMore) onFormSubmitClose();
|
||||
@ -232,13 +244,12 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
|
||||
await issueDraftService
|
||||
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Draft Issue created successfully.",
|
||||
});
|
||||
|
||||
handleClose();
|
||||
setActiveProject(null);
|
||||
setFormDirtyState(null);
|
||||
@ -262,7 +273,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
|
||||
await issueDetailStore
|
||||
.updateIssue(workspaceSlug.toString(), activeProject, data.id, payload)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
if (!createMore) onFormSubmitClose();
|
||||
|
||||
setToastAlert({
|
||||
@ -270,6 +281,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
title: "Success!",
|
||||
message: "Issue updated successfully.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"ISSUE_UPDATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
@ -277,6 +295,12 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
title: "Error!",
|
||||
message: "Issue could not be updated. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"ISSUE_UPDATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { CheckCircle2, Search } from "lucide-react";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
// components
|
||||
import { Button, Loader } from "@plane/ui";
|
||||
|
||||
@ -17,6 +15,8 @@ import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-key
|
||||
import { ROLE } from "constants/workspace";
|
||||
// types
|
||||
import { IWorkspaceMemberInvitation } from "types";
|
||||
// icons
|
||||
import { CheckCircle2, Search } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
handleNextStep: () => void;
|
||||
@ -32,6 +32,7 @@ const Invitations: React.FC<Props> = (props) => {
|
||||
const {
|
||||
workspace: workspaceStore,
|
||||
user: { currentUser, updateCurrentUser },
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
@ -63,14 +64,17 @@ const Invitations: React.FC<Props> = (props) => {
|
||||
await workspaceService
|
||||
.joinWorkspaces({ invitations: invitationsRespond })
|
||||
.then(async (res) => {
|
||||
trackEvent("WORKSPACE_USER_INVITE_ACCEPT", res);
|
||||
postHogEventTracker("WORKSPACE_USER_INVITE_ACCEPT", { ...res, state: "SUCCESS" });
|
||||
await mutateInvitations();
|
||||
await workspaceStore.fetchWorkspaces();
|
||||
await mutate(USER_WORKSPACES);
|
||||
await updateLastWorkspace();
|
||||
await handleNextStep();
|
||||
})
|
||||
.finally(() => setIsJoiningWorkspaces(false));
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker("WORKSPACE_USER_INVITE_ACCEPT", { state: "FAILED" });
|
||||
}).finally(() => setIsJoiningWorkspaces(false));
|
||||
};
|
||||
|
||||
return invitations && invitations.length > 0 ? (
|
||||
@ -85,11 +89,10 @@ const Invitations: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<div
|
||||
key={invitation.id}
|
||||
className={`flex cursor-pointer items-center gap-2 border p-3.5 rounded ${
|
||||
isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-onboarding-border-200 hover:bg-onboarding-background-300/30"
|
||||
}`}
|
||||
className={`flex cursor-pointer items-center gap-2 border p-3.5 rounded ${isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-onboarding-border-200 hover:bg-onboarding-background-300/30"
|
||||
}`}
|
||||
onClick={() => handleInvitation(invitation, isSelected ? "withdraw" : "accepted")}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
|
@ -166,8 +166,7 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active || selected ? "bg-onboarding-background-400/40" : ""
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${active || selected ? "bg-onboarding-background-400/40" : ""
|
||||
} ${selected ? "text-onboarding-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
|
@ -79,7 +79,7 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||
|
||||
const { user: userStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { user: userStore, commandPalette: commandPaletteStore, trackEvent: {setTrackElement} } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||
@ -157,6 +157,7 @@ export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
onComplete();
|
||||
setTrackElement("ONBOARDING_TOUR")
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
|
@ -19,7 +19,7 @@ export const WorkspaceDashboardView = observer(() => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
|
||||
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const user = userStore.currentUser;
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
@ -69,7 +69,11 @@ export const WorkspaceDashboardView = observer(() => {
|
||||
<div className="p-5 md:p-8 pr-0">
|
||||
<h5 className="text-xl font-semibold">Create a project</h5>
|
||||
<p className="mt-2 mb-5">Manage your projects by creating issues, cycles, modules, views and pages.</p>
|
||||
<Button variant="primary" size="sm" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
|
||||
<Button variant="primary" size="sm" onClick={() => {
|
||||
setTrackElement("DASHBOARD_PAGE");
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
}
|
||||
}>
|
||||
Create Project
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ export interface IProjectCardList {
|
||||
export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
||||
const { workspaceSlug } = props;
|
||||
// store
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
|
||||
@ -57,7 +57,10 @@ export const ProjectCardList: FC<IProjectCardList> = observer((props) => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "Start something new",
|
||||
onClick: () => commandPaletteStore.toggleCreateProjectModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("PROJECTS_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -20,8 +20,6 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
|
||||
import { IWorkspaceMember } from "types";
|
||||
// constants
|
||||
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
||||
// track events
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@ -68,6 +66,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
const {
|
||||
project: projectStore,
|
||||
workspaceMember: { workspaceMembers },
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
// states
|
||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||
@ -132,11 +131,11 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
.createProject(workspaceSlug.toString(), payload)
|
||||
.then((res) => {
|
||||
const newPayload = {
|
||||
...payload,
|
||||
id: res.id
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
trackEvent(
|
||||
"CREATE_PROJECT",
|
||||
postHogEventTracker(
|
||||
"PROJECT_CREATE",
|
||||
newPayload,
|
||||
)
|
||||
setToastAlert({
|
||||
@ -150,12 +149,19 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
handleClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
Object.keys(err.data).map((key) =>
|
||||
Object.keys(err.data).map((key) => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: err.data[key],
|
||||
})
|
||||
});
|
||||
postHogEventTracker(
|
||||
"PROJECT_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
},
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
@ -380,7 +386,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<WorkspaceMemberSelect
|
||||
value={workspaceMembers?.filter((member: IWorkspaceMember) => member.member.id ===value)[0]}
|
||||
value={workspaceMembers?.filter((member: IWorkspaceMember) => member.member.id === value)[0]}
|
||||
onChange={onChange}
|
||||
options={workspaceMembers || []}
|
||||
placeholder="Select Lead"
|
||||
|
@ -16,7 +16,6 @@ import { ProjectService } from "services/project";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
export interface IProjectDetailsForm {
|
||||
project: IProject;
|
||||
@ -29,7 +28,7 @@ const projectService = new ProjectService();
|
||||
export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
const { project, workspaceSlug, isAdmin } = props;
|
||||
// store
|
||||
const { project: projectStore } = useMobxStore();
|
||||
const { project: projectStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
@ -63,7 +62,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
return projectStore
|
||||
.updateProject(workspaceSlug.toString(), project.id, payload)
|
||||
.then((res) => {
|
||||
trackEvent("UPDATE_PROJECT", res);
|
||||
postHogEventTracker(
|
||||
'PROJECT_UPDATE',
|
||||
{...res, state: "SUCCESS"}
|
||||
);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -71,7 +73,12 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
trackEvent("UPDATE_PROJECT/FAIL");
|
||||
postHogEventTracker(
|
||||
'PROJECT_UPDATE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
|
@ -37,6 +37,7 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
||||
// store
|
||||
const {
|
||||
user: { leaveProject },
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
@ -63,6 +64,12 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
||||
.then(() => {
|
||||
handleClose();
|
||||
router.push(`/${workspaceSlug}/projects`);
|
||||
postHogEventTracker(
|
||||
"PROJECT_MEMBER_LEAVE",
|
||||
{
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
@ -70,6 +77,12 @@ export const LeaveProjectModal: FC<ILeaveProjectModal> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "Something went wrong please try again later.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"PROJECT_MEMBER_LEAVE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setToastAlert({
|
||||
|
@ -19,6 +19,7 @@ export const ProjectMemberList: React.FC = observer(() => {
|
||||
// store
|
||||
const {
|
||||
projectMember: { projectMembers, fetchProjectMembers },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
|
||||
// states
|
||||
@ -56,7 +57,12 @@ export const ProjectMemberList: React.FC = observer(() => {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="primary" onClick={() => setInviteModal(true)}>
|
||||
<Button variant="primary" onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_MEMBERS_PAGE_HEADER");
|
||||
setInviteModal(true)
|
||||
}
|
||||
}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -58,6 +58,7 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
workspaceMember: { workspaceMembers },
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
@ -85,18 +86,30 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||
|
||||
await projectMemberService
|
||||
.bulkAddMembersToProject(workspaceSlug.toString(), projectId.toString(), payload)
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
onSuccess();
|
||||
onClose();
|
||||
trackEvent("PROJECT_MEMBER_INVITE");
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Member added successfully",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'PROJECT_MEMBER_INVITE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker(
|
||||
'PROJECT_MEMBER_INVITE',
|
||||
{
|
||||
state: "FAILED",
|
||||
}
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
reset(defaultValues);
|
||||
|
@ -51,8 +51,10 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store
|
||||
const {
|
||||
workspace: { currentWorkspace },
|
||||
project: { currentProjectDetails, updateProject },
|
||||
user: { currentUser, currentProjectRole },
|
||||
trackEvent: { setTrackElement, postHogEventTracker },
|
||||
} = useMobxStore();
|
||||
const isAdmin = currentProjectRole === 20;
|
||||
// hooks
|
||||
@ -87,6 +89,16 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
|
||||
<ToggleSwitch
|
||||
value={currentProjectDetails?.[feature.property as keyof IProject]}
|
||||
onChange={() => {
|
||||
console.log(currentProjectDetails?.[feature.property as keyof IProject]);
|
||||
setTrackElement("PROJECT_SETTINGS_FEATURES_PAGE");
|
||||
postHogEventTracker(`TOGGLE_${feature.title.toUpperCase()}`, {
|
||||
workspace_id: currentWorkspace?.id,
|
||||
workspace_slug: currentWorkspace?.slug,
|
||||
project_id: currentProjectDetails?.id,
|
||||
project_name: currentProjectDetails?.name,
|
||||
project_identifier: currentProjectDetails?.identifier,
|
||||
enabled: !currentProjectDetails?.[feature.property as keyof IProject]
|
||||
});
|
||||
handleSubmit({
|
||||
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],
|
||||
});
|
||||
|
@ -75,7 +75,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { project, provided, snapshot, handleCopyText, shortContextMenu = false } = props;
|
||||
// store
|
||||
const { project: projectStore, theme: themeStore } = useMobxStore();
|
||||
const { project: projectStore, theme: themeStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
@ -118,6 +118,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
};
|
||||
|
||||
const handleLeaveProject = () => {
|
||||
setTrackElement("APP_SIDEBAR_PROJECT_DROPDOWN");
|
||||
setLeaveProjectModal(true);
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,7 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
theme: { sidebarCollapsed },
|
||||
project: { joinedProjects, favoriteProjects, orderProjectsWithSortOrder, updateProjectView },
|
||||
commandPalette: { toggleCreateProjectModal },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
@ -111,9 +112,8 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
)}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`h-full overflow-y-auto px-4 space-y-2 ${
|
||||
isScrolled ? "border-t border-custom-sidebar-border-300" : ""
|
||||
}`}
|
||||
className={`h-full overflow-y-auto px-4 space-y-2 ${isScrolled ? "border-t border-custom-sidebar-border-300" : ""
|
||||
}`}
|
||||
>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="favorite-projects">
|
||||
@ -140,6 +140,7 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
<button
|
||||
className="group-hover:opacity-100 opacity-0"
|
||||
onClick={() => {
|
||||
setTrackElement("APP_SIDEBAR_FAVORITES_BLOCK")
|
||||
setIsFavoriteProjectCreate(true);
|
||||
setIsProjectModalOpen(true);
|
||||
}}
|
||||
@ -262,7 +263,11 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-3 text-sm text-custom-sidebar-text-200"
|
||||
onClick={() => toggleCreateProjectModal(true)}
|
||||
onClick={() => {
|
||||
setTrackElement("APP_SIDEBAR");
|
||||
toggleCreateProjectModal(true)
|
||||
}
|
||||
}
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
{!isCollapsed && "Add Project"}
|
||||
|
@ -18,7 +18,7 @@ import type { IState } from "types";
|
||||
import { STATES_LIST } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { GROUP_CHOICES } from "constants/project";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
import { stat } from "fs";
|
||||
|
||||
type Props = {
|
||||
data: IState | null;
|
||||
@ -44,7 +44,7 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// store
|
||||
const { projectState: projectStateStore } = useMobxStore();
|
||||
const { projectState: projectStateStore, trackEvent: { postHogEventTracker, setTrackElement } } = useMobxStore();
|
||||
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
@ -90,15 +90,18 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
.createState(workspaceSlug.toString(), projectId.toString(), formData)
|
||||
.then((res) => {
|
||||
handleClose();
|
||||
trackEvent(
|
||||
'STATE_CREATE',
|
||||
res
|
||||
)
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "State created successfully.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_CREATE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.status === 400)
|
||||
@ -113,6 +116,12 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "State could not be created. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_CREATE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -124,10 +133,13 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
.then((res) => {
|
||||
mutate(STATES_LIST(projectId.toString()));
|
||||
handleClose();
|
||||
trackEvent(
|
||||
postHogEventTracker(
|
||||
'STATE_UPDATE',
|
||||
res
|
||||
)
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
}
|
||||
);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -147,6 +159,12 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "State could not be updated. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_UPDATE',
|
||||
{
|
||||
state: "FAILED",
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -274,7 +292,10 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
||||
<Button variant="neutral-primary" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" loading={isSubmitting}>
|
||||
<Button variant="primary" type="submit" loading={isSubmitting} onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
}>
|
||||
{isSubmitting ? (data ? "Updating..." : "Creating...") : data ? "Update" : "Create"}
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -13,7 +13,6 @@ import useToast from "hooks/use-toast";
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import type { IState } from "types";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@ -29,7 +28,7 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
const { projectState: projectStateStore } = useMobxStore();
|
||||
const { projectState: projectStateStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
|
||||
// states
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
@ -49,9 +48,12 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
||||
await projectStateStore
|
||||
.deleteState(workspaceSlug.toString(), data.project, data.id)
|
||||
.then((res) => {
|
||||
trackEvent(
|
||||
postHogEventTracker(
|
||||
'STATE_DELETE',
|
||||
)
|
||||
{
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
handleClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -68,6 +70,12 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "State could not be deleted. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_DELETE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsDeleteLoading(false);
|
||||
|
@ -30,6 +30,7 @@ export const ProjectSettingListItem: React.FC<Props> = observer((props) => {
|
||||
// store
|
||||
const {
|
||||
projectState: { markStateAsDefault, moveStatePosition },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
|
||||
// states
|
||||
@ -107,7 +108,12 @@ export const ProjectSettingListItem: React.FC<Props> = observer((props) => {
|
||||
className={`group-hover:opacity-100 opacity-0 ${
|
||||
state.default || groupLength === 1 ? "cursor-not-allowed" : ""
|
||||
} grid place-items-center`}
|
||||
onClick={handleDeleteState}
|
||||
onClick={
|
||||
() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
handleDeleteState()
|
||||
}
|
||||
}
|
||||
disabled={state.default || groupLength === 1}
|
||||
>
|
||||
{state.default ? (
|
||||
|
@ -23,6 +23,7 @@ export const ProjectSettingStateList: React.FC = observer(() => {
|
||||
// store
|
||||
const {
|
||||
projectState: { groupedProjectStates, projectStates, fetchProjectStates },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
// state
|
||||
const [activeGroup, setActiveGroup] = useState<StateGroup>(null);
|
||||
@ -58,7 +59,11 @@ export const ProjectSettingStateList: React.FC = observer(() => {
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-custom-primary-100 px-2 hover:text-custom-primary-200 outline-none"
|
||||
onClick={() => setActiveGroup(group as keyof StateGroup)}
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATES_PAGE");
|
||||
setActiveGroup(group as keyof StateGroup)
|
||||
}
|
||||
}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>
|
||||
|
@ -51,7 +51,7 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { workspace: workspaceStore } = useMobxStore();
|
||||
const { workspace: workspaceStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -73,15 +73,12 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
await workspaceStore
|
||||
.createWorkspace(formData)
|
||||
.then(async (res) => {
|
||||
const payload = {
|
||||
name: formData.name,
|
||||
slug: formData.slug,
|
||||
workspace_url: formData.url,
|
||||
organization_size: formData.organization_size
|
||||
};
|
||||
trackEvent(
|
||||
"CREATE_WORKSPACE",
|
||||
payload
|
||||
postHogEventTracker(
|
||||
"WORKSPACE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
},
|
||||
)
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
@ -92,11 +89,19 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
if (onSubmit) await onSubmit(res);
|
||||
})
|
||||
.catch(() =>
|
||||
{
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Workspace could not be created. Please try again.",
|
||||
})
|
||||
postHogEventTracker(
|
||||
"WORKSPACE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
},
|
||||
)
|
||||
}
|
||||
);
|
||||
} else setSlugError(true);
|
||||
})
|
||||
@ -106,6 +111,12 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||
title: "Error!",
|
||||
message: "Some error occurred while creating workspace. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
"WORKSPACE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
},
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,6 @@ import useToast from "hooks/use-toast";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import type { IWorkspace } from "types";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@ -30,7 +29,7 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { workspace: workspaceStore } = useMobxStore();
|
||||
const { workspace: workspaceStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -60,14 +59,16 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
||||
.deleteWorkspace(data.slug)
|
||||
.then((res) => {
|
||||
handleClose();
|
||||
console.log('DELETE WORKPSACE', res);
|
||||
router.push("/");
|
||||
const payload = {
|
||||
slug: data.slug
|
||||
};
|
||||
trackEvent(
|
||||
'DELETE_WORKSPACE',
|
||||
payload
|
||||
postHogEventTracker(
|
||||
'WORKSPACE_DELETE',
|
||||
{
|
||||
res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
@ -76,11 +77,19 @@ export const DeleteWorkspaceModal: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
})
|
||||
.catch(() =>
|
||||
{
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Something went wrong. Please try again later.",
|
||||
})
|
||||
postHogEventTracker(
|
||||
'WORKSPACE_DELETE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -40,6 +40,7 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
const {
|
||||
workspace: { currentWorkspace, updateWorkspace },
|
||||
user: { currentWorkspaceRole },
|
||||
trackEvent: { postHogEventTracker }
|
||||
} = useMobxStore();
|
||||
const isAdmin = currentWorkspaceRole === 20;
|
||||
// hooks
|
||||
@ -66,14 +67,28 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
|
||||
await updateWorkspace(currentWorkspace.slug, payload)
|
||||
.then((res) => {
|
||||
trackEvent("UPDATE_WORKSPACE", res);
|
||||
postHogEventTracker(
|
||||
'WORKSPACE_UPDATE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
)
|
||||
setToastAlert({
|
||||
title: "Success",
|
||||
type: "success",
|
||||
message: "Workspace updated successfully",
|
||||
});
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
}).catch((err) => {
|
||||
postHogEventTracker(
|
||||
'WORKSPACE_UPDATE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
console.error(err)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveLogo = () => {
|
||||
@ -262,10 +277,9 @@ export const WorkspaceDetails: FC = observer(() => {
|
||||
id="url"
|
||||
name="url"
|
||||
type="url"
|
||||
value={`${
|
||||
typeof window !== "undefined" &&
|
||||
value={`${typeof window !== "undefined" &&
|
||||
window.location.origin.replace("http://", "").replace("https://", "")
|
||||
}/${currentWorkspace.slug}`}
|
||||
}/${currentWorkspace.slug}`}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.url)}
|
||||
|
@ -56,6 +56,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
theme: { sidebarCollapsed },
|
||||
workspace: { workspaces, currentWorkspace: activeWorkspace },
|
||||
user: { currentUser, updateCurrentUser, isUserInstanceAdmin },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
@ -203,6 +204,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTrackElement("APP_SIEDEBAR_WORKSPACE_DROPDOWN");
|
||||
router.push("/create-workspace");
|
||||
}}
|
||||
className="flex w-full items-center gap-2 px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
|
@ -14,7 +14,7 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
// states
|
||||
const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false);
|
||||
|
||||
const { theme: themeStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { theme: themeStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
|
||||
const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", JSON.stringify({}));
|
||||
|
||||
@ -34,23 +34,24 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`flex items-center justify-between w-full cursor-pointer px-4 mt-4 ${
|
||||
isSidebarCollapsed ? "flex-col gap-1" : "gap-2"
|
||||
}`}
|
||||
className={`flex items-center justify-between w-full cursor-pointer px-4 mt-4 ${isSidebarCollapsed ? "flex-col gap-1" : "gap-2"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${
|
||||
isSidebarCollapsed
|
||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||
: "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
|
||||
}`}
|
||||
className={`relative flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${isSidebarCollapsed
|
||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||
: "px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none ${
|
||||
isSidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
onClick={() => commandPaletteStore.toggleCreateIssueModal(true)}
|
||||
className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none ${isSidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setTrackElement("APP_SIDEBAR_QUICK_ACTIONS");
|
||||
commandPaletteStore.toggleCreateIssueModal(true);
|
||||
}
|
||||
}
|
||||
>
|
||||
<PenSquare className="h-4 w-4 text-custom-sidebar-text-300" />
|
||||
{!isSidebarCollapsed && <span className="text-sm font-medium">New Issue</span>}
|
||||
@ -62,9 +63,8 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5 ${
|
||||
isSidebarCollapsed ? "hidden" : "block"
|
||||
}`}
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5 ${isSidebarCollapsed ? "hidden" : "block"
|
||||
}`}
|
||||
>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
@ -73,9 +73,8 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={`fixed h-10 pt-2 w-[203px] left-4 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto ${
|
||||
isSidebarCollapsed ? "top-[5.5rem]" : "top-24"
|
||||
}`}
|
||||
className={`fixed h-10 pt-2 w-[203px] left-4 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto ${isSidebarCollapsed ? "top-[5.5rem]" : "top-24"
|
||||
}`}
|
||||
>
|
||||
<div className="w-full h-full">
|
||||
<button
|
||||
@ -92,11 +91,10 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${
|
||||
isSidebarCollapsed
|
||||
? "hover:bg-custom-sidebar-background-80"
|
||||
: "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
|
||||
}`}
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none ${isSidebarCollapsed
|
||||
? "hover:bg-custom-sidebar-background-80"
|
||||
: "shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200"
|
||||
}`}
|
||||
onClick={() => commandPaletteStore.toggleCommandPaletteModal(true)}
|
||||
>
|
||||
<Search className="h-4 w-4 text-custom-sidebar-text-300" />
|
||||
|
@ -23,6 +23,7 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||
const {
|
||||
project: { workspaceProjects },
|
||||
commandPalette: { toggleCreateProjectModal },
|
||||
trackEvent: { setTrackElement }
|
||||
} = useMobxStore();
|
||||
|
||||
return (
|
||||
@ -35,11 +36,10 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||
<Tab
|
||||
key={tab.key}
|
||||
className={({ selected }) =>
|
||||
`rounded-3xl border border-custom-border-200 px-4 py-2 text-xs hover:bg-custom-background-80 ${
|
||||
selected ? "bg-custom-background-80" : ""
|
||||
`rounded-3xl border border-custom-border-200 px-4 py-2 text-xs hover:bg-custom-background-80 ${selected ? "bg-custom-background-80" : ""
|
||||
}`
|
||||
}
|
||||
onClick={() => {}}
|
||||
onClick={() => { }}
|
||||
>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
@ -64,7 +64,10 @@ const AnalyticsPage: NextPageWithLayout = observer(() => {
|
||||
primaryButton={{
|
||||
icon: <Plus className="h-4 w-4" />,
|
||||
text: "New Project",
|
||||
onClick: () => toggleCreateProjectModal(true),
|
||||
onClick: () => {
|
||||
setTrackElement("ANALYTICS_EMPTY_STATE");
|
||||
toggleCreateProjectModal(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -26,6 +26,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||
// store
|
||||
const {
|
||||
workspaceMember: { inviteMembersToWorkspace },
|
||||
trackEvent: { postHogEventTracker, setTrackElement }
|
||||
} = useMobxStore();
|
||||
// states
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
@ -37,9 +38,9 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||
.then(async () => {
|
||||
.then(async (res) => {
|
||||
setInviteModal(false);
|
||||
trackEvent("WORKSPACE_USER_INVITE");
|
||||
postHogEventTracker("WORKSPACE_USER_INVITE", { ...res, state: "SUCCESS" });
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -47,7 +48,7 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
trackEvent("WORKSPACE_USER_INVITE/FAIL");
|
||||
postHogEventTracker("WORKSPACE_USER_INVITE", { state: "FAILED" });
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
@ -78,7 +79,11 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||
<Button variant="primary" size="sm" onClick={() => {
|
||||
setTrackElement("WORKSPACE_SETTINGS_MEMBERS_PAGE_HEADER");
|
||||
setInviteModal(true)
|
||||
}
|
||||
}>
|
||||
Add Member
|
||||
</Button>
|
||||
</div>
|
||||
|
80
web/store/event-tracker.store.ts
Normal file
80
web/store/event-tracker.store.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import posthog from "posthog-js";
|
||||
import { RootStore } from "./root";
|
||||
|
||||
export interface ITrackEventStore {
|
||||
trackElement: string;
|
||||
setTrackElement: (element: string) => void;
|
||||
postHogEventTracker: (
|
||||
eventName: string,
|
||||
payload: object | [] | null
|
||||
// group: { isGrouping: boolean; groupType: string; gorupId: string } | null
|
||||
) => void;
|
||||
}
|
||||
|
||||
export class TrackEventStore implements ITrackEventStore {
|
||||
trackElement: string = "";
|
||||
rootStore;
|
||||
constructor(_rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
trackElement: observable,
|
||||
setTrackElement: action,
|
||||
postHogEventTracker: action,
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
}
|
||||
|
||||
setTrackElement = (element: string) => {
|
||||
this.trackElement = element;
|
||||
};
|
||||
|
||||
postHogEventTracker = (
|
||||
eventName: string,
|
||||
payload: object | [] | null
|
||||
// group: { isGrouping: boolean; groupType: string; gorupId: string } | null
|
||||
) => {
|
||||
try {
|
||||
console.log("POSTHOG_EVENT: ", eventName);
|
||||
let extras: any = {
|
||||
workspace_name: this.rootStore.workspace.currentWorkspace?.name ?? "",
|
||||
workspace_id: this.rootStore.workspace.currentWorkspace?.id ?? "",
|
||||
workspace_slug: this.rootStore.workspace.currentWorkspace?.slug ?? "",
|
||||
project_name: this.rootStore.project.currentProjectDetails?.name ?? "",
|
||||
project_id: this.rootStore.project.currentProjectDetails?.id ?? "",
|
||||
project_identifier: this.rootStore.project.currentProjectDetails?.identifier ?? "",
|
||||
};
|
||||
if (["PROJECT_CREATE", "PROJECT_UPDATE"].includes(eventName)) {
|
||||
const project_details: any = payload as object;
|
||||
extras = {
|
||||
...extras,
|
||||
project_name: project_details?.name ?? "",
|
||||
project_id: project_details?.id ?? "",
|
||||
project_identifier: project_details?.identifier ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
// if (group!.isGrouping === true) {
|
||||
// posthog?.group(group!.groupType, group!.gorupId, {
|
||||
// name: "PostHog",
|
||||
// subscription: "subscription",
|
||||
// date_joined: "2020-01-23T00:00:00.000Z",
|
||||
// });
|
||||
// console.log("END OF GROUPING");
|
||||
// posthog?.capture(eventName, {
|
||||
// ...payload,
|
||||
// element: this.trackElement ?? "",
|
||||
// });
|
||||
// } else {
|
||||
posthog?.capture(eventName, {
|
||||
...payload,
|
||||
extras: extras,
|
||||
element: this.trackElement ?? "",
|
||||
});
|
||||
// }
|
||||
console.log(payload);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
this.setTrackElement("");
|
||||
};
|
||||
}
|
@ -160,6 +160,8 @@ import { ModuleIssueFiltersStore, IModuleIssueFiltersStore } from "store/module-
|
||||
import { IMentionsStore, MentionsStore } from "store/editor";
|
||||
// pages
|
||||
import { PageStore, IPageStore } from "store/page.store";
|
||||
// event tracking
|
||||
import { TrackEventStore, ITrackEventStore } from "./event-tracker.store";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
@ -261,6 +263,8 @@ export class RootStore {
|
||||
|
||||
page: IPageStore;
|
||||
|
||||
trackEvent: ITrackEventStore;
|
||||
|
||||
constructor() {
|
||||
this.instance = new InstanceStore(this);
|
||||
|
||||
@ -357,5 +361,7 @@ export class RootStore {
|
||||
this.moduleIssueFilters = new ModuleIssueFiltersStore(this);
|
||||
|
||||
this.page = new PageStore(this);
|
||||
|
||||
this.trackEvent = new TrackEventStore(this);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user