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

UI: Refactor path-help service #28444

Merged
merged 18 commits into from
Sep 25, 2024
Merged
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
126 changes: 97 additions & 29 deletions ui/app/adapters/generated-item-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,112 @@
*/

import ApplicationAdapter from './application';
import { task } from 'ember-concurrency';
import { service } from '@ember/service';
import { sanitizePath } from 'core/utils/sanitize-path';
import { encodePath } from 'vault/utils/path-encoding-helpers';
import { tracked } from '@glimmer/tracking';

export default ApplicationAdapter.extend({
store: service(),
namespace: 'v1',
urlForItem() {},
dynamicApiPath: '',
export default class GeneratedItemListAdapter extends ApplicationAdapter {
@service store;
namespace = 'v1';

getDynamicApiPath: task(function* (id) {
// TODO: remove yield at some point.
const result = yield this.store.peekRecord('auth-method', id);
this.dynamicApiPath = result.apiPath;
return;
}),
// these items are set by calling getNewAdapter in the path-help service.
@tracked apiPath = '';
paths = {};

fetchByQuery: task(function* (store, query, isList) {
// These are the paths used for the adapter actions
get getPath() {
return this.paths.getPath || '';
}
get createPath() {
return this.paths.createPath || '';
}
get deletePath() {
return this.paths.deletePath || '';
}

getDynamicApiPath(id) {
const result = this.store.peekRecord('auth-method', id);
this.apiPath = result.apiPath;
return result.apiPath;
}

async fetchByQuery(store, query, isList) {
const { id } = query;
const data = {};
const payload = {};
if (isList) {
data.list = true;
yield this.getDynamicApiPath.perform(id);
payload.list = true;
}
const path = isList ? this.getDynamicApiPath(id) : '';

return this.ajax(this.urlForItem(id, isList, this.dynamicApiPath), 'GET', { data }).then((resp) => {
const data = {
id,
method: id,
};
return { ...resp, ...data };
});
}),
const resp = await this.ajax(this.urlForItem(id, isList, path), 'GET', { data: payload });
const data = {
id,
method: id,
};
return { ...resp, ...data };
}

query(store, type, query) {
return this.fetchByQuery.perform(store, query, true);
},
return this.fetchByQuery(store, query, true);
}

queryRecord(store, type, query) {
return this.fetchByQuery.perform(store, query);
},
});
return this.fetchByQuery(store, query);
}

urlForItem(id, isList, dynamicApiPath) {
const itemType = sanitizePath(this.getPath);
let url;
id = encodePath(id);
Copy link
Contributor

Choose a reason for hiding this comment

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

For my own understanding, because we're encoding the id here, i would expect it could be something like /test?. Is that correct? ID to me indicates a string that shouldn't need encoding, but then again we manual change ids to be concatenations of strange things.

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 believe this can be anything that an auth item (such as userpass username) can be. In that case, things like spaces or special characters need to be encoded, which is why we do this here.

// the apiPath changes when you switch between routes but the apiPath variable does not unless the model is reloaded
// overwrite apiPath if dynamicApiPath exist.
// dynamicApiPath comes from the model->adapter
let apiPath = this.apiPath;
if (dynamicApiPath) {
apiPath = dynamicApiPath;
}
// isList indicates whether we are viewing the list page
// of a top-level item such as userpass
if (isList) {
url = `${this.buildURL()}/${apiPath}${itemType}/`;
} else {
// build the URL for the show page of a nested item
// such as a userpass group
url = `${this.buildURL()}/${apiPath}${itemType}/${id}`;
}

return url;
}

urlForQueryRecord(id, modelName) {
return this.urlForItem(id, modelName);
}

urlForUpdateRecord(id) {
const itemType = this.createPath.slice(1, this.createPath.indexOf('{') - 1);
return `${this.buildURL()}/${this.apiPath}${itemType}/${id}`;
}

urlForCreateRecord(modelType, snapshot) {
const id = snapshot.record.mutableId; // computed property that returns either id or private settable _id value
const path = this.createPath.slice(1, this.createPath.indexOf('{') - 1);
return `${this.buildURL()}/${this.apiPath}${path}/${id}`;
}

urlForDeleteRecord(id) {
const path = this.deletePath.slice(1, this.deletePath.indexOf('{') - 1);
return `${this.buildURL()}/${this.apiPath}${path}/${id}`;
}

createRecord(store, type, snapshot) {
return super.createRecord(...arguments).then((response) => {
// if the server does not return an id and one has not been set on the model we need to set it manually from the mutableId value
if (!response?.id && !snapshot.record.id) {
snapshot.record.id = snapshot.record.mutableId;
snapshot.id = snapshot.record.id;
}
return response;
});
}
}
44 changes: 44 additions & 0 deletions ui/app/models/generated-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Model from '@ember-data/model';
import { tracked } from '@glimmer/tracking';

// This model is used for OpenApi-generated models in path-help service's getNewModel method
export default class GeneratedItemModel extends Model {
allFields = [];

@tracked _id;
get mutableId() {
return this._id || this.id;
}
set mutableId(value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nice!

this._id = value;
}

get fieldGroups() {
const groups = {
default: [],
};
const fieldGroups = [];
this.constructor.eachAttribute((name, attr) => {
// if the attr comes in with a fieldGroup from OpenAPI,
if (attr.options.fieldGroup) {
if (groups[attr.options.fieldGroup]) {
groups[attr.options.fieldGroup].push(attr);
} else {
groups[attr.options.fieldGroup] = [attr];
}
} else {
// otherwise just add that attr to the default group
groups.default.push(attr);
}
});
for (const group in groups) {
fieldGroups.push({ [group]: groups[group] });
}
return fieldGroups;
}
}
Loading
Loading