Skip to content

Commit

Permalink
updated ui to display errors
Browse files Browse the repository at this point in the history
  • Loading branch information
mathis-marcotte committed Oct 9, 2024
1 parent d9da853 commit f5a8ab7
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 14 deletions.
32 changes: 30 additions & 2 deletions components/centraldashboard/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export const ERRORS = {
invalid_create_requesting_shares: 'Failed to create requesting shares configmap',
invalid_update_requesting_shares: 'Failed to update requesting shares configmap',
invalid_update_existing_shares: 'Failed to update existing shares configmap',
invalid_delete_existing_shares: 'Failed to delete existing shares configmap'
invalid_delete_existing_shares: 'Failed to delete existing shares configmap' ,
invalid_get_shares_errors: 'Failed to load shares errors',
invalid_post_shares_errors: 'Failed to update shares errors',
};

export function apiError(a: {res: Response, error: string, code?: number}) {
Expand Down Expand Up @@ -188,7 +190,33 @@ export class Api {
error: ERRORS.invalid_delete_existing_shares,
});
}
});
})
.get(
'/get-shares-errors/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.getSharesErrorsConfigMap(req.params.namespace);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_get_shares_errors,
});
}
})
.post(
'/update-shares-errors/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.updateSharesErrorsConfigMap(req.params.namespace, req.body);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_post_shares_errors,
});
}
});
}

