[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:
guru_sainath
2025-01-02 13:39:02 +05:30
committed by GitHub
parent 2e0f9e3606
commit f8c19590e4
13 changed files with 392 additions and 41 deletions

View File

@@ -0,0 +1,2 @@
from .base import UserMutation
from .profile import ProfileMutation

View File

@@ -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

View 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

View File

@@ -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,
)

View 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
]

View File

@@ -0,0 +1,2 @@
from .base import UserQuery, UserFavoritesQuery, UserRecentVisitQuery
from .profile import ProfileQuery

View File

@@ -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:

View 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

View File

@@ -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

View File

@@ -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]

View File

@@ -0,0 +1,8 @@
import strawberry
@strawberry.type
class TimezoneListType:
value: str
query: str
label: str

View File

@@ -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:

View 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