Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release User Metadata tab #34734

Merged
merged 15 commits into from
Jun 11, 2024
Merged

Release User Metadata tab #34734

merged 15 commits into from
Jun 11, 2024

Conversation

esoergel
Copy link
Contributor

@esoergel esoergel commented Jun 5, 2024

Product Description

Add some basic metadata to the user page:

image

Technical Summary

https://dimagi.atlassian.net/browse/USH-4586

This also adds a template filter to format timestamps in django templates, and removes and replaces an old template tag that was not as nice to use.

Feature Flag

Safety Assurance

This is already live behind a feature flag.

Safety story

Automated test coverage

QA Plan

Rollback instructions

  • This PR can be reverted after deploy with no further considerations

Labels & Review

  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@esoergel esoergel added the product/all-users-all-environments Change impacts all users on all environments label Jun 5, 2024
@esoergel esoergel changed the title Release User Metadata tab` Release User Metadata tab Jun 5, 2024
@esoergel esoergel marked this pull request as ready for review June 7, 2024 17:33
Copy link
Contributor

@orangejenny orangejenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, glad to see this getting released. Nice cleanup of the old time zone tag.

One other UI comment, looking at the screenshot: I think wrapping the device timestamps / device ids in a table (or two columns - doesn't need all the stripes & borders), and sorting them by timestamp, would be a notable UX improvement for not a lot of effort.

@@ -746,3 +754,14 @@ def request_has_privilege(request, privilege_name):
from corehq import privileges
privilege = _get_obj_from_name_or_instance(privileges, privilege_name)
return domain_has_privilege(request.domain, privilege)


@register.filter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much nicer.

<td>{{ couch_user.last_login|to_user_time:request }}</td>
</tr>
<tr>
<td title="Approximate date/time of the last time this user synced with the server from CommCare">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The titles are informative, but hard to discover, how about switching them to help bubbles? Example:

<span class="hq-help-template"
data-placement="left"
data-title="{% trans 'Linked Configurations' %}"
data-content="
{% blocktrans %}
Linked configurations are controlled from a separate, linked project space.
<a href='https://confluence.dimagi.com/display/commcarepublic/Enterprise+Release+Management' target='_blank'>Learn more</a>.
{% endblocktrans %}">
</span>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, and I forgot to translate them 🤦 - apparently I'm bad at that.

@esoergel
Copy link
Contributor Author

esoergel commented Jun 11, 2024

@orangejenny thanks for the feedback - just pushed those changes and updated the screenshot in the PR description

@esoergel esoergel merged commit 344e59b into master Jun 11, 2024
13 checks passed
@esoergel esoergel deleted the es/support-toolz branch June 11, 2024 18:37
try:
return get_timezone_for_user(couch_user, domain)
except AttributeError:
return get_timezone_for_user(None, domain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this was an exact copy of corehq.util.timezones.utils.get_timezone
👍



@register.filter
def to_user_time(dt, request):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: definitely the filter is useful. Thinking if this is a good opportunity to name this better?

the function get_timezone being called does fallback to domain timezone if user is missing or still use domain's timezone if override_global_tz is set to False. So, considering the timezone used could be not that of the user, to_user_time could be misleading.
Could we say for_user_display or to_user_or_domain_timezone. I do find get_timezone could also be named better, like, get_timezone_for_user_or_domain or get_timezone_for_display but that is out of scope of this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was attempting to mirror the language used in corehq.util.timezones, which talks about datetimes as "server time", "user time", or "phone time". In that context, "server time" is UTC equivalent, but without a timezone attached, and "user time" is a datetime localized to whatever timezone is appropriate to show to the user.

the function get_timezone being called does fallback to domain timezone if user is missing or still use domain's timezone if override_global_tz is set to False

I feel like that is an implementation detail detail at a lower abstraction level. I think get_timezone answers the question of "what timezone should we use for this user on this domain"? That might be a timezone specified on the user, or on the domain, or just UTC, but the answer to that question should be the same in each part of HQ. So rather than have say the case data page use the user's timezone and the sms logs use the domain timezone, and something else use UTC, they should all use the "user time", as defined by get_timezone (and get_timezone_for_user, which it calls).

Relatedly, I actually kind of think get_timezone should take request as its only arg, since you probably want to use the domain from the active request, and it already falls back to UTC if there is no domain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.

I believe I tend to have the naming really explicit and seeing _user_time didn't sit well with me because it would at times not be the user time which in my head should be the timezone user had set to and be none or raise error if there was no timezone set and leave for the calling function to figure out what it wants to do, even if it was to fallback to domain timezone.
Though its not always feasible to put everything in the name. So I am not complaining about how its here.

I think get_timezone answers the question of "what timezone should we use for this user on this domain"?

Right. I don't prefer to have to look at the function definition to understand what it would be doing or have the name be totally ambiguous so I am forced to look into it. This is in the middle.

Relatedly, I actually kind of think get_timezone should take request as its only arg, since you probably want to use the domain from the active request, and it already falls back to UTC if there is no domain.

Ya I was unsure in what cases it needed domain to be passed other than the request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to have the naming really explicit

I've noticed you and I are often on opposite sides of the spectrum here. I find long names to hurt readability. I also think of a function's parameter names as part of its public interface, so get_timezone_from_request(request) tells me nothing that get_timezone(request) doesn't. I know it's getting it from the request, because I can see it's being passed in.

it would at times not be the user time

I disagree with this, actually - it's a question of "user" vs user. It will not always be the timezone specified on the CouchUser's domain_membership, but it will always be timezone that should be shown to the user. Every time we localize a timestamp to display to the user, we should use the same logic to determine what timezone to use. There's a quote from Clean Code I'm reminded of:

Another way to know that a function is doing more than “one thing” is if you can extract another function from it with a name that is not merely a restatement of its implementation

This functionality expresses an abstraction (what timezone should we use), and callers shouldn't need to know about the implementation. To accurately express the logic happening here it'd have to be something like get_timezone_from_user_if_global_overridden_else_domain. Assuming the name is clear enough to say what the function does, I shouldn't need to think about how it does it when calling it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find long names to hurt readability

same same. If a name needs to be long, the function itself probably can be split.

I also think of a function's parameter names as part of its public interface

Oh yes, Like i would not mind get_timezone(user), which I would read as get timezone for user. So I am aligned with you on this.

It will not always be the timezone specified on the CouchUser's domain_membership, but it will always be timezone that should be shown to the user

Exactly. And that distinction was what I was getting to with get_timezone_for_display to clarify that we are focused on timezone that we should show or display.

Another way to know that a function is doing more than “one thing” is if you can extract another function from it with a name that is not merely a restatement of its implementation

I like that.

This functionality expresses an abstraction (what timezone should we use), and callers shouldn't need to know about the implementation

Ya, that makes sense to me, like:

def to_user_time(dt, request):
    timezone = _get_timezone_to_use(...)
    return dt.to_timezone(timezone)

To accurately express the logic happening here it'd have to be something like get_timezone_from_user_if_global_overridden_else_domain

haha, that would be too much! I might have misspoken about what I like the naming to be.

@mkangia
Copy link
Contributor

mkangia commented Jun 13, 2024

This is already live behind a feature flag.

Nice to see more features getting GA'ed. Just checking if this needed any documentation updates for users?

</tr>
</thead>
<tbody>
{% for device in couch_user.devices|dictsortreversed:"last_used" %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this information limited to the request domain or is it showing devices for all domains?
Same for other metadata, is it limited to the active domain only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for mobile workers only, so it's all constrained to the domain

@mkangia
Copy link
Contributor

mkangia commented Jun 13, 2024

Jumped a little late on review. Just had one concern https://github.com/dimagi/commcare-hq/pull/34734/files#r1637787037.

@esoergel
Copy link
Contributor Author

Just checking if this needed any documentation updates for users?

Good point, I'll follow up in the ticket

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product/all-users-all-environments Change impacts all users on all environments
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants