Skip to content

Commit

Permalink
Api (#3367)
Browse files Browse the repository at this point in the history
* Remove simplistic "API" list from endpoints

PBENCH-1119

With the REST-ification of the API, only a few of the APIs can still be called
using the simplistic `{name: uri}` list, because URI parameters are needed and
are no longer at the end of the URI: for example,
`/api/v1/datasets/{dataset}/metadata`.

The UI now has the `uriTemplate` method to help expand the `"uri"` template
objects. Complete the transition by updating the remaining UI references and
dropping the `api` object entirely.
  • Loading branch information
dbutenhof committed Apr 4, 2023
1 parent 78dd636 commit c10bb8a
Show file tree
Hide file tree
Showing 9 changed files with 32 additions and 77 deletions.
4 changes: 3 additions & 1 deletion dashboard/src/actions/datasetListActions.js
@@ -1,13 +1,15 @@
import * as TYPES from "./types";

import API from "../utils/axiosInstance";
import { uriTemplate } from "utils/helper";

export const fetchPublicDatasets = () => async (dispatch, getState) => {
try {
dispatch({ type: TYPES.LOADING });
const endpoints = getState().apiEndpoint.endpoints;
const response = await API.get(
`${endpoints?.api?.datasets_list}?metadata=dataset.uploaded&access=public`
uriTemplate(endpoints, "datasets_list", {}),
{ params: { metadata: "dataset.uploaded", access: "public" } }
);
if (response.status === 200 && response.data) {
dispatch({
Expand Down
18 changes: 12 additions & 6 deletions dashboard/src/actions/overviewActions.js
Expand Up @@ -4,7 +4,7 @@ import * as TYPES from "./types";
import { DANGER, ERROR_MSG } from "assets/constants/toastConstants";

import API from "../utils/axiosInstance";
import { expandUriTemplate } from "../utils/helper";
import { uriTemplate } from "../utils/helper";
import { findNoOfDays } from "utils/dateFunctions";
import { showToast } from "./toastActions";
import { clearCachedSession } from "./authActions";
Expand All @@ -27,8 +27,8 @@ export const getDatasets = () => async (dispatch, getState) => {
params.append("mine", "true");

const endpoints = getState().apiEndpoint.endpoints;
const response = await API.get(endpoints?.api?.datasets_list, {
params: params,
const response = await API.get(uriTemplate(endpoints, "datasets_list"), {
params,
});

if (response.status === 200) {
Expand Down Expand Up @@ -127,7 +127,7 @@ export const updateDataset =
const method = metaDataActions[actionType];

const endpoints = getState().apiEndpoint.endpoints;
const uri = expandUriTemplate(endpoints, "datasets_metadata", {
const uri = uriTemplate(endpoints, "datasets_metadata", {
dataset: dataset.resource_id,
});
const response = await API.put(uri, {
Expand Down Expand Up @@ -176,7 +176,9 @@ export const deleteDataset = (dataset) => async (dispatch, getState) => {
dispatch({ type: TYPES.LOADING });
const endpoints = getState().apiEndpoint.endpoints;
const response = await API.delete(
`${endpoints.api.datasets}/${dataset.resource_id}`
uriTemplate(endpoints, "datasets", {
dataset: dataset.resource_id,
})
);
if (response.status === 200) {
const datasets = getState().overview.datasets;
Expand Down Expand Up @@ -257,7 +259,11 @@ export const publishDataset =
const savedRuns = getState().overview.savedRuns;

const response = await API.post(
`${endpoints.api.datasets}/${dataset.resource_id}?access=${updateValue}`
uriTemplate(endpoints, "datasets", {
dataset: dataset.resource_id,
}),
null,
{ params: { access: updateValue } }
);
if (response.status === 200) {
const dataIndex = savedRuns.findIndex(
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/actions/tableOfContentActions.js
@@ -1,12 +1,12 @@
import * as TYPES from "./types";
import API from "../utils/axiosInstance";
import { expandUriTemplate } from "../utils/helper";
import { uriTemplate } from "../utils/helper";

export const fetchTOC =
(param, parent, callForSubData) => async (dispatch, getState) => {
try {
const endpoints = getState().apiEndpoint.endpoints;
const uri = expandUriTemplate(endpoints, "datasets_contents", {
const uri = uriTemplate(endpoints, "datasets_contents", {
dataset: param,
target: parent,
});
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/utils/helper.js
Expand Up @@ -10,10 +10,10 @@ export const uid = () => {
*
* @param {Object} endpoints - endpoint object from server
* @param {string} name - name of the API to expand
* @param {Object} args - value for each templated parameter
* @param {Object} args - [Optional] value for each templated parameter
* @return {string} - formatted URI
*/
export const expandUriTemplate = (endpoints, name, args) => {
export const uriTemplate = (endpoints, name, args = {}) => {
let uri = endpoints.uri[name].template;
for (const [key, value] of Object.entries(args)) {
uri = uri.replace(`{${key}}`, value);
Expand Down
16 changes: 7 additions & 9 deletions lib/pbench/client/__init__.py
Expand Up @@ -120,15 +120,13 @@ def _uri(self, api: API, uri_params: Optional[JSONOBJECT] = None) -> str:
Returns:
A fully specified URI
"""
if not uri_params:
return self.endpoints["api"][api.value]
else:
description = self.endpoints["uri"][api.value]
template = description["template"]
cnt = len(description["params"])
if cnt != len(uri_params):
raise IncorrectParameterCount(api, cnt, uri_params)
return template.format(**uri_params)
description = self.endpoints["uri"][api.value]
template = description["template"]
cnt = len(description["params"])
params = uri_params if uri_params else {}
if cnt != len(params):
raise IncorrectParameterCount(api, cnt, params)
return template.format(**params)

def get(
self,
Expand Down
40 changes: 6 additions & 34 deletions lib/pbench/server/api/resources/endpoint_configure.py
Expand Up @@ -51,46 +51,22 @@ def get(self):
including the Pbench dashboard UI. This includes:
openid-connect: A JSON object containing the OpenID Connect parameters
required for the web client to use OIDC authentication.
required for the web client to use OIDC authentication.
identification: The Pbench server name and version
api: A dict of the server APIs supported; we give a name, which
identifies the service, and the full URI relative to the
configured host name and port (local or remote reverse proxy).
This is dynamically generated by processing the Flask URI
rules; refer to api/__init__.py for the code which creates
those mappings, or test_endpoint_configure.py for code that
validates the current set (and must be updated when the API
set changes).
uri: A dict of server API templates, where each template defines a
template URI and a list of typed parameters.
We derive a "name" for each API by removing URI parameters and the API
prefix (/api/v1/), then replacing the path "/" characters with
underscores.
The "api" object contains a key for each API name, where the value is a
simplified URI omitting URI parameters. The client must either know the
required parameters and order, and connect them to the "api" value
separated by slash characters, or refer to the "uri" templates.
E.g, "/api/v1/controllers/list" yields:
"controllers_list": "http://host/api/v1/controllers/list"
while "/api/v1/users/<string:username>" yields:
"users": "http://host/api/v1/users"
For URIs with multiple parameters, or embedded parameters, it may be
easier to work with the template string in the "uri" object. The value
of each API name key in the "uri" object is a minimal "schema" object
defining the template string and parameters for the API. The "uri"
value for the "users" API, for example, will be
The "uri" object defines a template for each API name, defining a set of
URI parameters that must be expanded in the template. For example, the
API to get or modify metadata is:
{
"template": "http://host/api/v1/users/{target_username}",
"params": {"target_username": {"type": "string"}}
"template": "http://host/api/v1/datasets/{dataset}/metadata",
"params": {"dataset": {"type": "string"}}
}
The template can be resolved in Python with:
Expand Down Expand Up @@ -137,7 +113,6 @@ def get(self):
host_value,
)

apis = {}
templates = {}

# Iterate through the Flask endpoints to add a description for each.
Expand All @@ -156,7 +131,6 @@ def get(self):
for match in matches
},
}
url = self.param_template.sub("", url)
path = rule.endpoint

# We have some URI endpoints that repeat a basic URI pattern.
Expand All @@ -168,12 +142,10 @@ def get(self):
if path not in templates or (
len(template["params"]) > len(templates[path]["params"])
):
apis[path] = urljoin(host, url)
templates[path] = template

endpoints = {
"identification": f"Pbench server {self.server_config.COMMIT_ID}",
"api": apis,
"uri": templates,
}

Expand Down
4 changes: 0 additions & 4 deletions lib/pbench/test/functional/server/test_connect.py
Expand Up @@ -12,19 +12,15 @@ def test_connect(self, server_client: PbenchServerClient):
assert server_client.session.headers["Accept"] == "application/json"
endpoints = server_client.endpoints
assert endpoints
assert "api" in endpoints
assert "identification" in endpoints
assert "uri" in endpoints

# Verify that all expected endpoints are reported
for a in endpoints["api"].keys():
assert a in expected
for a in endpoints["uri"].keys():
assert a in expected

# Verify that no unexpected endpoints are reported
for e in expected:
assert e in endpoints["api"].keys()
assert e in endpoints["uri"].keys()

# verify all the required openid-connect fields are present
Expand Down
2 changes: 0 additions & 2 deletions lib/pbench/test/unit/client/test_connect.py
Expand Up @@ -30,7 +30,6 @@ def test_connect(self):
url,
json={
"identification": "string",
"api": {},
"uri": {},
"openid": openid_dict,
},
Expand All @@ -54,7 +53,6 @@ def test_connect(self):
# Check that the fake endpoints we returned are captured
endpoints = pbench.endpoints
assert endpoints
assert endpoints["api"] == {}
assert endpoints["identification"] == "string"
assert endpoints["uri"] == {}
assert endpoints["openid"] == openid_dict
17 changes: 0 additions & 17 deletions lib/pbench/test/unit/server/test_endpoint_configure.py
Expand Up @@ -41,23 +41,6 @@ def check_config(self, client, server_config, host, my_headers={}):
uri = urljoin(host, uri_prefix)
expected_results = {
"identification": f"Pbench server {server_config.COMMIT_ID}",
"api": {
"datasets": f"{uri}/datasets",
"datasets_contents": f"{uri}/datasets/contents",
"datasets_daterange": f"{uri}/datasets/daterange",
"datasets_detail": f"{uri}/datasets/detail",
"datasets_inventory": f"{uri}/datasets/inventory",
"datasets_list": f"{uri}/datasets",
"datasets_mappings": f"{uri}/datasets/mappings",
"datasets_metadata": f"{uri}/datasets/metadata",
"datasets_namespace": f"{uri}/datasets/namespace",
"datasets_search": f"{uri}/datasets/search",
"datasets_values": f"{uri}/datasets/values",
"endpoints": f"{uri}/endpoints",
"server_audit": f"{uri}/server/audit",
"server_settings": f"{uri}/server/settings",
"upload": f"{uri}/upload",
},
"uri": {
"datasets": {
"template": f"{uri}/datasets/{{dataset}}",
Expand Down

0 comments on commit c10bb8a

Please sign in to comment.