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

Api #3367

Merged
merged 6 commits into from
Apr 4, 2023
Merged

Api #3367

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dashboard/src/actions/datasetListActions.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as TYPES from "./types";

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

Choose a reason for hiding this comment

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

Nit: overviewActions.js and tableOfContentActions.js import from ../utils/helper (although findNoOfDays is imported from utils/dateFunctions in overviewActions.js). Assuming that they are equivalent, maybe we should go with a single idiom in all cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a good point, and I'm not quite sure how this ended up that way because I'm usually not inclined to depend on the default root. (And it confuses vscode, too.)


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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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