import * as portableFetch from 'portable-fetch' ; import { ExperimentServiceApi , FetchAPI } from '../apis/experiment' ; import { JobServiceApi } from '../apis/job' ; import { ApiPipeline , ApiPipelineVersion , PipelineServiceApi } from '../apis/pipeline' ; import { RunServiceApi } from '../apis/run' ; import { ApiVisualization , VisualizationServiceApi } from '../apis/visualization' ; import { HTMLViewerConfig } from '../components/viewers/HTMLViewer' ; import { PlotType } from '../components/viewers/Viewer' ; import * as Utils from './Utils' ; import { buildQuery } from './Utils' ; import { StoragePath , StorageService } from './WorkflowParser' ; const v1beta1Prefix = 'apis/v1beta1' ; export interface ListRequest { filter ? : string ; orderAscending ? : boolean ; pageSize ? : number ; pageToken ? : string ; sortBy ? : string ; } export interface BuildInfo { apiServerCommitHash ? : string ; apiServerTagName ? : string ; apiServerReady ? : boolean ; buildDate ? : string ; frontendCommitHash ? : string ; frontendTagName ? : string ; } export type JSONPrimitive = string | number | boolean | null ; export type JSONValue = JSONPrimitive | JSONObject | JSONArray ; export type JSONObject = { [ member ] : JSONValue ; } ; export type JSONArray = JSONValue [ ] ; let customVisualizationsAllowed ; const crossBrowserFetch = ( url , init ) => portableFetch ( url , { credentials : 'same-origin' , ... init } ) ; export class Apis { public static async areCustomVisualizationsAllowed ( ) { if ( customVisualizationsAllowed === undefined ) { const result = await this . _fetch ( 'visualizations/allowed' ) ; customVisualizationsAllowed = result === 'true' ; } return customVisualizationsAllowed ; } public static async buildPythonVisualizationConfig ( visualizationData , namespace ? ) { const visualization = await Apis . visualizationServiceApi . createVisualization ( namespace || '' , visualizationData ) ; if ( visualization . html ) { const htmlContent = visualization . html . replace ( 'contentWindow.document.write' , 'srcdoc=' ) ; return { htmlContent , type : PlotType . WEB_APP , } as HTMLViewerConfig ; } else { throw new Error ( 'Visualization was generated successfully but generated HTML was not found.' ) ; } } /**
     * Get pod logs
     */ public static getPodLogs ( runId , podName , podNamespace , taskName ? ) { let query = `k8s/pod/logs?podname=${ encodeURIComponent ( podName ) }&runid=${ encodeURIComponent ( runId ) }${ taskName ? '&taskname=' + encodeURIComponent ( taskName ) : '' }` ; if ( podNamespace ) { query += `&podnamespace=${ encodeURIComponent ( podNamespace ) }` ; } return this . _fetch ( query ) ; } /**
     * Get pod info
     */ public static async getPodInfo ( podName , podNamespace ) { const query = `k8s/pod?podname=${ encodeURIComponent ( podName ) }&podnamespace=${ encodeURIComponent ( podNamespace ) }` ; const podInfo = await this . _fetch ( query ) ; return JSON . parse ( podInfo ) ; } /**
     * Get pod events
     */ public static async getPodEvents ( podName , podNamespace ) { const query = `k8s/pod/events?podname=${ encodeURIComponent ( podName ) }&podnamespace=${ encodeURIComponent ( podNamespace ) }` ; const eventList = await this . _fetch ( query ) ; return JSON . parse ( eventList ) ; } public static get basePath ( ) : string { const path = window . location . protocol + '//' + window . location . host + window . location . pathname ; return path . endsWith ( '/' ) ? path . substr ( 0 , path . length - 1 ) : path ; } public static get experimentServiceApi ( ) : ExperimentServiceApi { if ( ! this . _experimentServiceApi ) { this . _experimentServiceApi = new ExperimentServiceApi ( { basePath : this . basePath } , undefined , crossBrowserFetch ) ; } return this . _experimentServiceApi ; } public static get jobServiceApi ( ) : JobServiceApi { if ( ! this . _jobServiceApi ) { this . _jobServiceApi = new JobServiceApi ( { basePath : this . basePath } , undefined , crossBrowserFetch ) ; } return this . _jobServiceApi ; } public static get pipelineServiceApi ( ) : PipelineServiceApi { if ( ! this . _pipelineServiceApi ) { this . _pipelineServiceApi = new PipelineServiceApi ( { basePath : this . basePath } , undefined , crossBrowserFetch ) ; } return this . _pipelineServiceApi ; } public static get runServiceApi ( ) : RunServiceApi { if ( ! this . _runServiceApi ) { this . _runServiceApi = new RunServiceApi ( { basePath : this . basePath } , undefined , crossBrowserFetch ) ; } return this . _runServiceApi ; } public static get visualizationServiceApi ( ) : VisualizationServiceApi { if ( ! this . _visualizationServiceApi ) { this . _visualizationServiceApi = new VisualizationServiceApi ( { basePath : this . basePath } , undefined , crossBrowserFetch ) ; } return this . _visualizationServiceApi ; } /**
     * Retrieve various information about the build.
     */ public static async getBuildInfo ( ) { return await this . _fetchAndParse < BuildInfo > ( '/healthz' , v1beta1Prefix ) ; } /**
     * Verifies that Jupyter Hub is reachable.
     */ public static async isJupyterHubAvailable ( ) { const response = await fetch ( '/hub/' , { credentials : 'same-origin' } ) ; return response ? response . ok : false ; } /**
     * Reads file from storage using server.
     */ public static readFile ( path , namespace ? , peek ? ) { return this . _fetch ( this . buildReadFileUrl ( { path , namespace , peek , isDownload : false } ) ) ; } param path param isDownload public static buildReadFileUrl ( { path , namespace , peek , isDownload , } ) { const { source , bucket , key } = path ; if ( isDownload ) { return `artifacts/${ source }/${ bucket }/${ key }${ buildQuery ( { namespace , peek , } ) }` ; } else { return `artifacts/get${ buildQuery ( { source , namespace , peek , bucket , key } ) }` ; } } param param . source param param . bucket param param . key public static buildArtifactLinkText ( { source , bucket , key } ) { let platform = source ; if ( source === StorageService . GCS ) { platform = 'gs' ; } return `${ platform }://${ bucket }/${ key }` ; } /**
     * Gets the address (IP + port) of a Tensorboard service given the logdir and tfversion
     */ public static getTensorboardApp ( logdir , namespace ) { return this . _fetchAndParse < { podAddress : string ; tfVersion : string ; image : string ; } > ( `apps/tensorboard${ buildQuery ( { logdir , namespace } ) }` ) ; } /**
     * Starts a deployment and service for Tensorboard given the logdir.
     */ public static startTensorboardApp ( { logdir , namespace , image , podTemplateSpec , } ) { return this . _fetch ( `apps/tensorboard${ buildQuery ( { logdir , namespace , image , podtemplatespec : podTemplateSpec && JSON . stringify ( podTemplateSpec ) , } ) }` , undefined , undefined , { headers : { 'content-type' : 'application/json' } , method : 'POST' } ) ; } /**
     * Check if the underlying Tensorboard pod is actually up, given the pod address
     */ public static async isTensorboardPodReady ( path ) { const resp = await fetch ( path , { method : 'HEAD' } ) ; return resp . ok ; } /**
     * Delete a deployment and its service of the Tensorboard given the URL
     */ public static deleteTensorboardApp ( logdir , namespace ) { return this . _fetch ( `apps/tensorboard?logdir=${ encodeURIComponent ( logdir ) }&namespace=${ encodeURIComponent ( namespace ) }` , undefined , undefined , { method : 'DELETE' } ) ; } /**
     * Uploads the given pipeline file to the backend, and gets back a Pipeline
     * object with its metadata parsed.
     */ public static async uploadPipeline ( pipelineName , pipelineDescription , pipelineData ) { const fd = new FormData ( ) ; fd . append ( 'uploadfile' , pipelineData , pipelineData . name ) ; return await this . _fetchAndParse < ApiPipeline > ( '/pipelines/upload' , v1beta1Prefix , `name=${ encodeURIComponent ( pipelineName ) }&description=${ encodeURIComponent ( pipelineDescription ) }` , { body : fd , cache : 'no-cache' , method : 'POST' , } ) ; } public static async uploadPipelineVersion ( versionName , pipelineId , versionData ) { const fd = new FormData ( ) ; fd . append ( 'uploadfile' , versionData , versionData . name ) ; return await this . _fetchAndParse < ApiPipelineVersion > ( '/pipelines/upload_version' , v1beta1Prefix , `name=${ encodeURIComponent ( versionName ) }&pipelineid=${ encodeURIComponent ( pipelineId ) }` , { body : fd , cache : 'no-cache' , method : 'POST' , } ) ; } public static async getClusterName ( ) { return this . _fetch ( 'system/cluster-name' ) ; } public static async getProjectId ( ) { return this . _fetch ( 'system/project-id' ) ; } private static _experimentServiceApi ? : ExperimentServiceApi ; private static _jobServiceApi ? : JobServiceApi ; private static _pipelineServiceApi ? : PipelineServiceApi ; private static _runServiceApi ? : RunServiceApi ; private static _visualizationServiceApi ? : VisualizationServiceApi ; /**
     * This function will call this._fetch() and parse the resulting JSON into an object of type T.
     */ private static async _fetchAndParse < T > ( path , apisPrefix ? , query ? , init ? ) { const responseText = await this . _fetch ( path , apisPrefix , query , init ) ; try { return JSON . parse ( responseText ) as T ; } catch ( err ) { throw new Error ( `Error parsing response for path: ${ path }\n\n` + `Response was: ${ responseText }\n\nError was: ${ JSON . stringify ( err ) }` ) ; } } /**
     * Makes an HTTP request and returns the response as a string.
     *
     * Use this._fetchAndParse() if you intend to parse the response as JSON into an object.
     */ private static async _fetch ( path , apisPrefix ? , query ? , init ? ) { init = Object . assign ( init || { } , { credentials : 'same-origin' } ) ; const response = await fetch ( ( apisPrefix || '' ) + path + ( query ? '?' + query : '' ) , init ) ; const responseText = await response . text ( ) ; if ( response . ok ) { return responseText ; } else { Utils . logger . error ( `Response for path: ${ path } was not 'ok'\n\nResponse was: ${ responseText }` ) ; throw new Error ( responseText ) ; } } } export enum RunSortKeys { CREATED_AT = 'created_at' , NAME = 'name' } export enum PipelineSortKeys { AUTHOR = 'id' , CREATED_AT = 'created_at' , ID = 'id' , NAME = 'name' } export enum JobSortKeys { CREATED_AT = 'created_at' , ID = 'id' , NAME = 'name' , PIPELINE_ID = 'pipeline_id' } export enum ExperimentSortKeys { CREATED_AT = 'created_at' , ID = 'id' , NAME = 'name' } export enum PipelineVersionSortKeys { CREATED_AT = 'created_at' , NAME = 'name' }