|
| 1 | +import { Tag } from "@blueprintjs/core"; |
| 2 | +import { PageLinks } from "df/docs/components/page_links"; |
| 3 | +import * as styles from "df/docs/components/swagger.css"; |
| 4 | +import React from "react"; |
| 5 | +import { |
| 6 | + Operation as IOperation, |
| 7 | + Parameter as IParameter, |
| 8 | + Schema, |
| 9 | + Spec |
| 10 | +} from "swagger-schema-official"; |
| 11 | + |
| 12 | +interface IProps { |
| 13 | + apiHost: string; |
| 14 | + spec: Spec; |
| 15 | +} |
| 16 | + |
| 17 | +interface IOperationWithPath extends IOperation { |
| 18 | + path: string; |
| 19 | + method: string; |
| 20 | +} |
| 21 | + |
| 22 | +export class Swagger extends React.Component<IProps> { |
| 23 | + public render() { |
| 24 | + const { spec, apiHost } = this.props; |
| 25 | + const allOperations: IOperationWithPath[] = Object.keys(spec.paths) |
| 26 | + .map(path => { |
| 27 | + const schema = spec.paths[path]; |
| 28 | + return [ |
| 29 | + { ...schema.post, method: "post" }, |
| 30 | + { ...schema.get, method: "get" }, |
| 31 | + { ...schema.put, method: "put" }, |
| 32 | + { ...schema.delete, method: "delete" } |
| 33 | + ] |
| 34 | + .filter(operation => !!operation.operationId) |
| 35 | + .map(operation => ({ path, ...operation })); |
| 36 | + }) |
| 37 | + .reduce((acc, curr) => [...acc, ...curr], []); |
| 38 | + |
| 39 | + return ( |
| 40 | + <div className={styles.container}> |
| 41 | + <div className={styles.sidebar} /> |
| 42 | + <div className={styles.mainContent}> |
| 43 | + <div> |
| 44 | + <h1>Dataform Web API</h1> |
| 45 | + {allOperations.map(operation => ( |
| 46 | + <Operation key={operation.operationId} apiHost={apiHost} operation={operation} /> |
| 47 | + ))} |
| 48 | + {Object.keys(spec.definitions).map(name => ( |
| 49 | + <Definition key={name} name={name} schema={spec.definitions[name]} /> |
| 50 | + ))} |
| 51 | + </div> |
| 52 | + {this.props.children} |
| 53 | + </div> |
| 54 | + <div className={styles.sidebarRight}> |
| 55 | + <h5>Operations</h5> |
| 56 | + <PageLinks |
| 57 | + links={allOperations.map(operation => ({ |
| 58 | + id: operation.operationId, |
| 59 | + text: operation.operationId |
| 60 | + }))} |
| 61 | + /> |
| 62 | + <h5>Types</h5> |
| 63 | + <PageLinks |
| 64 | + links={Object.keys(spec.definitions).map(name => ({ |
| 65 | + id: `/definitions/${name}`, |
| 66 | + text: classname(name) |
| 67 | + }))} |
| 68 | + /> |
| 69 | + </div> |
| 70 | + </div> |
| 71 | + ); |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +export class Operation extends React.Component<{ apiHost: string; operation: IOperationWithPath }> { |
| 76 | + public render() { |
| 77 | + const { operation, apiHost } = this.props; |
| 78 | + return ( |
| 79 | + <div className={styles.definition}> |
| 80 | + <h2 id={operation.operationId}> |
| 81 | + {operation.operationId} |
| 82 | + <code className={styles.methodTag}>{operation.method.toUpperCase()}</code> |
| 83 | + </h2> |
| 84 | + <code>{apiHost + operation.path}</code> |
| 85 | + <p>{operation.summary}</p> |
| 86 | + <h3>Parameters</h3> |
| 87 | + {operation.parameters |
| 88 | + // These types are a nightmare :(. |
| 89 | + .map(parameter => parameter as any) |
| 90 | + .map(parameter => ( |
| 91 | + <div key={parameter.name} className={styles.property}> |
| 92 | + <div className={styles.propertyName}> |
| 93 | + <code>{parameter.name}</code> |
| 94 | + </div> |
| 95 | + <div className={styles.propertyDescription}> |
| 96 | + {parameter.schema && ( |
| 97 | + <a href={parameter.schema.$ref}> |
| 98 | + {classname(parameter.schema.$ref.replace("#/definitions/", ""))} |
| 99 | + </a> |
| 100 | + )} |
| 101 | + {!parameter.schema && <code>string</code>} |
| 102 | + {parameter.description && ( |
| 103 | + <div className={styles.propertyComment}>{parameter.description}</div> |
| 104 | + )} |
| 105 | + </div> |
| 106 | + </div> |
| 107 | + ))} |
| 108 | + <h3>Responses</h3> |
| 109 | + {Object.keys(operation.responses) |
| 110 | + .map(code => ({ code, ...operation.responses[code] })) |
| 111 | + .map(code => code as any) |
| 112 | + .map(response => ( |
| 113 | + <div key={response.code} className={styles.property}> |
| 114 | + <div className={styles.propertyName}> |
| 115 | + <code>{response.code}</code> |
| 116 | + </div> |
| 117 | + <a href={response.schema.$ref}> |
| 118 | + {classname(response.schema.$ref.replace("#/definitions/", ""))} |
| 119 | + </a> |
| 120 | + </div> |
| 121 | + ))} |
| 122 | + </div> |
| 123 | + ); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +export class Definition extends React.Component<{ name: string; schema: Schema }> { |
| 128 | + public render() { |
| 129 | + const { name, schema } = this.props; |
| 130 | + return ( |
| 131 | + <div className={styles.definition}> |
| 132 | + <h2 id={`/definitions/${name}`}>{classname(name)}</h2> |
| 133 | + <div>{schema.description}</div> |
| 134 | + {schema.properties && ( |
| 135 | + <> |
| 136 | + <h3>Properties</h3> |
| 137 | + <div className={styles.properties}> |
| 138 | + {Object.keys(schema.properties).map(propertyName => ( |
| 139 | + <Property |
| 140 | + key={propertyName} |
| 141 | + name={propertyName} |
| 142 | + property={schema.properties[propertyName]} |
| 143 | + /> |
| 144 | + ))} |
| 145 | + </div> |
| 146 | + </> |
| 147 | + )} |
| 148 | + {schema.enum && |
| 149 | + schema.enum.map(enumValue => ( |
| 150 | + <li key={String(enumValue)}> |
| 151 | + <code>{enumValue}</code> |
| 152 | + </li> |
| 153 | + ))} |
| 154 | + </div> |
| 155 | + ); |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +export class Parameter extends React.Component<{ name: string; property: Schema }> { |
| 160 | + public render() { |
| 161 | + const { name, property } = this.props; |
| 162 | + const isArray = property.type === "array"; |
| 163 | + const itemSchema = isArray ? (property.items as Schema) : property; |
| 164 | + const typeTag = ( |
| 165 | + <code> |
| 166 | + {itemSchema.type || classname(itemSchema.$ref.replace("#/definitions/", ""))} |
| 167 | + {isArray ? "[]" : ""} |
| 168 | + </code> |
| 169 | + ); |
| 170 | + return ( |
| 171 | + <div className={styles.property}> |
| 172 | + <div className={styles.propertyName}> |
| 173 | + <code>{name}</code> |
| 174 | + </div> |
| 175 | + <div className={styles.propertyDescription}> |
| 176 | + {itemSchema.$ref ? <a href={itemSchema.$ref}>{typeTag}</a> : typeTag} |
| 177 | + {property.description && ( |
| 178 | + <div className={styles.propertyComment}>{property.description}</div> |
| 179 | + )} |
| 180 | + </div> |
| 181 | + </div> |
| 182 | + ); |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +export class Property extends React.Component<{ name: string; property: Schema }> { |
| 187 | + public render() { |
| 188 | + const { name, property } = this.props; |
| 189 | + const isArray = property.type === "array"; |
| 190 | + const itemSchema = isArray ? (property.items as Schema) : property; |
| 191 | + const typeTag = ( |
| 192 | + <code> |
| 193 | + {itemSchema.type || classname(itemSchema.$ref.replace("#/definitions/", ""))} |
| 194 | + {isArray ? "[]" : ""} |
| 195 | + </code> |
| 196 | + ); |
| 197 | + return ( |
| 198 | + <div className={styles.property}> |
| 199 | + <div className={styles.propertyName}> |
| 200 | + <code>{name}</code> |
| 201 | + </div> |
| 202 | + <div className={styles.propertyDescription}> |
| 203 | + {itemSchema.$ref ? <a href={itemSchema.$ref}>{typeTag}</a> : typeTag} |
| 204 | + {property.description && ( |
| 205 | + <div className={styles.propertyComment}>{property.description}</div> |
| 206 | + )} |
| 207 | + </div> |
| 208 | + </div> |
| 209 | + ); |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +function classname(definitionName: string): string { |
| 214 | + return definitionName.match(/[A-Z].*/)[0]; |
| 215 | +} |
0 commit comments