Skip to content

Commit

Permalink
Merge 40488bc into ff32d5f
Browse files Browse the repository at this point in the history
  • Loading branch information
romeonicholas authored May 22, 2024
2 parents ff32d5f + 40488bc commit 005e7ab
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 91 deletions.
18 changes: 14 additions & 4 deletions backend/capellacollab/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@

import typing as t

import pydantic

from capellacollab.core import pydantic as core_pydantic

T = t.TypeVar("T")


class Message(core_pydantic.BaseModel):
err_code: str | None = None
title: str | None = None
reason: str | tuple | None = None
technical: str | None = None
err_code: str = pydantic.Field(
description="The error code of the message, used for testing, not displayed in the frontend.",
examples=["T4C_SERVER_UNREACHABLE"],
)
title: str = pydantic.Field(
description="The title of the message, displayed in the frontend",
examples=["TeamForCapella server not reachable"],
)
reason: str = pydantic.Field(
description="The reason for the message and any possible resolutions/next steps, displayed in the frontend",
examples=["We will only show a representation of our database."],
)


class ResponseModel(core_pydantic.BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/capellacollab/sessions/hooks/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def session_connection_hook( # type: ignore[override]
warnings=[
core_models.Message(
err_code="REDIRECT_URL_DERIVATION_FAILED",
title="Couldn't derive the redirect URL.",
title="Couldn't derive the redirect URL",
reason="Please check the backend logs for more information.",
)
]
Expand Down
1 change: 1 addition & 0 deletions backend/capellacollab/sessions/hooks/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def _get_project_share_volume_mounts(
warnings.append(
core_models.Message(
err_code="JUPYTER_FILE_SHARE_VOLUME_NOT_FOUND",
title="Jupyter file-share volume not found",
reason=(
f"The Jupyter file-share volume for the model '{model.name}' in the project '{model.project.name}' couldn't be located. "
"Please contact your system administrator or recreate the model (this will erase all data in the file-share)."
Expand Down
14 changes: 8 additions & 6 deletions backend/capellacollab/sessions/hooks/pure_variants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ def configuration_hook( # type: ignore
warnings = [
core_models.Message(
err_code="PV_MODEL_NOT_FOUND",
title="No pure::variants model found",
reason=(
"You are trying to create a persistent session with a pure::variants integration.",
"We were not able to find a model with a pure::variants integration.",
"Your session will not be connected to the pure::variants license server.",
"You are trying to create a persistent session with a pure::variants integration. "
"We were not able to find a model with a pure::variants integration. "
"Your session will not be connected to the pure::variants license server."
),
)
]
Expand All @@ -61,10 +62,11 @@ def configuration_hook( # type: ignore
warnings = [
core_models.Message(
err_code="PV_LICENSE_SERVER_NOT_CONFIGURED",
title="No pure::variants license server URL configured",
reason=(
"You are trying to create a persistent session with a pure::variants integration.",
"We were not able to find a valid license server URL in our database.",
"Your session will not be connected to the pure::variants license server.",
"You are trying to create a persistent session with a pure::variants integration. "
"We were not able to find a valid license server URL in our database. "
"Your session will not be connected to the pure::variants license server."
),
)
]
Expand Down
10 changes: 6 additions & 4 deletions backend/capellacollab/sessions/hooks/t4c.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ def configuration_hook( # type: ignore
except requests.RequestException:
warnings.append(
core_models.Message(
err_code="T4C_USER_CREATION_FAILED",
title="Could not create user in TeamForCapella repository",
reason=(
f"The creation of your user in the repository '{repository.name}' of the the instance '{repository.instance.name}' failed.",
"Most likely this is due to a downtime of the corresponding TeamForCapella server.",
"If you don't need access to the repository you can still use the session.",
)
f"The creation of your user in the repository '{repository.name}' of the the instance '{repository.instance.name}' failed. "
"Most likely this is due to a downtime of the corresponding TeamForCapella server. "
"If you don't need access to the repository you can still use the session."
),
)
)
log.warning(
Expand Down
2 changes: 1 addition & 1 deletion backend/capellacollab/sessions/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ def resolve_environment_variables(
)
warnings += [
core_models.Message(
title="Couldn't resolve environment variable.",
err_code="ENVIRONMENT_VARIABLE_RESOLUTION_FAILED",
title="Couldn't resolve environment variable",
reason=(
f"Failed to resolve environment variable '{key}'. "
"This might be due to a incorrect configuration. "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,21 @@ def list_t4c_repositories(

try:
server_repositories = interface.list_repositories(instance)
except requests.RequestException as e:
except requests.RequestException:
for repo in repositories:
repo.status = models.T4CRepositoryStatus.INSTANCE_UNREACHABLE
log.error("TeamForCapella server not reachable", exc_info=True)
log.error(
"TeamForCapella server not reachable.",
exc_info=True,
)

return T4CRepositoriesResponseModel(
payload=repositories,
warnings=[
core_models.Message(
title="TeamForCapella server not reachable.",
err_code="T4C_SERVER_UNREACHABLE",
title="TeamForCapella server not reachable",
reason="We will only show a representation of our database.",
technical=f"TeamForCapella not reachable with exception {e}",
)
],
)
Expand Down Expand Up @@ -123,23 +126,21 @@ def delete_t4c_repository(
response.status_code = status.HTTP_422_UNPROCESSABLE_ENTITY

if isinstance(e, fastapi.HTTPException):
reason: tuple[str, ...] = (
"The TeamForCapella returned an error when deleting the repository.",
"We deleted it the repository our database. When the connection is successful, we'll synchronize the repositories again.",
reason: str = (
"The TeamForCapella returned an error when deleting the repository. "
"We deleted it the repository our database. When the connection is successful, we'll synchronize the repositories again."
)
technical = f"TeamForCapella returned status code {e.status_code}"
else:
reason = (
"The TeamForCapella server is not reachable.",
"We deleted the repository from our database.",
"During the next connection attempt, we'll synchronize the repository again.",
"The TeamForCapella server is not reachable. "
"We deleted the repository from our database. "
"During the next connection attempt, we'll synchronize the repository again."
)
technical = f"TeamForCapella not reachable with exception {e}"

return create_single_warning_response_model(
err_code="T4C_REPOSITORY_DELETION_FAILED",
title="Repository deletion failed partially.",
reason=reason,
technical=technical,
)

response.status_code = status.HTTP_204_NO_CONTENT
Expand Down Expand Up @@ -249,15 +250,15 @@ def sync_db_with_server_repositories(


def create_single_warning_response_model(
title: str, reason: str | tuple[str, ...], technical: str
err_code: str, title: str, reason: str
) -> core_models.ResponseModel:
return core_models.ResponseModel(
errors=[],
warnings=[
core_models.Message(
err_code=err_code,
title=title,
reason=reason,
technical=technical,
)
],
)
26 changes: 26 additions & 0 deletions backend/tests/settings/teamforcapella/test_t4c_repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,29 @@ def test_list_t4c_repositories(
assert transformed_response["test3"] == "INITIAL"
assert transformed_response["test4"] == "ONLINE"
assert transformed_response["test5"] == "NOT_FOUND"


@responses.activate
@pytest.mark.usefixtures("admin")
def test_list_t4c_repositories_instance_unreachable_exception(
client: testclient.TestClient,
db: orm.Session,
t4c_instance: t4c_models.DatabaseT4CInstance,
):
responses.get(
"http://localhost:8080/api/v1.0/repositories",
status=500,
)

t4c_repositories_crud.create_t4c_repository(db, "test4", t4c_instance)
response = client.get(
f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/repositories",
)

assert response.status_code == 200
assert len(response.json()["payload"]) == 1

transformed_response = {
repo["name"]: repo["status"] for repo in response.json()["payload"]
}
assert transformed_response["test4"] == "INSTANCE_UNREACHABLE"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Injectable } from '@angular/core';
import { getReasonPhrase } from 'http-status-codes';
import { Observable, tap, map, from, catchError } from 'rxjs';
import { ToastService } from 'src/app/helpers/toast/toast.service';
import { Message } from 'src/app/openapi';

// Skips the automated error handling.
// When this option is set, the error messages from the backend are not auto-printed as toast message
Expand Down Expand Up @@ -50,13 +51,7 @@ export class ErrorHandlingInterceptor implements HttpInterceptor {
const body = event.body;
if (body?.errors) {
for (const error of body.errors) {
if (error.reason && Array.isArray(error.reason)) {
error.reason = error.reason.join(' ');
}
this.toastService.showError(
error.title || '',
error.reason || '',
);
this.toastService.showError(error.title, error.reason);
}
}
const warnings = body?.warnings;
Expand Down Expand Up @@ -90,10 +85,7 @@ export class ErrorHandlingInterceptor implements HttpInterceptor {
}
} else if (detail.reason) {
// User defined error
this.toastService.showError(
'An error occurred!',
ErrorHandlingInterceptor.getErrorReason(detail),
);
this.toastService.showError('An error occurred!', detail.reason);
}
} else if (err.status === 0) {
this.toastService.showError(
Expand Down Expand Up @@ -126,14 +118,6 @@ export class ErrorHandlingInterceptor implements HttpInterceptor {
);
}

static getErrorReason(detail: ErrorDetail): string {
if (Array.isArray(detail.reason)) {
return detail.reason.join(' ');
}

return detail.reason || '';
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructErrorDetailTransformationObservable(err: any): Observable<any> {
// https://github.com/angular/angular/issues/19888
Expand Down Expand Up @@ -172,12 +156,9 @@ export class ErrorHandlingInterceptor implements HttpInterceptor {
throw err;
}

private displayWarningInToaster(warnings: ErrorDetail[]): void {
private displayWarningInToaster(warnings: Message[]): void {
for (const warning of warnings) {
if (warning.reason && Array.isArray(warning.reason)) {
warning.reason = warning.reason.join(' ');
}
this.toastService.showWarning(warning.title || '', warning.reason || '');
this.toastService.showWarning(warning.title, warning.reason);
}
}

Expand All @@ -187,16 +168,9 @@ export class ErrorHandlingInterceptor implements HttpInterceptor {
}
}

export type ErrorDetail = {
err_code?: string;
title?: string;
reason?: string | string[];
technical?: string;
};

export type PayloadWrapper = {
warnings: ErrorDetail[];
errors: ErrorDetail[];
warnings: Message[];
errors: Message[];
payload: unknown;
};

Expand Down
1 change: 0 additions & 1 deletion frontend/src/app/openapi/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ model/pure-variants-licenses-input.ts
model/pure-variants-licenses-output.ts
model/rdp-ports-input.ts
model/rdp-ports-output.ts
model/reason.ts
model/refresh-token-request.ts
model/resources-input.ts
model/resources-output.ts
Expand Down
17 changes: 12 additions & 5 deletions frontend/src/app/openapi/model/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@
+ To generate a new version, run `make openapi` in the root directory of this repository.
*/

import { Reason } from './reason';


export interface Message {
err_code: string | null;
title: string | null;
reason: Reason | null;
technical: string | null;
/**
* The error code of the message, used for testing, not displayed in the frontend.
*/
err_code: string;
/**
* The title of the message, displayed in the frontend
*/
title: string;
/**
* The reason for the message and any possible resolutions/next steps, displayed in the frontend
*/
reason: string;
}

1 change: 0 additions & 1 deletion frontend/src/app/openapi/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export * from './pure-variants-licenses-input';
export * from './pure-variants-licenses-output';
export * from './rdp-ports-input';
export * from './rdp-ports-output';
export * from './reason';
export * from './refresh-token-request';
export * from './resources-input';
export * from './resources-output';
Expand Down
16 changes: 0 additions & 16 deletions frontend/src/app/openapi/model/reason.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { filter, map, switchMap } from 'rxjs';
import { ErrorHandlingInterceptor } from 'src/app/general/error-handling/error-handling.interceptor';
import { ModelComplexityBadgeService } from 'src/app/projects/project-detail/model-overview/model-complexity-badge/service/model-complexity-badge.service';
import { ProjectService } from 'src/app/projects/service/project.service';
import { environment } from 'src/environments/environment';
Expand Down Expand Up @@ -71,9 +70,7 @@ export class ModelComplexityBadgeComponent implements OnChanges {
},
error: (err) => {
this.loadingComplexityBadge = false;
this.errorMessage = ErrorHandlingInterceptor.getErrorReason(
err.error?.detail,
);
this.errorMessage = err.error?.detail.reason;
this.errorCode = err.error?.detail?.err_code;
},
});
Expand Down

0 comments on commit 005e7ab

Please sign in to comment.