Skip to content

Commit

Permalink
Added access to Grafana dashboard from TaskReview app
Browse files Browse the repository at this point in the history
  • Loading branch information
meta-paul committed Jul 17, 2024
1 parent af18164 commit 5a40ee7
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 4 deletions.
18 changes: 17 additions & 1 deletion docs/web/docs/guides/how_to_use/review_app/server_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Here is a typical user journey through TaskReview app:
- UI renders unit's review representation in an iframe
- User can choose to reject/accept unit, grant/revoke qualification, and block the worker
- When all units are reviewed, UI redirects user to the "Tasks" page
- User clicks "Show" button in "Stats" column to see hystograms if a Task has suitable data format for them
- User clicks "Open" button in "Chars" column to see Grafana dashboard to investigate a Task
- User clicks "Download" button for a reviewed Task
- UI pulls Task data from `GET /tasks/<task_id>/<n_units>/export-results.json` endpoint

Expand Down Expand Up @@ -91,7 +93,7 @@ Serve a single composed file with reviewed task results (API response is a file

---

### `GET /api/tasks/{id}/{n_units}/stats-results`
### `GET /api/tasks/{id}/stats-results`

Assemble stats with results for a Task.

Expand All @@ -112,6 +114,20 @@ Assemble stats with results for a Task.

---

### `GET /api/tasks/{id}/charts`

Check if Grafana server is available and redirect or return error.

```
{
"dashboard_url": <str> | null,
"server_is_available": <bool>,
"task_name": <str>,
}
```

---

### `GET /api/tasks/{id}/worker-units-ids`

Get full, unpaginated list of unit IDs within a task (for subsequent client-side grouping by worker_id and `GET /task-units` pagination)
Expand Down
5 changes: 5 additions & 0 deletions mephisto/review_app/client/src/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.bundle.min";
import Errors from "components/Errors/Errors";
import HomePage from "pages/HomePage/HomePage";
import TaskChartsPage from "pages/TaskChartsPage/TaskChartsPage";
import TaskPage from "pages/TaskPage/TaskPage";
import TasksPage from "pages/TasksPage/TasksPage";
import TaskStatsPage from "pages/TaskStatsPage/TaskStatsPage";
Expand All @@ -34,6 +35,10 @@ function App() {
path={urls.client.taskStats(":id")}
element={<TaskStatsPage setErrors={setErrors} />}
/>
<Route
path={urls.client.taskCharts(":id")}
element={<TaskChartsPage setErrors={setErrors} />}
/>
<Route
path={urls.client.tasks}
element={<TasksPage setErrors={setErrors} />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

.task-charts {
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
display: flex;
flex-direction: column;
}

.task-charts .header {
padding: 10px 20px;
background-color: #ecdadf;
}

.task-charts .header .task-name {
font-size: 25px;
}

.task-charts .content {
padding: 10px 20px;
}

.task-charts .loading {
width: 100%;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
128 changes: 128 additions & 0 deletions mephisto/review_app/client/src/pages/TaskChartsPage/TaskChartsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { setPageTitle } from "pages/TaskPage/helpers";
import * as React from "react";
import { useEffect } from "react";
import { Spinner } from "react-bootstrap";
import { useParams } from "react-router-dom";
import { getTaskCharts } from "requests/tasks";
import TasksHeader from "../TasksPage/TasksHeader/TasksHeader";
import "./TaskChartsPage.css";

type ParamsType = {
id: string;
};

interface TaskChartsPagePropsType {
setErrors: Function;
}

function TaskChartsPage(props: TaskChartsPagePropsType) {
const params = useParams<ParamsType>();

const [taskCharts, setTaskCharts] = React.useState<TaskChartsType>(null);
const [loading, setLoading] = React.useState(false);

function onError(errorResponse: ErrorResponseType | null) {
if (errorResponse) {
props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]);
}
}

// Effects
useEffect(() => {
// Set default title
setPageTitle("Mephisto - Task Charts");

if (taskCharts === null) {
getTaskCharts(params.id, setTaskCharts, setLoading, onError, null);
}
}, []);

useEffect(() => {
if (taskCharts) {
if (taskCharts.server_is_available) {
// Redirect to Grafana dashboard
window.location.replace(taskCharts.dashboard_url);
} else {
// Update title with current task name
setPageTitle(`Mephisto - Task Charts - ${taskCharts.task_name}`);
}
}
}, [taskCharts]);

if (taskCharts === null) {
return null;
}

return (
<div className={"task-charts"}>
{/* Header */}
<TasksHeader />

{!loading && taskCharts && (
// Task name
<div className={"header"}>
<div className={"task-name"}>Task: {taskCharts.task_name}</div>
</div>
)}

<div className={"content"}>
{/* Preloader when we request task charts */}
{loading ? (
<div className={"loading"}>
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>
) : (
<>
{!taskCharts.server_is_available && (
<>
<h4>Grafana server is unreachable</h4>

<br />

<div>
To see Grafana dashboard, you need:
<ol>
<li>
Install metrics (should have be done before running this
Task)
</li>
<li>Start metrics server</li>
<li>Refresh this page</li>
</ol>
How to install and start metrics, you can find in{" "}
<a
href={
"https://mephisto.ai/docs/guides/how_to_use/efficiency_organization/metrics_dashboard/"
}
target={"_blank"}
>
documentation
</a>
.
<br />
<br />
<button
className={"btn btn-primary btn-sm"}
onClick={() => window.location.reload()}
>
Refresh page
</button>
</div>
</>
)}
</>
)}
</div>
</div>
);
}

export default TaskChartsPage;
5 changes: 5 additions & 0 deletions mephisto/review_app/client/src/pages/TasksPage/TasksPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
text-align: center;
}

.tasks .tasks-table .titles-row .charts {
width: 80px;
text-align: center;
}

.tasks .tasks-table .task-row:not(.no-hover) {
cursor: pointer;
}
Expand Down
11 changes: 11 additions & 0 deletions mephisto/review_app/client/src/pages/TasksPage/TasksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ function TasksPage(props: PropsType) {
<th className={"title stats"}>
<b>Stats</b>
</th>
<th className={"title charts"}>
<b>Charts</b>
</th>
<th className={"title export"}>
<b>Export results</b>
</th>
Expand Down Expand Up @@ -143,6 +146,14 @@ function TasksPage(props: PropsType) {
</Link>
)}
</td>
<td className={"charts"}>
<Link
to={urls.client.taskCharts(task.id)}
target={"_blank"}
>
Open
</Link>
</td>
<td className={"export"}>
{task.is_reviewed &&
!(
Expand Down
21 changes: 21 additions & 0 deletions mephisto/review_app/client/src/requests/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,24 @@ export function getTaskStats(
abortController
);
}

export function getTaskCharts(
id: string,
setDataAction: SetRequestDataActionType,
setLoadingAction: SetRequestLoadingActionType,
setErrorsAction: SetRequestErrorsActionType,
abortController?: AbortController
) {
const url = generateURL(urls.server.taskCharts, [id], null);

makeRequest(
"GET",
url,
null,
(data) => setDataAction(data),
setLoadingAction,
setErrorsAction,
"getTaskCharts error:",
abortController
);
}
6 changes: 6 additions & 0 deletions mephisto/review_app/client/src/types/tasks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ declare type TaskRusultStatsType = {
task_name: string;
workers_count: number;
};

declare type TaskChartsType = {
dashboard_url: string | null;
server_is_available: boolean;
task_name: string;
};
2 changes: 2 additions & 0 deletions mephisto/review_app/client/src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const urls = {
home: "/",
task: (id) => `/tasks/${id}`,
taskStats: (id) => `/tasks/${id}/stats`,
taskCharts: (id) => `/tasks/${id}/charts`,
tasks: "/tasks",
},
server: {
Expand All @@ -22,6 +23,7 @@ const urls = {
API_URL + `/api/qualifications/${id}/workers/${workerId}/revoke`,
stats: API_URL + "/api/review-stats",
task: (id) => API_URL + `/api/tasks/${id}`,
taskCharts: (id) => API_URL + `/api/tasks/${id}/charts`,
taskExportResults: (id) => API_URL + `/api/tasks/${id}/export-results`,
taskExportResultsJson: (id, nUnits) =>
API_URL + `/api/tasks/${id}/${nUnits}/export-results.json`,
Expand Down
1 change: 1 addition & 0 deletions mephisto/review_app/server/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .qualifications_view import QualificationsView
from .qualify_worker_view import QualifyWorkerView
from .review_stats_view import ReviewStatsView
from .task_charts_view import TaskChartsView
from .task_export_results_json_view import TaskExportResultsJsonView
from .task_export_results_view import TaskExportResultsView
from .task_stats_results_view import TaskStatsResultsView
Expand Down
43 changes: 43 additions & 0 deletions mephisto/review_app/server/api/views/task_charts_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python3

# Copyright (c) Meta Platforms and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import requests
from flask import current_app as app
from flask.views import MethodView

from mephisto.data_model.task import Task
from mephisto.utils import metrics


class TaskChartsView(MethodView):
def get(self, task_id: str = None) -> dict:
"""Check if Grafana server is available and redirect or return error"""

task: Task = Task.get(db=app.db, db_id=task_id)
app.logger.debug(f"Found Task in DB: {task}")

try:
grafana_url = metrics.get_grafana_url()
r = requests.get(grafana_url)
grafana_server_is_ok = r.ok
except requests.exceptions.ConnectionError as e:
grafana_server_is_ok = False

data = {
"dashboard_url": None,
"server_is_available": False,
"task_name": task.task_name,
}

if grafana_server_is_ok:
data.update(
{
"dashboard_url": "http://" + metrics.get_default_dashboard_url(),
"server_is_available": True,
}
)

return data
4 changes: 4 additions & 0 deletions mephisto/review_app/server/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def init_urls(app: Flask):
"/api/tasks/<int:task_id>/stats-results",
view_func=api_views.TaskStatsResultsView.as_view("task_stats_results"),
)
app.add_url_rule(
"/api/tasks/<int:task_id>/charts",
view_func=api_views.TaskChartsView.as_view("task_charts"),
)
app.add_url_rule(
"/api/units",
view_func=api_views.UnitsView.as_view("units"),
Expand Down
6 changes: 3 additions & 3 deletions mephisto/utils/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def _get_pid_from_file(fn) -> int:
return pid


def _get_grafana_url() -> str:
def get_grafana_url() -> str:
return f"http://{GRAFANA_HOST}:{GRAFANA_PORT}/"


Expand Down Expand Up @@ -221,7 +221,7 @@ def launch_grafana_server() -> bool:
"""
if os.path.exists(GRAFANA_PID_FILE):
try:
grafana_url = _get_grafana_url()
grafana_url = get_grafana_url()
r = requests.get(grafana_url)
is_ok = r.ok
except requests.exceptions.ConnectionError:
Expand Down Expand Up @@ -271,7 +271,7 @@ def get_default_dashboard_url() -> str:
Return the url to the default Mephisto dashboard. Requires a running grafana server
"""
headers_dict = {"Accept": "application/json"}
grafana_url = _get_grafana_url()
grafana_url = get_grafana_url()
url = urljoin(grafana_url, "/api/search?query=Default%20Mephisto%20Monitorin")

r = requests.get(
Expand Down

0 comments on commit 5a40ee7

Please sign in to comment.