mirror of
https://github.com/makeplane/plane.git
synced 2026-07-13 22:09:12 +02:00
[MOBIL-644] chore: Integrate timezone list and update user information query and profile update mutation (#2057)
* chore: updated timezones list * chore: updated mobile fileds in user profile type * chore: updated user information query and profile update mutation
This commit is contained in:
2
apiserver/plane/graphql/mutations/user/__init__.py
Normal file
2
apiserver/plane/graphql/mutations/user/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .base import UserMutation
|
||||
from .profile import ProfileMutation
|
||||
@@ -10,7 +10,7 @@ from strawberry.permission import PermissionExtension
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User, Profile, WorkspaceMember
|
||||
from plane.db.models import User
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
from plane.graphql.types.users import UserType
|
||||
|
||||
@@ -48,26 +48,3 @@ class UserMutation:
|
||||
user = await sync_to_async(User.objects.get)(id=info.context.user.id)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ProfileMutation:
|
||||
@strawberry.mutation(
|
||||
extensions=[PermissionExtension(permissions=[IsAuthenticated()])]
|
||||
)
|
||||
async def update_last_workspace(self, info: Info, workspace: strawberry.ID) -> bool:
|
||||
profile = await sync_to_async(Profile.objects.get)(user=info.context.user)
|
||||
|
||||
# Wrap the synchronous call to `exists()` with `sync_to_async`
|
||||
workspace_member_exists = await sync_to_async(
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=workspace, member=info.context.user
|
||||
).exists
|
||||
)()
|
||||
|
||||
if not workspace_member_exists:
|
||||
return False
|
||||
|
||||
profile.last_workspace_id = workspace
|
||||
await sync_to_async(profile.save)()
|
||||
return True
|
||||
50
apiserver/plane/graphql/mutations/user/profile.py
Normal file
50
apiserver/plane/graphql/mutations/user/profile.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# python imports
|
||||
from typing import Optional
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Third-party imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Module imports
|
||||
from plane.graphql.types.users import ProfileType
|
||||
from plane.db.models import Profile, WorkspaceMember
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ProfileMutation:
|
||||
@strawberry.mutation(
|
||||
extensions=[PermissionExtension(permissions=[IsAuthenticated()])]
|
||||
)
|
||||
async def update_last_workspace(self, info: Info, workspace: strawberry.ID) -> bool:
|
||||
profile = await sync_to_async(Profile.objects.get)(user=info.context.user)
|
||||
|
||||
workspace_member_exists = await sync_to_async(
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=workspace, member=info.context.user
|
||||
).exists
|
||||
)()
|
||||
|
||||
if not workspace_member_exists:
|
||||
return False
|
||||
|
||||
profile.last_workspace_id = workspace
|
||||
await sync_to_async(profile.save)()
|
||||
return True
|
||||
|
||||
@strawberry.mutation(
|
||||
extensions=[PermissionExtension(permissions=[IsAuthenticated()])]
|
||||
)
|
||||
async def update_profile(
|
||||
self, info: Info, mobile_timezone_auto_set: Optional[bool] = False
|
||||
) -> ProfileType:
|
||||
profile = await sync_to_async(Profile.objects.get)(user=info.context.user)
|
||||
|
||||
profile.mobile_timezone_auto_set = mobile_timezone_auto_set
|
||||
|
||||
await sync_to_async(profile.save)()
|
||||
return profile
|
||||
@@ -59,5 +59,8 @@ class userInformationQuery:
|
||||
device_information = None
|
||||
|
||||
return UserInformationType(
|
||||
user=info.context.user, workspace=workspace, device_info=device_information
|
||||
user=info.context.user,
|
||||
profile=profile,
|
||||
workspace=workspace,
|
||||
device_info=device_information,
|
||||
)
|
||||
|
||||
27
apiserver/plane/graphql/queries/timezone.py
Normal file
27
apiserver/plane/graphql/queries/timezone.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Third-Party Imports
|
||||
import strawberry
|
||||
|
||||
# Strawberry Imports
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.graphql.utils.timezone.base import get_timezone_list
|
||||
from plane.graphql.types.timezone import TimezoneListType
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class TimezoneListQuery:
|
||||
@strawberry.field(extensions=[PermissionExtension(permissions=[IsAuthenticated()])])
|
||||
async def timezone_list(self, info: Info) -> list[TimezoneListType]:
|
||||
timezones = get_timezone_list()
|
||||
|
||||
return [
|
||||
TimezoneListType(
|
||||
value=timezone["value"],
|
||||
query=timezone["query"],
|
||||
label=timezone["label"],
|
||||
)
|
||||
for timezone in timezones
|
||||
]
|
||||
2
apiserver/plane/graphql/queries/users/__init__.py
Normal file
2
apiserver/plane/graphql/queries/users/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .base import UserQuery, UserFavoritesQuery, UserRecentVisitQuery
|
||||
from .profile import ProfileQuery
|
||||
@@ -13,13 +13,8 @@ from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Profile, UserFavorite, UserRecentVisit
|
||||
from plane.graphql.types.users import (
|
||||
UserType,
|
||||
ProfileType,
|
||||
UserFavoriteType,
|
||||
UserRecentVisitType,
|
||||
)
|
||||
from plane.db.models import UserFavorite, UserRecentVisit
|
||||
from plane.graphql.types.users import UserType, UserFavoriteType, UserRecentVisitType
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated, WorkspaceBasePermission
|
||||
|
||||
|
||||
@@ -30,14 +25,6 @@ class UserQuery:
|
||||
return info.context.user
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ProfileQuery:
|
||||
@strawberry.field(extensions=[PermissionExtension(permissions=[IsAuthenticated()])])
|
||||
async def profile(self, info: Info) -> ProfileType:
|
||||
profile = await sync_to_async(Profile.objects.get)(user=info.context.user)
|
||||
return profile
|
||||
|
||||
|
||||
# user favorite
|
||||
@strawberry.type
|
||||
class UserFavoritesQuery:
|
||||
22
apiserver/plane/graphql/queries/users/profile.py
Normal file
22
apiserver/plane/graphql/queries/users/profile.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Third-Party Imports
|
||||
import strawberry
|
||||
|
||||
# Python Standard Library Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Strawberry Imports
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Profile
|
||||
from plane.graphql.types.users import ProfileType
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ProfileQuery:
|
||||
@strawberry.field(extensions=[PermissionExtension(permissions=[IsAuthenticated()])])
|
||||
async def profile(self, info: Info) -> ProfileType:
|
||||
profile = await sync_to_async(Profile.objects.get)(user=info.context.user)
|
||||
return profile
|
||||
@@ -50,6 +50,7 @@ from .queries.dashboard import userInformationQuery
|
||||
from .queries.external import UnsplashImagesQuery, ProjectCoversQuery
|
||||
from .queries.feature_flag import FeatureFlagQuery
|
||||
from .queries.version_check import VersionCheckQuery
|
||||
from .queries.timezone import TimezoneListQuery
|
||||
|
||||
# mutations
|
||||
from .mutations.workspace import WorkspaceMutation, WorkspaceInviteMutation
|
||||
@@ -144,6 +145,7 @@ class Query(
|
||||
FeatureFlagQuery,
|
||||
VersionCheckQuery,
|
||||
WorkspacePageQuery,
|
||||
TimezoneListQuery,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
import strawberry
|
||||
|
||||
# module imports
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.users import UserType, ProfileType
|
||||
from plane.graphql.types.workspace import WorkspaceType
|
||||
from plane.graphql.types.device import DeviceInformationType
|
||||
|
||||
@@ -12,5 +12,6 @@ from plane.graphql.types.device import DeviceInformationType
|
||||
@strawberry.type
|
||||
class UserInformationType:
|
||||
user: UserType
|
||||
profile: ProfileType
|
||||
workspace: Optional[WorkspaceType]
|
||||
device_info: Optional[DeviceInformationType]
|
||||
|
||||
8
apiserver/plane/graphql/types/timezone.py
Normal file
8
apiserver/plane/graphql/types/timezone.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import strawberry
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class TimezoneListType:
|
||||
value: str
|
||||
query: str
|
||||
label: str
|
||||
@@ -61,6 +61,9 @@ class ProfileType:
|
||||
billing_address: Optional[str]
|
||||
has_billing_address: bool
|
||||
company_name: str
|
||||
mobile_timezone_auto_set: bool
|
||||
is_mobile_onboarded: bool
|
||||
mobile_onboarding_step = Optional[JSON]
|
||||
|
||||
@strawberry.field
|
||||
def user(self) -> int:
|
||||
|
||||
267
apiserver/plane/graphql/utils/timezone/base.py
Normal file
267
apiserver/plane/graphql/utils/timezone/base.py
Normal file
@@ -0,0 +1,267 @@
|
||||
# Python imports
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def group_timezones(timezones):
|
||||
grouped_map = {}
|
||||
|
||||
for timezone in timezones:
|
||||
key = timezone["value"]
|
||||
|
||||
if key not in grouped_map:
|
||||
grouped_map[key] = {
|
||||
"utc_offset": timezone["utc_offset"],
|
||||
"gmt_offset": timezone["gmt_offset"],
|
||||
"value": timezone["value"],
|
||||
"label": timezone["label"],
|
||||
}
|
||||
else:
|
||||
existing = grouped_map[key]
|
||||
existing["label"] = f"{existing['label']}, {timezone['label']}"
|
||||
|
||||
return list(grouped_map.values())
|
||||
|
||||
|
||||
def get_timezone_list():
|
||||
timezone_mapping = {
|
||||
"-1100": [
|
||||
("Midway Island", "Pacific/Midway"),
|
||||
("American Samoa", "Pacific/Pago_Pago"),
|
||||
],
|
||||
"-1000": [("Hawaii", "Pacific/Honolulu"), ("Aleutian Islands", "America/Adak")],
|
||||
"-0930": [("Marquesas Islands", "Pacific/Marquesas")],
|
||||
"-0900": [
|
||||
("Alaska", "America/Anchorage"),
|
||||
("Gambier Islands", "Pacific/Gambier"),
|
||||
],
|
||||
"-0800": [
|
||||
("Pacific Time (US and Canada)", "America/Los_Angeles"),
|
||||
("Baja California", "America/Tijuana"),
|
||||
],
|
||||
"-0700": [
|
||||
("Mountain Time (US and Canada)", "America/Denver"),
|
||||
("Arizona", "America/Phoenix"),
|
||||
("Chihuahua, Mazatlan", "America/Chihuahua"),
|
||||
],
|
||||
"-0600": [
|
||||
("Central Time (US and Canada)", "America/Chicago"),
|
||||
("Saskatchewan", "America/Regina"),
|
||||
("Guadalajara, Mexico City, Monterrey", "America/Mexico_City"),
|
||||
("Tegucigalpa, Honduras", "America/Tegucigalpa"),
|
||||
("Costa Rica", "America/Costa_Rica"),
|
||||
],
|
||||
"-0500": [
|
||||
("Eastern Time (US and Canada)", "America/New_York"),
|
||||
("Lima", "America/Lima"),
|
||||
("Bogota", "America/Bogota"),
|
||||
("Quito", "America/Guayaquil"),
|
||||
("Chetumal", "America/Cancun"),
|
||||
],
|
||||
"-0430": [("Caracas (Old Venezuela Time)", "America/Caracas")],
|
||||
"-0400": [
|
||||
("Atlantic Time (Canada)", "America/Halifax"),
|
||||
("Caracas", "America/Caracas"),
|
||||
("Santiago", "America/Santiago"),
|
||||
("La Paz", "America/La_Paz"),
|
||||
("Manaus", "America/Manaus"),
|
||||
("Georgetown", "America/Guyana"),
|
||||
("Bermuda", "Atlantic/Bermuda"),
|
||||
],
|
||||
"-0330": [("Newfoundland Time (Canada)", "America/St_Johns")],
|
||||
"-0300": [
|
||||
("Buenos Aires", "America/Argentina/Buenos_Aires"),
|
||||
("Brasilia", "America/Sao_Paulo"),
|
||||
("Greenland", "America/Godthab"),
|
||||
("Montevideo", "America/Montevideo"),
|
||||
("Falkland Islands", "Atlantic/Stanley"),
|
||||
],
|
||||
"-0200": [
|
||||
("South Georgia and the South Sandwich Islands", "Atlantic/South_Georgia")
|
||||
],
|
||||
"-0100": [
|
||||
("Azores", "Atlantic/Azores"),
|
||||
("Cape Verde Islands", "Atlantic/Cape_Verde"),
|
||||
],
|
||||
"+0000": [
|
||||
("Dublin", "Europe/Dublin"),
|
||||
("Reykjavik", "Atlantic/Reykjavik"),
|
||||
("Lisbon", "Europe/Lisbon"),
|
||||
("Monrovia", "Africa/Monrovia"),
|
||||
("Casablanca", "Africa/Casablanca"),
|
||||
],
|
||||
"+0100": [
|
||||
("Central European Time (Berlin, Rome, Paris)", "Europe/Paris"),
|
||||
("West Central Africa", "Africa/Lagos"),
|
||||
("Algiers", "Africa/Algiers"),
|
||||
("Lagos", "Africa/Lagos"),
|
||||
("Tunis", "Africa/Tunis"),
|
||||
],
|
||||
"+0200": [
|
||||
("Eastern European Time (Cairo, Helsinki, Kyiv)", "Europe/Kiev"),
|
||||
("Athens", "Europe/Athens"),
|
||||
("Jerusalem", "Asia/Jerusalem"),
|
||||
("Johannesburg", "Africa/Johannesburg"),
|
||||
("Harare, Pretoria", "Africa/Harare"),
|
||||
],
|
||||
"+0300": [
|
||||
("Moscow Time", "Europe/Moscow"),
|
||||
("Baghdad", "Asia/Baghdad"),
|
||||
("Nairobi", "Africa/Nairobi"),
|
||||
("Kuwait, Riyadh", "Asia/Riyadh"),
|
||||
],
|
||||
"+0330": [("Tehran", "Asia/Tehran")],
|
||||
"+0400": [
|
||||
("Abu Dhabi", "Asia/Dubai"),
|
||||
("Baku", "Asia/Baku"),
|
||||
("Yerevan", "Asia/Yerevan"),
|
||||
("Astrakhan", "Europe/Astrakhan"),
|
||||
("Tbilisi", "Asia/Tbilisi"),
|
||||
("Mauritius", "Indian/Mauritius"),
|
||||
],
|
||||
"+0500": [
|
||||
("Islamabad", "Asia/Karachi"),
|
||||
("Karachi", "Asia/Karachi"),
|
||||
("Tashkent", "Asia/Tashkent"),
|
||||
("Yekaterinburg", "Asia/Yekaterinburg"),
|
||||
("Maldives", "Indian/Maldives"),
|
||||
("Chagos", "Indian/Chagos"),
|
||||
],
|
||||
"+0530": [
|
||||
("Chennai", "Asia/Kolkata"),
|
||||
("Kolkata", "Asia/Kolkata"),
|
||||
("Mumbai", "Asia/Kolkata"),
|
||||
("New Delhi", "Asia/Kolkata"),
|
||||
("Sri Jayawardenepura", "Asia/Colombo"),
|
||||
],
|
||||
"+0545": [("Kathmandu", "Asia/Kathmandu")],
|
||||
"+0600": [
|
||||
("Dhaka", "Asia/Dhaka"),
|
||||
("Almaty", "Asia/Almaty"),
|
||||
("Bishkek", "Asia/Bishkek"),
|
||||
("Thimphu", "Asia/Thimphu"),
|
||||
],
|
||||
"+0630": [
|
||||
("Yangon (Rangoon)", "Asia/Yangon"),
|
||||
("Cocos Islands", "Indian/Cocos"),
|
||||
],
|
||||
"+0700": [
|
||||
("Bangkok", "Asia/Bangkok"),
|
||||
("Hanoi", "Asia/Ho_Chi_Minh"),
|
||||
("Jakarta", "Asia/Jakarta"),
|
||||
("Novosibirsk", "Asia/Novosibirsk"),
|
||||
("Krasnoyarsk", "Asia/Krasnoyarsk"),
|
||||
],
|
||||
"+0800": [
|
||||
("Beijing", "Asia/Shanghai"),
|
||||
("Singapore", "Asia/Singapore"),
|
||||
("Perth", "Australia/Perth"),
|
||||
("Hong Kong", "Asia/Hong_Kong"),
|
||||
("Ulaanbaatar", "Asia/Ulaanbaatar"),
|
||||
("Palau", "Pacific/Palau"),
|
||||
],
|
||||
"+0845": [("Eucla", "Australia/Eucla")],
|
||||
"+0900": [
|
||||
("Tokyo", "Asia/Tokyo"),
|
||||
("Seoul", "Asia/Seoul"),
|
||||
("Yakutsk", "Asia/Yakutsk"),
|
||||
],
|
||||
"+0930": [("Adelaide", "Australia/Adelaide"), ("Darwin", "Australia/Darwin")],
|
||||
"+1000": [
|
||||
("Sydney", "Australia/Sydney"),
|
||||
("Brisbane", "Australia/Brisbane"),
|
||||
("Guam", "Pacific/Guam"),
|
||||
("Vladivostok", "Asia/Vladivostok"),
|
||||
("Tahiti", "Pacific/Tahiti"),
|
||||
],
|
||||
"+1030": [("Lord Howe Island", "Australia/Lord_Howe")],
|
||||
"+1100": [
|
||||
("Solomon Islands", "Pacific/Guadalcanal"),
|
||||
("Magadan", "Asia/Magadan"),
|
||||
("Norfolk Island", "Pacific/Norfolk"),
|
||||
("Bougainville Island", "Pacific/Bougainville"),
|
||||
("Chokurdakh", "Asia/Srednekolymsk"),
|
||||
],
|
||||
"+1200": [
|
||||
("Auckland", "Pacific/Auckland"),
|
||||
("Wellington", "Pacific/Auckland"),
|
||||
("Fiji Islands", "Pacific/Fiji"),
|
||||
("Anadyr", "Asia/Anadyr"),
|
||||
],
|
||||
"+1245": [("Chatham Islands", "Pacific/Chatham")],
|
||||
"+1300": [("Nuku'alofa", "Pacific/Tongatapu"), ("Samoa", "Pacific/Apia")],
|
||||
"+1400": [("Kiritimati Island", "Pacific/Kiritimati")],
|
||||
}
|
||||
|
||||
timezone_list = []
|
||||
now = datetime.now()
|
||||
|
||||
# Process timezone mapping
|
||||
for offset, locations in timezone_mapping.items():
|
||||
sign = "-" if offset.startswith("-") else "+"
|
||||
hours = offset[1:3]
|
||||
minutes = offset[3:] if len(offset) > 3 else "00"
|
||||
|
||||
for friendly_name, tz_identifier in locations:
|
||||
try:
|
||||
tz = pytz.timezone(tz_identifier)
|
||||
current_offset = now.astimezone(tz).strftime("%z")
|
||||
|
||||
# converting and formatting UTC offset to GMT offset
|
||||
current_utc_offset = now.astimezone(tz).utcoffset()
|
||||
total_seconds = int(current_utc_offset.total_seconds())
|
||||
hours_offset = total_seconds // 3600
|
||||
minutes_offset = abs(total_seconds % 3600) // 60
|
||||
gmt_offset = (
|
||||
f"GMT{'+' if hours_offset >= 0 else '-'}"
|
||||
f"{abs(hours_offset):02}:{minutes_offset:02}"
|
||||
)
|
||||
|
||||
timezone_value = {
|
||||
"offset": int(current_offset),
|
||||
"utc_offset": f"UTC{sign}{hours}:{minutes}",
|
||||
"gmt_offset": gmt_offset,
|
||||
"value": tz_identifier,
|
||||
"label": f"{friendly_name}",
|
||||
}
|
||||
|
||||
timezone_list.append(timezone_value)
|
||||
except pytz.exceptions.UnknownTimeZoneError:
|
||||
continue
|
||||
|
||||
# Sort by offset and then by label
|
||||
timezone_list.sort(key=lambda x: (x["offset"], x["label"]))
|
||||
|
||||
# Remove offset from final output
|
||||
for tz in timezone_list:
|
||||
del tz["offset"]
|
||||
|
||||
timezone_options_list = [
|
||||
{
|
||||
"value": timezone["value"],
|
||||
"query": (
|
||||
f"{timezone['value']} {timezone['label']}, "
|
||||
f"{timezone['gmt_offset']}, {timezone['utc_offset']}"
|
||||
),
|
||||
"label": timezone.get("label"),
|
||||
}
|
||||
for timezone in group_timezones(timezone_list)
|
||||
]
|
||||
|
||||
# Add the additional static timezone_options_list
|
||||
timezone_options_list.extend(
|
||||
[
|
||||
{
|
||||
"value": "UTC",
|
||||
"query": "utc, coordinated universal time",
|
||||
"label": "UTC",
|
||||
},
|
||||
{
|
||||
"value": "Universal",
|
||||
"query": "universal, coordinated universal time",
|
||||
"label": "Universal",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return timezone_options_list
|
||||
Reference in New Issue
Block a user