resolveLanguage(requested: string[], supported: string[], defaultLang: string) {
Expand Down
45 changes: 44 additions & 1 deletion components/centraldashboard/app/k8s_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const APP_API_VERSION = 'v1beta1';
const APP_API_NAME = 'applications';
const REQUESTING_SHARES_CM_NAME = 'requesting-shares';
const EXISTING_SHARES_CM_NAME = 'existing-shares';
const SHARES_ERRORS_CM_NAME = 'shares-errors';

/** Wrap Kubernetes API calls in a simpler interface for use in routes. */
export class KubernetesService {
Expand Down Expand Up @@ -125,7 +126,7 @@ export class KubernetesService {
return body;
} catch (err) {
if(err.statusCode === 404){
//user has no user-filers yet
//user has no existing-shares yet
return new k8s.V1ConfigMap();
}
console.error('Unable to fetch ConfigMap:', err.response?.body || err.body || err);
Expand All @@ -148,6 +149,21 @@ export class KubernetesService {
}
}

/** Retrieves the shares errors configmap data for the central dashboard. */
async getSharesErrorsConfigMap(namespace: string): Promise<k8s.V1ConfigMap> {
try {
const { body } = await this.coreAPI.readNamespacedConfigMap(SHARES_ERRORS_CM_NAME, namespace);
return body;
} catch (err) {
if(err.statusCode === 404){
//user has no shares-errors yet
return new k8s.V1ConfigMap();
}
console.error('Unable to fetch ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Updates the requesting shares configmap for the central dashboard. Creates the configmap if it is not created.*/
async updateRequestingSharesConfigMap(namespace: string, data: {[key:string]:string}, email: string): Promise<k8s.V1ConfigMap> {
const config = {
Expand Down Expand Up @@ -209,6 +225,33 @@ export class KubernetesService {
}
}

/** Updates the requesting shares configmap for the central dashboard. Creates the configmap if it is not created.*/
async updateSharesErrorsConfigMap(namespace: string, data: Object[]): Promise<k8s.V1ConfigMap> {
try {
//delete the configmap if no values would be added
if(data.length===0){
const { body } = await this.coreAPI.deleteNamespacedConfigMap(SHARES_ERRORS_CM_NAME, namespace);
return body;
}

const config = {
metadata: {
name: SHARES_ERRORS_CM_NAME
},
data: {
errors: JSON.stringify(data)
}
} as k8s.V1ConfigMap;

//if no error, it means the configmap exists already
const { body } = await this.coreAPI.replaceNamespacedConfigMap(SHARES_ERRORS_CM_NAME, namespace, config);
return body;
} catch (err) {
console.error('Unable to update ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Retrieves the list of events for the given Namespace from the Cluster. */
async getEventsForNamespace(namespace: string): Promise<k8s.V1Event[]> {
try {
Expand Down
10 changes: 6 additions & 4 deletions components/centraldashboard/public/assets/i18n/languages.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@
"manageFilersView.successUpdate": "Filers updated!",
"manageFilersView.missingFiler": "Please select a filer",
"manageFilersView.invalidSharePath": "Please input a valid share path",
"manageFilersView.duplicateFiler": "Filer has already been selected",
"manageFilersView.missingDeleteError": "User does not have filer {filerShare}",
"manageFilersView.duplicateFiler": "Filer share has already been selected",
"manageFilersView.missingDeleteError": "User does not have filer share {filerShare}",
"manageFilersView.removeErrorError": "Failed to remove error",
"s3Proxy.defaultMessage": "This workspace does not include S3 proxy. To start using S3 proxy, please opt in to the service."
},
"fr": {
Expand Down Expand Up @@ -291,8 +292,9 @@
"manageFilersView.successUpdate": "Classeurs mis à jour!",
"manageFilersView.missingFiler": "Veuillez sélectionner un classeur",
"manageFilersView.invalidSharePath": "Veuillez saisir un chemin de partage valide",
"manageFilersView.duplicateFiler": "Le classeur a déjà été sélectionné",
"manageFilersView.missingDeleteError": "L'utilisateur n'a pas le classeur {filerShare}",
"manageFilersView.duplicateFiler": "Le chemin de partage a déjà été sélectionné",
"manageFilersView.missingDeleteError": "L'utilisateur n'a pas le chemin de partage {filerShare}",
"manageFilersView.removeErrorError": "Échec de la suppression de l'erreur",
"s3Proxy.defaultMessage": "Cet espace de travail n'inclue pas S3 proxy. Pour commencer à utiliser S3 proxy, veuilliez choisir de participer au service."
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ h2 .icon {
.filersTable{
margin-bottom: 1.5em;
border-collapse: collapse;
border: 1px solid gray;
}

table, th, td {
.filersTable th, .filersTable td {
border: 1px solid gray;
}

Expand Down Expand Up @@ -101,3 +102,8 @@ label {
.formInputDiv span select{
width: 97%;
}

.errorDiv{
background-color: #ff9b9b;
color: #212324;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import '@polymer/iron-icon/iron-icon.js';
import '@polymer/paper-toast/paper-toast.js';
import '@polymer/paper-ripple/paper-ripple.js';
import '@polymer/paper-item/paper-icon-item.js';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/paper-spinner/paper-spinner-lite.js';

// eslint-disable-next-line max-len
Expand Down Expand Up @@ -96,6 +97,12 @@ export class ManageFilersView extends mixinBehaviors([AppLocalizeBehavior], util
return result;
}

formatErrors(errors) {
// eslint-disable-next-line
console.log(JSON.parse(errors.errors));
return JSON.parse(errors.errors);
}

onChangeFilers(e) {
this.validateError = '';
}
Expand Down Expand Up @@ -188,6 +195,7 @@ export class ManageFilersView extends mixinBehaviors([AppLocalizeBehavior], util
}

// delete the configmap if only contains this filershare
// TODO Move this logic to the api service layer
if (existingDataValue.length===1 &&
Object.keys(existingData).length===1) {
const api = this.$.DeleteExistingSharesAjax;
Expand All @@ -212,6 +220,42 @@ export class ManageFilersView extends mixinBehaviors([AppLocalizeBehavior], util
return;
}

deleteShareError(e) {
const deleteIndex = e.model.itemsIndex;
// TODO: maybe move this logic to the service layer.
// We should just send the value that needs to be
// deleted to the service
// The service can then get the CM on the
// spot and remove the right value
// this would be to avoid unexpected changes in the data
// since the data could be changed without warning by the controller
// and the UI page would not be aware of this change.
// basically, make sure the source of truth is from the cluster
// and not the UI (if we just send the index, that index might be
// different on the cluster when this api call resolves)
const sharesErrorsData = this.sharesErrors === null ? [] :
JSON.parse(this.sharesErrors.errors);

if (!sharesErrorsData[deleteIndex]) {
this.showError(
this.localize(
'manageFilersView.removeErrorError'
)
);
return;
}

// cloning to avoid assigning by reference
const newSharesErrorsData = _.clone(sharesErrorsData);

newSharesErrorsData.splice(deleteIndex, 1);

const api = this.$.UpdateSharesErrorsAjax;
api.body = newSharesErrorsData;
api.generateRequest();
return;
}

/**
* Takes an event from iron-ajax and isolates the error from a request that
* failed
Expand All @@ -228,8 +272,11 @@ export class ManageFilersView extends mixinBehaviors([AppLocalizeBehavior], util
* @param {IronAjaxEvent} e
*/
handleUpdateShares(e) {
this.$.filersSelect.value = '';
this.$.sharesInput.value = '';
// eslint-disable-next-line
if(e.originalTarget.id==="UpdateRequestingSharesAjax"){
this.$.filersSelect.value = '';
this.$.sharesInput.value = '';
}

if (e.detail.error) {
const error = this._isolateErrorFromIronRequest(e);
Expand All @@ -242,8 +289,7 @@ export class ManageFilersView extends mixinBehaviors([AppLocalizeBehavior], util
// updates the data
this.$.GetRequestingSharesAjax.generateRequest();
this.$.GetExistingSharesAjax.generateRequest();
// eslint-disable-next-line
console.log('here', this.requestingShares);
this.$.GetSharesErrorsAjax.generateRequest();
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ iron-ajax#UpdateExistingSharesAjax(method='PATCH', url='/api/update-existing-sha
on-response='handleUpdateShares', on-error='handleUpdateShares', handle-as='json', content-type='application/json')
iron-ajax#DeleteExistingSharesAjax(method='DELETE', url='/api/delete-existing-shares/[[namespace]]',
on-response='handleUpdateShares', on-error='handleUpdateShares')
iron-ajax#GetSharesErrorsAjax(auto, url='/api/get-shares-errors/[[namespace]]', handle-as='json',
last-response='{{sharesErrors}}', on-error='onHasFilersError', loading='{{sharesErrorsLoading}}')
iron-ajax#UpdateSharesErrorsAjax(method='POST', url='/api/update-shares-errors/[[namespace]]',
on-response='handleUpdateShares', on-error='handleUpdateShares', handle-as='json', content-type='application/json')

h1 {{localize('manageFilersView.lblManageFilers')}}
section#Main-Content
Expand Down Expand Up @@ -39,8 +43,7 @@ section#Main-Content
tr
template(is='dom-if', if="[[renderHeader(j)]]")
th(rowspan$='[[getRowspan(svm.shares)]]') {{svm.svm}}
td {{share}}

td {{share}}
div
h2 {{localize('manageFilersView.headerAddFiler')}}
form#filersForm
Expand All @@ -59,6 +62,15 @@ section#Main-Content

template(is='dom-if', if="[[filers]]")
button#shareSubmit(type="button", on-click="updateShares") {{localize('manageFilersView.btnSubmit')}}
template(is='dom-if', if="[[sharesErrors]]")
div.errorDiv
b Latest Errors
table
template(is='dom-repeat', items='[[formatErrors(sharesErrors)]]')
tr
td [{{item.Timestamp}}] - {{item.Svm}}/{{item.Share}}: {{item.ErrorMessage}}
td
paper-icon-button(icon='icons:clear', on-click="deleteShareError")

paper-toast#FilersErrorToast(duration=8000, horizontal-align="center")
strong [[errorText]]
Expand Down

0 comments on commit f5a8ab7

Please sign in to comment.