From 5663123eb9d2cac73d1925791a5649a850a413b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Couzini=C3=A9?= <couzinie@cines.fr> Date: Sun, 15 Nov 2020 15:00:15 +0100 Subject: [PATCH] Added form to describe repository's openapi --- package-lock.json | 5 + package.json | 1 + src/app/accessapi/accessapi.component.html | 15 +- src/app/app.module.ts | 25 +- src/app/publishapi/class/http-enum.ts | 394 ++++++++++++++++++ src/app/publishapi/class/openapi-dto.ts | 60 +++ src/app/publishapi/class/openapi-enum.ts | 13 + src/app/publishapi/class/openapi.ts | 61 +++ src/app/publishapi/publishapi.component.html | 223 +++++++++- src/app/publishapi/publishapi.component.scss | 63 ++- src/app/publishapi/publishapi.component.ts | 82 +++- .../services/openapi-dto-mapping-service.ts | 90 ++++ .../publishapi/services/openapi-service.ts | 349 ++++++++++++++++ src/app/repository/repository.component.html | 17 +- src/styles.scss | 3 +- 15 files changed, 1355 insertions(+), 46 deletions(-) create mode 100644 src/app/publishapi/class/http-enum.ts create mode 100644 src/app/publishapi/class/openapi-dto.ts create mode 100644 src/app/publishapi/class/openapi-enum.ts create mode 100644 src/app/publishapi/class/openapi.ts create mode 100644 src/app/publishapi/services/openapi-dto-mapping-service.ts create mode 100644 src/app/publishapi/services/openapi-service.ts diff --git a/package-lock.json b/package-lock.json index 239cbbc2d..69adfc8fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3157,6 +3157,11 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/lodash": { + "version": "4.14.165", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz", + "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", diff --git a/package.json b/package.json index 9c7fbc243..af389bb3d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@angular/compiler": "~9.1.11", "@angular/core": "~9.1.11", "@angular/forms": "~9.1.11", + "@types/lodash": "^4.14.165", "@angular/material": "^9.2.4", "@angular/platform-browser": "~9.1.11", "@angular/platform-browser-dynamic": "~9.1.11", diff --git a/src/app/accessapi/accessapi.component.html b/src/app/accessapi/accessapi.component.html index 6da8eb4e6..9ce046658 100644 --- a/src/app/accessapi/accessapi.component.html +++ b/src/app/accessapi/accessapi.component.html @@ -6,14 +6,12 @@ <label>Or describe your repository and save a new file for publishing in the FDP database:</label><br> <div class="form-group"> - <label>title - <input type="text" class="form-control" formControlName="title" required> - </label> + <label>title</label> + <input type="text" class="form-control" formControlName="title" required> </div> <div class="form-group"> - <label>Description - <textarea class="form-control" formControlName="description"></textarea> - </label> + <label>Description</label> + <textarea class="form-control" formControlName="description"></textarea> </div> <div class="form-group"> <label>version</label> @@ -24,9 +22,8 @@ <input type="text" class="form-control" formControlName="url"> </div> <div class="form-group"> - <label>Description - <textarea class="form-control" formControlName="urldescription"></textarea> - </label> + <label>Description</label> + <textarea class="form-control" formControlName="urldescription"></textarea> </div> <div class="form-group"> <label>paths</label> diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a88ee54d8..2f073af7f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -32,9 +32,19 @@ import { ElasticsearchComponent } from './elasticsearch/elasticsearch.component' import { PublishApiComponent } from './publishapi/publishapi.component'; import { SearchComponent } from './search/search.component' import { ParseXmlService } from './services/parse-xml.service'; +<<<<<<< HEAD import { AuthenticationComponent } from './authentication/authentication.component'; import { AuthenticationModule } from './authentication/authentication.module'; +======= +import { NbMenuModule, + NbThemeModule,NbStepperModule, + NbCardModule, NbSidebarModule, NbLayoutModule, + NbButtonModule, NbIconModule, NbInputModule, NbContextMenuModule, NbUserModule, NbSpinnerModule, NbSelectModule, NbTabsetModule, NbTooltipModule } from '@nebular/theme/'; +import { NbEvaIconsModule } from '@nebular/eva-icons'; +import { AccountComponent } from './account/account.component'; +import { AccountModule } from './account/account.module'; +>>>>>>> Added form to describe repository's openapi import { SearchModule} from './search/search.module'; import { StatsComponent } from './stats/stats.component'; import { NebularModule } from './nebular.module'; @@ -79,20 +89,27 @@ import { DashboardComponent } from './dashboard/dashboard.component'; HttpClientModule, FileSaverModule, SearchModule, +<<<<<<< HEAD AppRoutingModule, AuthenticationModule, NebularModule, BrowserAnimationsModule, NbThemeModule.forRoot({ name: 'default' }), NbLayoutModule +======= + NbSpinnerModule, + NbSelectModule, + NbTabsetModule, + NbTooltipModule +>>>>>>> Added form to describe repository's openapi ], providers: [ AppConfiguration, ParseXmlService, - { - provide: APP_INITIALIZER, - useFactory: AppConfigurationFactory, + { + provide: APP_INITIALIZER, + useFactory: AppConfigurationFactory, deps: [AppConfiguration, HttpClient], multi: true }, ], bootstrap: [AppComponent] @@ -101,4 +118,4 @@ export class AppModule { } export function AppConfigurationFactory(appConfig: AppConfiguration) { return () => appConfig.ensureInit(); - } \ No newline at end of file + } diff --git a/src/app/publishapi/class/http-enum.ts b/src/app/publishapi/class/http-enum.ts new file mode 100644 index 000000000..1fcdd1423 --- /dev/null +++ b/src/app/publishapi/class/http-enum.ts @@ -0,0 +1,394 @@ +"use strict"; + +export enum HttpMethod { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + DELETE = 'DELETE', + HEAD = 'HEAD', + OPTIONS = 'OPTIONS' +} + + +/** + * Hypertext Transfer Protocol (HTTP) response status codes. + * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} + */ +enum HttpStatusCode { + + /** + * The server has received the request headers and the client should proceed to send the request body + * (in the case of a request for which a body needs to be sent; for example, a POST request). + * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient. + * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request + * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued. + */ + CONTINUE = 100, + + /** + * The requester has asked the server to switch protocols and the server has agreed to do so. + */ + SWITCHING_PROTOCOLS = 101, + + /** + * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. + * This code indicates that the server has received and is processing the request, but no response is available yet. + * This prevents the client from timing out and assuming the request was lost. + */ + PROCESSING = 102, + + /** + * Standard response for successful HTTP requests. + * The actual response will depend on the request method used. + * In a GET request, the response will contain an entity corresponding to the requested resource. + * In a POST request, the response will contain an entity describing or containing the result of the action. + */ + OK = 200, + + /** + * The request has been fulfilled, resulting in the creation of a new resource. + */ + CREATED = 201, + + /** + * The request has been accepted for processing, but the processing has not been completed. + * The request might or might not be eventually acted upon, and may be disallowed when processing occurs. + */ + ACCEPTED = 202, + + /** + * SINCE HTTP/1.1 + * The server is a transforming proxy that received a 200 OK from its origin, + * but is returning a modified version of the origin's response. + */ + NON_AUTHORITATIVE_INFORMATION = 203, + + /** + * The server successfully processed the request and is not returning any content. + */ + NO_CONTENT = 204, + + /** + * The server successfully processed the request, but is not returning any content. + * Unlike a 204 response, this response requires that the requester reset the document view. + */ + RESET_CONTENT = 205, + + /** + * The server is delivering only part of the resource (byte serving) due to a range header sent by the client. + * The range header is used by HTTP clients to enable resuming of interrupted downloads, + * or split a download into multiple simultaneous streams. + */ + PARTIAL_CONTENT = 206, + + /** + * The message body that follows is an XML message and can contain a number of separate response codes, + * depending on how many sub-requests were made. + */ + MULTI_STATUS = 207, + + /** + * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, + * and are not being included again. + */ + ALREADY_REPORTED = 208, + + /** + * The server has fulfilled a request for the resource, + * and the response is a representation of the result of one or more instance-manipulations applied to the current instance. + */ + IM_USED = 226, + + /** + * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation). + * For example, this code could be used to present multiple video format options, + * to list files with different filename extensions, or to suggest word-sense disambiguation. + */ + MULTIPLE_CHOICES = 300, + + /** + * This and all future requests should be directed to the given URI. + */ + MOVED_PERMANENTLY = 301, + + /** + * This is an example of industry practice contradicting the standard. + * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect + * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 + * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 + * to distinguish between the two behaviours. However, some Web applications and frameworks + * use the 302 status code as if it were the 303. + */ + FOUND = 302, + + /** + * SINCE HTTP/1.1 + * The response to the request can be found under another URI using a GET method. + * When received in response to a POST (or PUT/DELETE), the client should presume that + * the server has received the data and should issue a redirect with a separate GET message. + */ + SEE_OTHER = 303, + + /** + * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match. + * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy. + */ + NOT_MODIFIED = 304, + + /** + * SINCE HTTP/1.1 + * The requested resource is available only through a proxy, the address for which is provided in the response. + * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons. + */ + USE_PROXY = 305, + + /** + * No longer used. Originally meant "Subsequent requests should use the specified proxy." + */ + SWITCH_PROXY = 306, + + /** + * SINCE HTTP/1.1 + * In this case, the request should be repeated with another URI; however, future requests should still use the original URI. + * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. + * For example, a POST request should be repeated using another POST request. + */ + TEMPORARY_REDIRECT = 307, + + /** + * The request and all future requests should be repeated using another URI. + * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change. + * So, for example, submitting a form to a permanently redirected resource may continue smoothly. + */ + PERMANENT_REDIRECT = 308, + + /** + * The server cannot or will not process the request due to an apparent client error + * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing). + */ + BAD_REQUEST = 400, + + /** + * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet + * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the + * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means + * "unauthenticated",i.e. the user does not have the necessary credentials. + */ + UNAUTHORIZED = 401, + + /** + * Reserved for future use. The original intention was that this code might be used as part of some form of digital + * cash or micro payment scheme, but that has not happened, and this code is not usually used. + * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests. + */ + PAYMENT_REQUIRED = 402, + + /** + * The request was valid, but the server is refusing action. + * The user might not have the necessary permissions for a resource. + */ + FORBIDDEN = 403, + + /** + * The requested resource could not be found but may be available in the future. + * Subsequent requests by the client are permissible. + */ + NOT_FOUND = 404, + + /** + * A request method is not supported for the requested resource; + * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource. + */ + METHOD_NOT_ALLOWED = 405, + + /** + * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. + */ + NOT_ACCEPTABLE = 406, + + /** + * The client must first authenticate itself with the proxy. + */ + PROXY_AUTHENTICATION_REQUIRED = 407, + + /** + * The server timed out waiting for the request. + * According to HTTP specifications: + * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time." + */ + REQUEST_TIMEOUT = 408, + + /** + * Indicates that the request could not be processed because of conflict in the request, + * such as an edit conflict between multiple simultaneous updates. + */ + CONFLICT = 409, + + /** + * Indicates that the resource requested is no longer available and will not be available again. + * This should be used when a resource has been intentionally removed and the resource should be purged. + * Upon receiving a 410 status code, the client should not request the resource in the future. + * Clients such as search engines should remove the resource from their indices. + * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead. + */ + GONE = 410, + + /** + * The request did not specify the length of its content, which is required by the requested resource. + */ + LENGTH_REQUIRED = 411, + + /** + * The server does not meet one of the preconditions that the requester put on the request. + */ + PRECONDITION_FAILED = 412, + + /** + * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large". + */ + PAYLOAD_TOO_LARGE = 413, + + /** + * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request, + * in which case it should be converted to a POST request. + * Called "Request-URI Too Long" previously. + */ + URI_TOO_LONG = 414, + + /** + * The request entity has a media type which the server or resource does not support. + * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format. + */ + UNSUPPORTED_MEDIA_TYPE = 415, + + /** + * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. + * For example, if the client asked for a part of the file that lies beyond the end of the file. + * Called "Requested Range Not Satisfiable" previously. + */ + RANGE_NOT_SATISFIABLE = 416, + + /** + * The server cannot meet the requirements of the Expect request-header field. + */ + EXPECTATION_FAILED = 417, + + /** + * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, + * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by + * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com. + */ + I_AM_A_TEAPOT = 418, + + /** + * The request was directed at a server that is not able to produce a response (for example because a connection reuse). + */ + MISDIRECTED_REQUEST = 421, + + /** + * The request was well-formed but was unable to be followed due to semantic errors. + */ + UNPROCESSABLE_ENTITY = 422, + + /** + * The resource that is being accessed is locked. + */ + LOCKED = 423, + + /** + * The request failed due to failure of a previous request (e.g., a PROPPATCH). + */ + FAILED_DEPENDENCY = 424, + + /** + * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. + */ + UPGRADE_REQUIRED = 426, + + /** + * The origin server requires the request to be conditional. + * Intended to prevent "the 'lost update' problem, where a client + * GETs a resource's state, modifies it, and PUTs it back to the server, + * when meanwhile a third party has modified the state on the server, leading to a conflict." + */ + PRECONDITION_REQUIRED = 428, + + /** + * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes. + */ + TOO_MANY_REQUESTS = 429, + + /** + * The server is unwilling to process the request because either an individual header field, + * or all the header fields collectively, are too large. + */ + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + + /** + * A server operator has received a legal demand to deny access to a resource or to a set of resources + * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451. + */ + UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + /** + * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. + */ + INTERNAL_SERVER_ERROR = 500, + + /** + * The server either does not recognize the request method, or it lacks the ability to fulfill the request. + * Usually this implies future availability (e.g., a new feature of a web-service API). + */ + NOT_IMPLEMENTED = 501, + + /** + * The server was acting as a gateway or proxy and received an invalid response from the upstream server. + */ + BAD_GATEWAY = 502, + + /** + * The server is currently unavailable (because it is overloaded or down for maintenance). + * Generally, this is a temporary state. + */ + SERVICE_UNAVAILABLE = 503, + + /** + * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + */ + GATEWAY_TIMEOUT = 504, + + /** + * The server does not support the HTTP protocol version used in the request + */ + HTTP_VERSION_NOT_SUPPORTED = 505, + + /** + * Transparent content negotiation for the request results in a circular reference. + */ + VARIANT_ALSO_NEGOTIATES = 506, + + /** + * The server is unable to store the representation needed to complete the request. + */ + INSUFFICIENT_STORAGE = 507, + + /** + * The server detected an infinite loop while processing the request. + */ + LOOP_DETECTED = 508, + + /** + * Further extensions to the request are required for the server to fulfill it. + */ + NOT_EXTENDED = 510, + + /** + * The client needs to authenticate to gain network access. + * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used + * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot). + */ + NETWORK_AUTHENTICATION_REQUIRED = 511 +} + +export default HttpStatusCode; diff --git a/src/app/publishapi/class/openapi-dto.ts b/src/app/publishapi/class/openapi-dto.ts new file mode 100644 index 000000000..ec0f1da6f --- /dev/null +++ b/src/app/publishapi/class/openapi-dto.ts @@ -0,0 +1,60 @@ +import HttpStatusCode, { HttpMethod } from './http-enum' +import { ParameterType, ShemaType } from './openapi-enum'; + + +export interface ServerDTO { + url:string; +} + +export interface SchemaDTO { + type: ShemaType, + default: string +} + +export interface ParameterDTO { + in: ParameterType, + name: string, + schema: SchemaDTO +} + +export interface ItemDTO { + type: string +} +export interface ResponsSchemaDTO { + type: string, + items: ItemDTO[] +} + +export interface ContentDTO { + schema: ResponsSchemaDTO +} + +export type MimeTypeToContentMapDTO = Map<string,ContentDTO>; + +export interface ResponseDTO { + description: string, + content: MimeTypeToContentMapDTO +} + +export type HttpCodeToResponseMapDTO = Map<HttpStatusCode,ResponseDTO>; + +export interface RequestDTO { + description: string, + tags: string[] + parameters: ParameterDTO[], + responses : HttpCodeToResponseMapDTO[], +} + +export type MethodToRequestMapDTO = Map<HttpMethod,RequestDTO>; + +export type PathMapDTO = Map<string,MethodToRequestMapDTO>; + +export type InfoMapDTO = Map<string,string>; + +export interface OpenApiDTO { + openapi: string; + info: InfoMapDTO; + servers: ServerDTO[]; + paths: PathMapDTO; +} + diff --git a/src/app/publishapi/class/openapi-enum.ts b/src/app/publishapi/class/openapi-enum.ts new file mode 100644 index 000000000..54cbf3f14 --- /dev/null +++ b/src/app/publishapi/class/openapi-enum.ts @@ -0,0 +1,13 @@ +export enum ShemaType { + string = 'string', + integer = 'integer', + boolean = 'boolean' +} + +export enum ParameterType { + query = 'query', + path = 'path', + header = 'header', + cookie = 'cookie', + body = 'body' +} diff --git a/src/app/publishapi/class/openapi.ts b/src/app/publishapi/class/openapi.ts new file mode 100644 index 000000000..30d2a7bbb --- /dev/null +++ b/src/app/publishapi/class/openapi.ts @@ -0,0 +1,61 @@ +import HttpStatusCode, { HttpMethod } from './http-enum' +import { ParameterType } from './openapi-enum'; + +export interface Server { + url:string; +} + +export interface Schema { + type: string, + default: string +} + +export interface Parameter { + in: ParameterType, + name: string, + schema: Schema +} + +export interface Item { + type: string +} + +export interface ResponseSchema { + type: string, + items: Item[] +} + +export interface Content { + contentType: string, + schema: ResponseSchema +} + +export interface Response { + httpStatusCode: HttpStatusCode, + description: string, + contents: Content[] +} + +export interface Request { + description: string, + tags: string[] + parameters: Parameter[], + responses: Response[], + httpmethod: HttpMethod, + toggled: boolean +} + +export interface Path { + pathName: string, + requests: Request[] +} + +export type InfoMap = Map<string,string>; + +export interface OpenApi { + openapi: string; + info: InfoMap; + servers: Server[]; + paths: Path[]; +} + diff --git a/src/app/publishapi/publishapi.component.html b/src/app/publishapi/publishapi.component.html index 1dcac6658..7038e3131 100644 --- a/src/app/publishapi/publishapi.component.html +++ b/src/app/publishapi/publishapi.component.html @@ -5,19 +5,216 @@ <nb-card> <nb-card-body> - <nb-stepper orientation="horizontal" disableStepNavigation> + <nb-stepper orientation="horizontal" disableStepNavigation> <nb-step [label]="labelOne"> <ng-template #labelOne>First step</ng-template> - <h4>Describe API </h4> - <p class="lorem"> - Describe your repository api to access datasets - </p> - <label>Choose your Repository - <select class="form-control" formControlName="repotype" required> - <option value="1">Inrae</option>> - <option value="2">Inrae-2</option>> - </select> - </label> + <h4>Describe API</h4> + <nb-card> + <nb-card-body> + <nb-tabset> + <nb-tab class="header" tabTitle="Headers" tabIcon="file-text-outline"> + <div class="field"> + <label class="" for="title">Title</label> + <div> + <input name="title" [(ngModel)]="openApi.info.title" fullWidth nbInput/> + </div> + </div> + <div class="field"> + <label for="description">Description</label> + <div> + <textarea name="description " [(ngModel)]="openApi.info.description" placeholder="Textarea" fullWidth nbInput></textarea> + </div> + </div> + <div class="field"> + <label for="version">version</label> + <div> + <input name="version" [(ngModel)]="openApi.info.version" fullWidth nbInput/> + </div> + </div> + <div class="field"> + <label for="x-format">x-format</label> + <div> + <input name="x-format" [(ngModel)]="openApi.info['x-format']" fullWidth nbInput/> + </div> + </div> + <div class="field"> + <label for="x-result">x-result</label> + <div> + <input name="x-result" [(ngModel)]="openApi.info['x-result']" fullWidth nbInput/> + </div> + </div> + <div class="field"> + <label for="x-start-param">x-start-param</label> + <div> + <input name="x-start-param" [(ngModel)]="openApi.info['x-start-param']" fullWidth nbInput/> + </div> + </div> + <div class="field"> + <label for="x-page-param">x-page-param</label> + <div> + <input name="x-page-param" [(ngModel)]="openApi.info['x-page-param']" fullWidth nbInput/> + </div> + </div> + </nb-tab> + <nb-tab class="main" tabTitle="Main" tabIcon="edit-2-outline"> + <div *ngFor="let path of openApi.paths"> + <nb-card> + <nb-card-body> + <!-- <nb-icon icon="plus-outline" nbTooltip="Add path" (click)="addPath()"></nb-icon> + <nb-icon icon="copy-outline" nbTooltip="Duplicate path"(click)="duplicatePath()"></nb-icon> + <nb-icon icon="edit-2-outline" nbTooltip="Edit path description"(click)="editDescriptionPath()"></nb-icon> + <ng-container *ngIf="openApi.paths.length>1"> + <nb-icon icon="trash-2-outline" nbTooltip="Delete path" (click)="deletePath(path)"></nb-icon> + </ng-container> --> + <h5 *ngIf="path.requests[0].tags[0]=='datasetlist'">Get list of datasets</h5> + <h5 *ngIf="path.requests[0].tags[0]=='dataset'">Get dataset</h5> + <label for="Path">Path</label> + <input name="path" [(ngModel)]="path.pathName" nbInput/> + <div *ngFor="let request of path.requests"> + <nb-card> + <nb-card-body> + <div class="request"> + <label>Request</label><br/> + <!-- <nb-icon icon="menu-outline" nbTooltip="Toggle Request" (click)="togglRequest(request)"></nb-icon> + <nb-icon icon="plus-outline" nbTooltip="Add Request" (click)="addRequest(path)"></nb-icon> + <nb-icon icon="copy-outline" nbTooltip="Duplicate Request" (click)="DuplicateRequest(path)"></nb-icon> + <ng-container *ngIf="path.requests.length>1"> + <nb-icon icon="trash-2-outline" nbTooltip="Delete Request" (click)="deleteRequest(path, request)"></nb-icon> + </ng-container> --> + <span class="title">{{request.httpmethod}}</span> + <span class="title">{{request.description}}</span> + <div class="requestbody"> + <nb-tabset> + <nb-tab tabTitle="Main" tabIcon="settings-2-outline"> + <nb-card> + <nb-card-body> + <table> + <thead> + <tr> + <th>Method</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <nb-select [(ngModel)]="request.httpmethod"> + <nb-option value="GET">GET</nb-option> + <nb-option value="POST">POST</nb-option> + <nb-option value="PUT">PUT</nb-option> + <nb-option value="PATCH">PATCH</nb-option> + <nb-option value="DELETE">DELETE</nb-option> + <nb-option value="HEAD">HEAD</nb-option> + <nb-option value="OPTIONS">OPTIONS</nb-option> + </nb-select> + </td> + <td> + <input name="description" [(ngModel)]="request.description" nbInput/> + </td> + </tr> + </tbody> + </table> + </nb-card-body> + </nb-card> + </nb-tab> + <nb-tab tabTitle="Parameters" tabIcon="list-outline"> + <nb-card> + <nb-card-body> + <label>Parameters</label> + <table> + <thead> + <tr> + <th>Name</th> + <th>Location</th> + <th>Type</th> + <th>Default value</th> + <th></th> + </tr> + </thead> + <tbody> + <tr *ngFor="let parameter of request.parameters"> + <td> + <input [(ngModel)]="parameter.name" nbInput/> + </td> + <td> + <nb-select [(ngModel)]="parameter.in"> + <nb-option *ngFor="let type of parametersTypes" value="{{type}}">{{type}}</nb-option> + </nb-select> + </td> + <td> + <nb-select [(ngModel)]="parameter.schema.type"> + <nb-option *ngFor="let type of shemaTypes" value="{{type}}">{{type}}</nb-option> + </nb-select> + </td> + <td> + <input name="default" [(ngModel)]="parameter.schema.default" nbInput/> + </td> + <td> + <nb-icon icon="plus-outline" nbTooltip="Add parameter" (click)="addParameter(request)"></nb-icon> + <nb-icon icon="copy-outline" nbTooltip="Duplicate parameter" (click)="duplicateParameter(request, parameter)"></nb-icon> + <ng-container *ngIf="request.parameters.length>1"> + <nb-icon icon="trash-2-outline" nbTooltip="Delete parameter" (click)="deleteParameter(request, parameter)"></nb-icon> + </ng-container> + </td> + </tr> + </tbody> + </table> + </nb-card-body> + </nb-card> + </nb-tab> + <nb-tab tabTitle="Response" tabIcon="checkmark-circle-2-outline"> + <div *ngFor="let response of request.responses"> + <nb-card> + <nb-card-body> + <label>Reponse</label><br/> + <!-- <nb-icon icon="plus-outline" nbTooltip="Add Response" (click)="addResponse(request)"></nb-icon> + <nb-icon icon="copy-outline" nbTooltip="Duplicate Response" (click)="duplicateResponse(request)"></nb-icon> + <ng-container *ngIf="request.responses.length>1"> + <nb-icon icon="trash-2-outline" nbTooltip="Delete Response" (click)="deleteResponse(request, response)"></nb-icon> + </ng-container> --> + <table> + <thead> + <tr> + <th>Http Status Code</th> + <th>Media-Type</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <nb-select> + <nb-option *ngFor="let code of httpStatusCodes" value="{{code}}">{{code}}</nb-option> + </nb-select> + </td> + <td> + <div *ngFor="let content of response.contents"> + <input name="default" [(ngModel)]="content.contentType" nbInput/> + </div> + </td> + <td> + <input name="default" [(ngModel)]="response.description" nbInput/> + </td> + </tr> + </tbody> + </table> + </nb-card-body> + </nb-card> + </div> + </nb-tab> + </nb-tabset> + </div> + </div> + </nb-card-body> + </nb-card> + </div> + </nb-card-body> + </nb-card> + </div> + </nb-tab> + </nb-tabset> + </nb-card-body> + </nb-card> <button class="prev-button" nbButton disabled nbStepperNext>prev</button> <button class="next-button" nbButton nbStepperNext>next</button> </nb-step> @@ -34,7 +231,7 @@ <ng-template #labelThree>Third step</ng-template> <h4>Map DCAT Schema </h4> <p class="lorem"> - Map your metadata schema with DCAT schema + Map your metadata schema with DCAT schema </p> <button class="prev-button" nbButton nbStepperPrevious>prev</button> <button class="next-button" nbButton nbStepperNext>next</button> @@ -50,4 +247,4 @@ </nb-step> </nb-stepper> </nb-card-body> - </nb-card> \ No newline at end of file + </nb-card> diff --git a/src/app/publishapi/publishapi.component.scss b/src/app/publishapi/publishapi.component.scss index b4051cfb6..17d278b2e 100644 --- a/src/app/publishapi/publishapi.component.scss +++ b/src/app/publishapi/publishapi.component.scss @@ -1,4 +1,65 @@ .prev-button { margin-right: 1rem; - } \ No newline at end of file +} + + +/** +HEADER TAB +*/ +.header label { + display: inline-block; + text-align: right; + width: 10rem; + margin-right: 1.5rem; + vertical-align: top; +} + +.header input { + max-width: 100%; + width: 100%; +} + +.header textarea { + max-height: 600px; + min-height: 120px; +} + +.header div.field { + margin-bottom: .75rem; +} + +.header div.field div { + display: inline-block; + position: relative; + width: 100%; + max-width: 40rem; + min-width: 10rem; +} + +/** +MAIN TAB +*/ + +.main label { + display: inline-block; + margin-right: 1.5rem; +} + +.main nb-icon { + margin: 0.32em; + background-color: transparent; +} + +.main nb-icon:hover { + color: #598bff !important; + cursor: pointer; +} + +.main nb-card { + background: rgba(0, 0, 0, 0.030); +} + +.main div.request span.title { + margin-right: 0.32em; +} diff --git a/src/app/publishapi/publishapi.component.ts b/src/app/publishapi/publishapi.component.ts index f3ed41202..fc913f07e 100644 --- a/src/app/publishapi/publishapi.component.ts +++ b/src/app/publishapi/publishapi.component.ts @@ -1,22 +1,90 @@ import { ComponentFactoryResolver, OnDestroy, ViewChild } from '@angular/core'; -import { Component, Input, OnInit, TemplateRef } from '@angular/core'; +import { Component, Input, OnInit, TemplateRef } from '@angular/core'; import { Directive, ViewContainerRef } from '@angular/core'; - - - - +import { FormControl } from '@angular/forms'; +import { cloneDeep } from 'lodash'; +import HttpStatusCode from './class/http-enum'; +import { OpenApi, Parameter, Path, Request, Response } from './class/openapi'; +import { ParameterType, ShemaType } from './class/openapi-enum'; +import { OpenApiService } from './services/openapi-service'; @Component({ selector: 'app-publishapi', templateUrl: './publishapi.component.html', styleUrls: ['./publishapi.component.scss'] }) + export class PublishApiComponent implements OnInit { - constructor() { } + constructor(private openApiService:OpenApiService) {} + + openApi: OpenApi; + parametersTypes: string[]; + shemaTypes: string[]; + httpStatusCodes: string[]; ngOnInit(): void { - + this.openApi = this.openApiService.getFromJson(); + this.parametersTypes = Object.keys(ParameterType); + this.shemaTypes = Object.keys(ShemaType); + const httpStatusCodeskeys = Object.keys(HttpStatusCode).filter(k => typeof HttpStatusCode[k as any] === "number"); + this.httpStatusCodes = httpStatusCodeskeys.map(k => HttpStatusCode[k as any]); + } + + addPath(): void { + let emptyPath: Path = this.openApiService.getEmptyPath(); + this.openApi.paths.unshift(emptyPath); + } + + deletePath(path: Path): void { + const index: number = this.openApi.paths.indexOf(path); + if (index !== -1) { + this.openApi.paths.splice(index, 1); + } + } + + addRequest(path: Path): void { + let emptyRequest: Request = this.openApiService.getEmptyRequest(); + path.requests.unshift(emptyRequest); + } + + deleteRequest(path: Path, request: Request): void { + const indexRequest: number = path.requests.indexOf(request); + if (indexRequest !== -1) { + path.requests.splice(indexRequest, 1); + } + } + + togglRequest(request: Request): void { + request.toggled = !request.toggled; + } + + addParameter(request: Request): void { + let emptyParameter: Parameter = this.openApiService.getEmptyParameter(); + request.parameters.unshift(emptyParameter); + } + + duplicateParameter(request: Request, parameter: Parameter): void { + request.parameters.unshift(cloneDeep(parameter)); + } + + deleteParameter(request: Request, parameter: Parameter): void { + const indexParameter: number = request.parameters.indexOf(parameter); + if (indexParameter !== -1) { + request.parameters.splice(indexParameter, 1); + } + } + + addResponse(request: Request): void { + let emptyResponse: Response = this.openApiService.getEmptyResponse(); + request.responses.unshift(emptyResponse); + } + + deleteResponse(request: Request, response: Response): void { + const indexResponse: number = request.responses.indexOf(response); + if (indexResponse !== -1) { + request.responses.splice(indexResponse, 1); + } } goToLink() { diff --git a/src/app/publishapi/services/openapi-dto-mapping-service.ts b/src/app/publishapi/services/openapi-dto-mapping-service.ts new file mode 100644 index 000000000..16e8d7432 --- /dev/null +++ b/src/app/publishapi/services/openapi-dto-mapping-service.ts @@ -0,0 +1,90 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import HttpStatusCode, { HttpMethod } from '../class/http-enum'; +import { InfoMap, OpenApi, Path, Server, Request, Parameter, Response, Content, ResponseSchema } from '../class/openapi'; +import { ContentDTO, HttpCodeToResponseMapDTO, MethodToRequestMapDTO, MimeTypeToContentMapDTO, OpenApiDTO, ParameterDTO, PathMapDTO, RequestDTO, ResponseDTO } from '../class/openapi-dto'; + +@Injectable({ + providedIn: 'root' + }) + +export class OpenApiDTOMappingService { + constructor() {} + + mapContentFromDTO(mimeTypeToContentMapDTO: MimeTypeToContentMapDTO, contentType: string){ + let contentDTO: ContentDTO = mimeTypeToContentMapDTO[contentType]; + let content: Content = {} as Content; + content.contentType = contentType; + content.schema = contentDTO.schema as ResponseSchema; + return content; + } + + mapResponseFromDTO(httpCodeToResponseMapDTO: HttpCodeToResponseMapDTO[], httpStatusCodeToreponseDTOMap: string): Response { + let responseDTO: ResponseDTO = httpCodeToResponseMapDTO[httpStatusCodeToreponseDTOMap]; + let response: Response = {} as Response; + response.description = responseDTO.description; + response.httpStatusCode = httpStatusCodeToreponseDTOMap as unknown as HttpStatusCode; + response.contents = [] as Content[]; + Object.keys(responseDTO.content).forEach((contentType)=>{ + let content: Content = this.mapContentFromDTO(responseDTO.content, contentType); + response.contents.push(content); + }); + return response; + } + + mapParametersFromDTO(parameterDTO: ParameterDTO[]): Parameter[] { + return parameterDTO as Parameter[]; + } + + mapRequestFromDTO(methodToRequestMapDTO: MethodToRequestMapDTO, httpMethod: HttpMethod): Request{ + let requestDTO: RequestDTO = methodToRequestMapDTO[httpMethod]; + let request: Request = {} as Request; + request.httpmethod = httpMethod; + request.description = requestDTO.description; + request.tags = Object.create(requestDTO.tags) as string[]; + request.parameters = this.mapParametersFromDTO(requestDTO.parameters); + request.responses = [] as Response[]; + request.toggled = false; + Object.keys(requestDTO.responses).forEach((HttpStatusCode)=>{ + let response: Response = this.mapResponseFromDTO(requestDTO.responses, HttpStatusCode); + request.responses.push(response); + }); + return request; + } + + mapPathFromDTO(pathMapDTO: PathMapDTO, pathName: string): Path{ + let methodToRequestMapDTO: MethodToRequestMapDTO = pathMapDTO[pathName]; + let path:Path = {} as Path; + path.pathName = pathName.trim(); + path.requests = [] as Request[]; + Object.keys(methodToRequestMapDTO).forEach((httpMethod:HttpMethod)=>{ + let request: Request = this.mapRequestFromDTO(methodToRequestMapDTO, httpMethod); + path.requests.push(request); + }); + return path; + } + + mapOpenApiFromDTO(openApiDTO:OpenApiDTO): OpenApi { + let openApi:OpenApi = {} as OpenApi; + openApi.info = Object.create(openApiDTO.info) as InfoMap; + openApi.openapi = openApiDTO.openapi; + openApi.servers = Object.create(openApiDTO.servers) as Server[]; + openApi.paths = [] as Path[]; + Object.keys(openApiDTO.paths).forEach((pathName)=>{ + let path: Path = this.mapPathFromDTO(openApiDTO.paths, pathName); + openApi.paths.push(path); + }); + return openApi; + } + + /** + * TODO Mapping to DTO + * @param openApi + */ + mapToDto(openApi:OpenApi): OpenApiDTO { + let openApiDTO:OpenApiDTO = {} as OpenApiDTO; + // TODO + return openApiDTO; + } + +} diff --git a/src/app/publishapi/services/openapi-service.ts b/src/app/publishapi/services/openapi-service.ts new file mode 100644 index 000000000..d0729639f --- /dev/null +++ b/src/app/publishapi/services/openapi-service.ts @@ -0,0 +1,349 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import HttpStatusCode, { HttpMethod } from '../class/http-enum'; +import { OpenApi, Parameter, Path, Request, Response } from '../class/openapi'; +import { HttpCodeToResponseMapDTO, MethodToRequestMapDTO, OpenApiDTO, ParameterDTO, PathMapDTO } from '../class/openapi-dto'; +import { OpenApiDTOMappingService } from './openapi-dto-mapping-service'; + +@Injectable({ + providedIn: 'root' + }) + +export class OpenApiService { + constructor(private http: HttpClient, private openApiDTOMappingService: OpenApiDTOMappingService) {} + + getFromRepository(repositoryId:string): Observable<OpenApiDTO> { + return this.http.get<OpenApiDTO>('/openapi/' + repositoryId); + } + + getFromJson(): OpenApi { + let openApiDTO = <OpenApiDTO>JSON.parse(this.jsonFull); + return this.openApiDTOMappingService.mapOpenApiFromDTO(openApiDTO); + } + + getEmptyPath(): Path { + let pathDTO = <PathMapDTO>JSON.parse(this.emptyPathJson); + let paths = [] as Path[]; + Object.keys(pathDTO).forEach((pathName)=>{ + let path: Path = this.openApiDTOMappingService.mapPathFromDTO(pathDTO, pathName); + paths.push(path); + }); + return paths[0]; + } + + getEmptyRequest(): Request { + let methodToRequestMapDTO: MethodToRequestMapDTO = <MethodToRequestMapDTO>JSON.parse('{' + this.emptyMethodJson + '}'); + let requests = [] as Request[]; + Object.keys(methodToRequestMapDTO).forEach((httpMethod:HttpMethod)=>{ + let request: Request = this.openApiDTOMappingService.mapRequestFromDTO(methodToRequestMapDTO, httpMethod); + requests.push(request); + }); + return requests[0]; + } + + getEmptyParameter(): Parameter { + let parameterDTO: ParameterDTO[] = <ParameterDTO[]>JSON.parse(this.emptyParameter); + return this.openApiDTOMappingService.mapParametersFromDTO(parameterDTO)[0]; + } + + getEmptyResponse(): Response { + let httpCodeToResponseMapDTO : HttpCodeToResponseMapDTO[] = <HttpCodeToResponseMapDTO[]>JSON.parse('{' + this.emptyResponse + '}'); + let responses = [] as Response[]; + Object.keys(httpCodeToResponseMapDTO).forEach((httpStatusCode:string)=>{ + let response: Response = this.openApiDTOMappingService.mapResponseFromDTO(httpCodeToResponseMapDTO, httpStatusCode); + responses.push(response); + }); + return responses[0]; + } + + + private json = '{' + + ' "openapi" : "3.0.1",' + + ' "info" : {' + + ' "title" : "ifremer opensearch",' + + ' "description" : "balises argo",' + + ' "version" : "0.1",' + + ' "x-format" : "custom",' + + ' "x-catalog-id" : "083f3716-1038-4fdd-b275-67c67f0c26b7",' + + ' "x-result" : "xml",' + + ' "x-start-param" : "startPage",' + + ' "x-page-param" : "count"' + + ' },' + + ' "servers" : [ ' + + ' {' + + ' "url" : "https://opensearch.ifremer.fr"' + + ' }' + + ' ],' + + ' "paths" : {' + + ' "/granules.atom" : {' + + ' "GET" : {' + + ' "description" : "Liste de liens permettant de télécharger des fichiers correspondant aux critères spatio-temporels demandés",' + + ' "tags" : [ ' + + ' "datasetlist"' + + ' ],' + + ' "parameters" : [ ' + + ' {' + + ' "name" : "timeEnd",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "2020-04-30T00:00:00Z"' + + ' }' + + ' }, ' + + ' {' + + ' "name" : "startPage",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "0"' + + ' }' + + ' }, ' + + ' {' + + ' "name" : "timeStart",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "2020-02-01T00:00:00Z"' + + ' }' + + ' }, ' + + ' {' + + ' "name" : "count",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "10"' + + ' }' + + ' }, ' + + ' {' + + ' "name" : "datasetId",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "argo"' + + ' }' + + ' }, ' + + ' {' + + ' "name" : "geoBox",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "-180.0,-20.0,180.0,20.0"' + + ' }' + + ' }' + + ' ],' + + ' "responses" : {' + + ' "200" : {' + + ' "description" : "A XML array",' + + ' "content" : {' + + ' "application/xml" : {' + + ' "schema" : {' + + ' "type" : "array",' + + ' "items" : {' + + ' "type" : "string"' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + '}'; + + private jsonFull = + '{' + + ' "openapi" : "3.0.0",' + + ' "info" : {' + + ' "title" : "inrae list datasets",' + + ' "version" : "0",' + + ' "x-format" : "dataverse",' + + ' "x-catalog-id" : "09de0761-f1b5-4dc1-8c5c-92e5e782cb2c",' + + ' "x-result" : "json",' + + ' "x-start-param" : "start",' + + ' "x-page-param" : "per_page"' + + ' },' + + ' "servers" : [ ' + + ' {' + + ' "url" : "https://data.inrae.fr/api"' + + ' }' + + ' ],' + + ' "paths" : {' + + ' "/search" : {' + + ' "GET" : {' + + ' "description" : "Returns a list of datasets",' + + ' "tags" : [ ' + + ' "datasetlist"' + + ' ],' + + ' "parameters" : [ ' + + ' {' + + ' "in" : "query",' + + ' "name" : "q",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "*"' + + ' },' + + ' "description" : "for all star"' + + ' }, ' + + ' {' + + ' "in" : "query",' + + ' "name" : "per_page",' + + ' "schema" : {' + + ' "type" : "integer",' + + ' "default" : 10' + + ' },' + + ' "description" : "pages"' + + ' }, ' + + ' {' + + ' "in" : "query",' + + ' "name" : "type",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "dataset"' + + ' },' + + ' "description" : "dataset"' + + ' }, ' + + ' {' + + ' "in" : "query",' + + ' "name" : "start",' + + ' "schema" : {' + + ' "type" : "integer",' + + ' "default" : 0' + + ' },' + + ' "description" : "start"' + + ' }, ' + + ' {' + + ' "in" : "query",' + + ' "name" : "show_entity_ids",' + + ' "schema" : {' + + ' "type" : "boolean",' + + ' "default" : true' + + ' },' + + ' "description" : "id"' + + ' }, ' + + ' {' + + ' "in" : "query",' + + ' "name" : "show_my_data",' + + ' "schema" : {' + + ' "type" : "boolean",' + + ' "default" : true' + + ' },' + + ' "description" : "aaa"' + + ' }' + + ' ],' + + ' "responses" : {' + + ' "200" : {' + + ' "description" : "A JSON array of datasets",' + + ' "content" : {' + + ' "application/json" : {' + + ' "schema" : {' + + ' "type" : "array",' + + ' "items" : {' + + ' "type" : "string"' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' },' + + ' "/datasets/{entity_id}" : {' + + ' "GET" : {' + + ' "description" : "Get the details on the dataset that the identifier is in parameter ",' + + ' "tags" : [ ' + + ' "dataset"' + + ' ],' + + ' "parameters" : [ ' + + ' {' + + ' "in" : "path",' + + ' "name" : "entity_id",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : "95707"' + + ' },' + + ' "description" : "he Digital Object Identifier (DOI) of the dataset searched."' + + ' }' + + ' ],' + + ' "responses" : {' + + ' "200" : {' + + ' "description" : "A JSON array of one dataset",' + + ' "content" : {' + + ' "application/json" : {' + + ' "schema" : {' + + ' "type" : "array",' + + ' "items" : {' + + ' "type" : "string"' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + '}'; + + private emptyResponse = + '"200" : {' + + ' "description" : "",' + + ' "content" : {' + + ' " " : {' + + ' "schema" : {' + + ' "type" : "array",' + + ' "items" : {' + + ' "type" : "string"' + + ' }' + + ' }' + + ' }' + + ' }' + + '}'; + + private emptyParameter = + '[' + + ' {' + + ' "name" : "",' + + ' "in" : "query",' + + ' "schema" : {' + + ' "type" : "string",' + + ' "default" : ""' + + ' }' + + ' }' + + ']'; + + + private emptyMethodJson = + ' "GET" : {' + + ' "description" : "",' + + ' "tags" : [ ' + + ' "datasetlist"' + + ' ],' + + ' "parameters" : ' + + this.emptyParameter + + ' ,' + + ' "responses" : {' + + ' "200" : {' + + ' "description" : "",' + + ' "content" : {' + + ' "" : {' + + ' "schema" : {' + + ' "type" : "array",' + + ' "items" : {' + + ' "type" : "string"' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }' + + ' }'; + + private emptyPathJson = '{' + + ' " " : {' + + this.emptyMethodJson + + ' }' + + '}'; + +} diff --git a/src/app/repository/repository.component.html b/src/app/repository/repository.component.html index 787fd5ea8..a5a3abd63 100644 --- a/src/app/repository/repository.component.html +++ b/src/app/repository/repository.component.html @@ -7,22 +7,19 @@ <div> - <label>Repository Type - <select class="form-control" formControlName="repotype" required> - <option value="Dataverse">Dataverse</option>> - <option value="Custom">Custom</option>> - </select> - </label> + <label>Repository Type</label> + <select class="form-control" formControlName="repotype" required> + <option value="Dataverse">Dataverse</option>> + <option value="Custom">Custom</option>> + </select> </div> <div class="form-group"> - <label>Repository Name + <label>Repository Name</label> <input type="text" class="form-control" formControlName="reponame" required> - </label> </div> <div class="form-group"> - <label>Description + <label>Description</label> <textarea class="form-control" formControlName="repodescription"></textarea> - </label> </div> <div class="form-group"> <label>url</label> diff --git a/src/styles.scss b/src/styles.scss index 3c4e4a354..a931d951b 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -40,7 +40,6 @@ button:disabled { label { display: block; - width: 40em; margin: .5em 0; color: #607D8B; font-weight: bold; @@ -87,4 +86,4 @@ textarea { // install the framework styles @include nb-install() { @include nb-theme-global(); -}; \ No newline at end of file +}; -- GitLab