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

Improvements to ensemble selector #427

Merged
merged 11 commits into from
Oct 16, 2023
2 changes: 2 additions & 0 deletions backend/src/backend/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .routers.well.router import router as well_router
from .routers.seismic.router import router as seismic_router
from .routers.surface_polygons.router import router as surface_polygons_router
from .routers.graph.router import router as graph_router

logging.basicConfig(
level=logging.WARNING,
Expand Down Expand Up @@ -60,6 +61,7 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app.include_router(well_router, prefix="/well", tags=["well"])
app.include_router(seismic_router, prefix="/seismic", tags=["seismic"])
app.include_router(surface_polygons_router, prefix="/surface_polygons", tags=["surface_polygons"])
app.include_router(graph_router, prefix="/graph", tags=["graph"])

authHelper = AuthHelper()
app.include_router(authHelper.router)
Expand Down
18 changes: 15 additions & 3 deletions backend/src/backend/primary/routers/explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class FieldInfo(BaseModel):
class CaseInfo(BaseModel):
uuid: str
name: str
status: str
user: str


class EnsembleInfo(BaseModel):
Expand Down Expand Up @@ -64,11 +66,21 @@ async def get_cases(
if field_identifier == "DROGON":
for case_info in case_info_arr:
if case_info.uuid == "10f41041-2c17-4374-a735-bb0de62e29dc":
ret_arr.insert(0, CaseInfo(uuid=case_info.uuid, name=f"GOOD -- {case_info.name}"))
ret_arr.insert(
0,
CaseInfo(
uuid=case_info.uuid,
name=f"GOOD -- {case_info.name}",
status=case_info.status,
user=case_info.user,
),
)
else:
ret_arr.append(CaseInfo(uuid=case_info.uuid, name=case_info.name))
ret_arr.append(
CaseInfo(uuid=case_info.uuid, name=case_info.name, status=case_info.status, user=case_info.user)
)
else:
ret_arr = [CaseInfo(uuid=ci.uuid, name=ci.name) for ci in case_info_arr]
ret_arr = [CaseInfo(uuid=ci.uuid, name=ci.name, status=ci.status, user=ci.user) for ci in case_info_arr]

return ret_arr

Expand Down
4 changes: 2 additions & 2 deletions backend/src/backend/primary/routers/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ async def logged_in_user(
if authenticated_user.has_graph_access_token() and includeGraphApiInfo:
graph_api_access = GraphApiAccess(authenticated_user.get_graph_access_token())
try:
avatar_b64str_future = asyncio.create_task(graph_api_access.get_user_profile_photo())
graph_user_info_future = asyncio.create_task(graph_api_access.get_user_info())
avatar_b64str_future = asyncio.create_task(graph_api_access.get_user_profile_photo("me"))
graph_user_info_future = asyncio.create_task(graph_api_access.get_user_info("me"))

avatar_b64str = await avatar_b64str_future
graph_user_info = await graph_user_info_future
Expand Down
Empty file.
42 changes: 42 additions & 0 deletions backend/src/backend/primary/routers/graph/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import logging

import httpx
from fastapi import APIRouter, Depends, Query

from src.backend.auth.auth_helper import AuthHelper
from src.services.utils.authenticated_user import AuthenticatedUser
from src.services.graph_access.graph_access import GraphApiAccess

from .schemas import GraphUserPhoto

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/user_photo/")
async def user_info(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
user_id: str = Query(description="User id"),
# fmt:on
) -> GraphUserPhoto:
"""Get username, display name and avatar from Microsoft Graph API for a given user id"""

user_photo = GraphUserPhoto(
avatar_b64str=None,
)

if authenticated_user.has_graph_access_token():
graph_api_access = GraphApiAccess(authenticated_user.get_graph_access_token())
try:
avatar_b64str = await graph_api_access.get_user_profile_photo(user_id)

user_photo.avatar_b64str = avatar_b64str
except httpx.HTTPError as exc:
print("Error while fetching user avatar and info from Microsoft Graph API (HTTP error):\n", exc)
except httpx.InvalidURL as exc:
print("Error while fetching user avatar and info from Microsoft Graph API (Invalid URL):\n", exc)

# Return 404 if no user info was found?
return user_photo
5 changes: 5 additions & 0 deletions backend/src/backend/primary/routers/graph/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pydantic import BaseModel


class GraphUserPhoto(BaseModel):
avatar_b64str: str | None = None
2 changes: 1 addition & 1 deletion backend/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
SMDA_SUBSCRIPTION_KEY = os.environ["WEBVIZ_SMDA_SUBSCRIPTION_KEY"]
SMDA_RESOURCE_SCOPE = os.environ["WEBVIZ_SMDA_RESOURCE_SCOPE"]
SUMO_ENV = os.getenv("WEBVIZ_SUMO_ENV", "dev")
GRAPH_SCOPES = ["User.Read"]
GRAPH_SCOPES = ["User.Read", "User.ReadBasic.All"]

RESOURCE_SCOPES_DICT = {
"sumo": [f"api://{sumo_app_reg[SUMO_ENV]['RESOURCE_ID']}/access_as_user"],
Expand Down
20 changes: 14 additions & 6 deletions backend/src/services/graph_access/graph_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,26 @@ async def _request(self, url: str) -> httpx.Response:
)
return response

async def get_user_profile_photo(self) -> str | None:
print("entering get_user_profile_photo")
response = await self._request("https://graph.microsoft.com/v1.0/me/photo/$value")
async def get_user_profile_photo(self, user_id: str) -> str | None:
request_url = f"https://graph.microsoft.com/v1.0/me/photo/$value"

if user_id != "me":
request_url = f"https://graph.microsoft.com/v1.0/users/{user_id}/photo/$value"
rubenthoms marked this conversation as resolved.
Show resolved Hide resolved

response = await self._request(request_url)

if response.status_code == 200:
return base64.b64encode(response.content).decode("utf-8")
else:
return None

async def get_user_info(self) -> Mapping[str, str] | None:
print("entering get_user_info")
response = await self._request("https://graph.microsoft.com/v1.0/me")
async def get_user_info(self, user_id: str) -> Mapping[str, str] | None:
request_url = f"https://graph.microsoft.com/v1.0/me"

if user_id != "me":
request_url = f"https://graph.microsoft.com/v1.0/users/{user_id}"
rubenthoms marked this conversation as resolved.
Show resolved Hide resolved

response = await self._request(request_url)

if response.status_code == 200:
return response.json()
Expand Down
4 changes: 3 additions & 1 deletion backend/src/services/sumo_access/sumo_explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class AssetInfo(BaseModel):
class CaseInfo(BaseModel):
uuid: str
name: str
status: str
user: str


class IterationInfo(BaseModel):
Expand Down Expand Up @@ -49,7 +51,7 @@ async def get_cases(self, field_identifier: str) -> List[CaseInfo]:

case_info_arr: List[CaseInfo] = []
async for case in case_collection:
case_info_arr.append(CaseInfo(uuid=case.uuid, name=case.name))
case_info_arr.append(CaseInfo(uuid=case.uuid, name=case.name, status=case.status, user=case.user))

# Sort on case name before returning
case_info_arr.sort(key=lambda case_info: case_info.name)
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AxiosHttpRequest } from './core/AxiosHttpRequest';

import { DefaultService } from './services/DefaultService';
import { ExploreService } from './services/ExploreService';
import { GraphService } from './services/GraphService';
import { GridService } from './services/GridService';
import { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
import { ParametersService } from './services/ParametersService';
Expand All @@ -24,6 +25,7 @@ export class ApiService {

public readonly default: DefaultService;
public readonly explore: ExploreService;
public readonly graph: GraphService;
public readonly grid: GridService;
public readonly inplaceVolumetrics: InplaceVolumetricsService;
public readonly parameters: ParametersService;
Expand Down Expand Up @@ -52,6 +54,7 @@ export class ApiService {

this.default = new DefaultService(this.request);
this.explore = new ExploreService(this.request);
this.graph = new GraphService(this.request);
this.grid = new GridService(this.request);
this.inplaceVolumetrics = new InplaceVolumetricsService(this.request);
this.parameters = new ParametersService(this.request);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type { EnsembleSensitivity as EnsembleSensitivity_api } from './models/En
export type { EnsembleSensitivityCase as EnsembleSensitivityCase_api } from './models/EnsembleSensitivityCase';
export type { FieldInfo as FieldInfo_api } from './models/FieldInfo';
export { Frequency as Frequency_api } from './models/Frequency';
export type { GraphUserPhoto as GraphUserPhoto_api } from './models/GraphUserPhoto';
export type { GridIntersection as GridIntersection_api } from './models/GridIntersection';
export type { GridSurface as GridSurface_api } from './models/GridSurface';
export type { HTTPValidationError as HTTPValidationError_api } from './models/HTTPValidationError';
Expand Down Expand Up @@ -57,6 +58,7 @@ export type { WellCompletionsZone as WellCompletionsZone_api } from './models/We

export { DefaultService } from './services/DefaultService';
export { ExploreService } from './services/ExploreService';
export { GraphService } from './services/GraphService';
export { GridService } from './services/GridService';
export { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
export { ParametersService } from './services/ParametersService';
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/models/CaseInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
export type CaseInfo = {
uuid: string;
name: string;
status: string;
user: string;
};

8 changes: 8 additions & 0 deletions frontend/src/api/models/GraphUserPhoto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type GraphUserPhoto = {
avatar_b64str: (string | null);
};

35 changes: 35 additions & 0 deletions frontend/src/api/services/GraphService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GraphUserPhoto } from '../models/GraphUserPhoto';

import type { CancelablePromise } from '../core/CancelablePromise';
import type { BaseHttpRequest } from '../core/BaseHttpRequest';

export class GraphService {

constructor(public readonly httpRequest: BaseHttpRequest) {}

/**
* User Info
* Get username, display name and avatar from Microsoft Graph API for a given user id
* @param userId User id
* @returns GraphUserPhoto Successful Response
* @throws ApiError
*/
public userInfo(
userId: string,
): CancelablePromise<GraphUserPhoto> {
return this.httpRequest.request({
method: 'GET',
url: '/graph/user_photo/',
query: {
'user_id': userId,
},
errors: {
422: `Validation Error`,
},
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { UseQueryResult, useQuery } from "@tanstack/react-query";
import React from "react";

import { GraphUserPhoto_api } from "@api";
import { apiService } from "@framework/ApiService";
import { CircularProgress } from "@lib/components/CircularProgress";
import { AccountCircle } from "@mui/icons-material";

export type UserAvatarProps = {
userId: string;
}

const STALE_TIME = 60 * 1000;
const CACHE_TIME = 60 * 1000;

function useUserInfoQuery(
userId: string
): UseQueryResult<GraphUserPhoto_api> {
return useQuery({
queryKey: ["getUserInfo", userId],
queryFn: () => apiService.graph.userInfo(`${userId.toUpperCase()}@equinor.com`),
staleTime: STALE_TIME,
cacheTime: CACHE_TIME,
enabled: userId !== "",
});
}

export const UserAvatar: React.FC<UserAvatarProps> = (props) => {
const userInfo = useUserInfoQuery(props.userId);

if (userInfo.isFetching) {
return <CircularProgress size="medium-small" className="mr-1" />;
}

if (userInfo.data?.avatar_b64str) {
return (
<img
src={`data:image/png;base64,${userInfo.data.avatar_b64str}`}
alt="Avatar"
className="w-5 h-5 rounded-full mr-1"
title={props.userId}
/>
);
}
return <span title={props.userId}><AccountCircle className="w-5 h-5 mr-1" /></span>;
}
rubenthoms marked this conversation as resolved.
Show resolved Hide resolved
Loading