Skip to content

Commit

Permalink
Implement APIInterface as an abstract class
Browse files Browse the repository at this point in the history
  • Loading branch information
ducku committed Jan 4, 2024
1 parent 55103c7 commit 631d7f8
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 100 deletions.
42 changes: 42 additions & 0 deletions src/APIInterface.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

// Interface for handling function called from the tubemap frontend
// Abstract class expecting different implmentations of the following functions
// Substituting different subclasses should allow the functions to give the same result
export class APIInterface {
// Takes in and process a tube map view(viewTarget) from the tubemap container
// Expects a object to be returned with the necessary information to draw a tubemap from vg
// object should contain keys: graph, gam, region, coloredNodes
async getChunkedData(viewTarget) {
throw new Error("getChunkedData function not implemented");
}

// Returns files used to determine what options are available in the track picker
// Returns object with keys: files, bedFiles
async getFilenames() {
throw new Error("getFilenames function not implemented");
}

// Takes in a bedfile path or a url pointing to a raw bed file
// Returns object with key: bedRegions
// bedRegions contains information extrapolated from each line of the bedfile
async getBedRegions(bedFile) {
throw new Error("getBedRegions function not implemented");
}

// Takes in a graphFile path
// Returns object with key: pathNames
// Returns pathnames available in a graphfile
async getPathNames(graphFile) {
throw new Error("getPathNames function not implemented");
}

// Expects a bed file(or url) and a chunk name
// Attempts to download tracks associated with the chunk name from the bed file if it is a URL
// Returns object with key: tracks
// Returns tracks found from local directories as a tracks object
async getChunkTracks(bedFile, chunk) {
throw new Error("getChunkTracks function not implemented");
}
}

export default APIInterface;
5 changes: 5 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Footer from "./components/Footer";
import { dataOriginTypes } from "./enums";
import "./config-client.js";
import { config } from "./config-global.mjs";
import ServerAPI from "./ServerAPI.mjs";

const EXAMPLE_TRACKS = [
// Fake tracks for the generated examples.
Expand Down Expand Up @@ -46,6 +47,8 @@ class App extends Component {
constructor(props) {
super(props);

this.APIInterface = new ServerAPI(props.apiUrl);

console.log('App component starting up with API URL: ' + props.apiUrl)

// Set defaultViewTarget to either URL params (if present) or the first example
Expand Down Expand Up @@ -186,12 +189,14 @@ class App extends Component {
apiUrl={this.props.apiUrl}
defaultViewTarget={this.defaultViewTarget}
getCurrentViewTarget={this.getCurrentViewTarget}
APIInterface={this.APIInterface}
/>
<TubeMapContainer
viewTarget={this.state.viewTarget}
dataOrigin={this.state.dataOrigin}
apiUrl={this.props.apiUrl}
visOptions={this.state.visOptions}
APIInterface={this.APIInterface}
/>
<CustomizationAccordion
visOptions={this.state.visOptions}
Expand Down
75 changes: 75 additions & 0 deletions src/ServerAPI.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { fetchAndParse } from "./fetchAndParse.js";
import { APIInterface } from "./APIInterface.mjs";

export class ServerAPI extends APIInterface {
constructor(apiUrl) {
super();
this.apiUrl = apiUrl;
this.fetchCanceler = new AbortController();
this.cancelSignal = this.fetchCanceler.signal;
}

async getChunkedData(viewTarget) {
const json = await fetchAndParse(`${this.apiUrl}/getChunkedData`, {
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(viewTarget),
});
return json;
}

async getFilenames() {
const json = await fetchAndParse(`${this.apiUrl}/getFilenames`, {
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
return json;
}

async getBedRegions(bedFile) {
const json = await fetchAndParse(`${this.apiUrl}/getBedRegions`, {
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ bedFile }),
});
return json;
}

async getPathNames(graphFile) {
const json = await fetchAndParse(`${this.apiUrl}/getPathNames`, {
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ graphFile }),
});
return json
}

async getChunkTracks(bedFile, chunk) {
const json = await fetchAndParse(`${this.apiUrl}/getChunkTracks`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ bedFile: bedFile, chunk: chunk }),
});
return json;
}

abortRequests() {
this.fetchCanceler.abort();
}
}

export default ServerAPI;
88 changes: 0 additions & 88 deletions src/components/APIInterface.js

This file was deleted.

12 changes: 6 additions & 6 deletions src/components/HeaderForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import TrackPicker from "./TrackPicker";
import BedFileDropdown from "./BedFileDropdown";
import FormHelperText from "@mui/material/FormHelperText";
import { parseRegion, stringifyRegion, isEmpty, readsExist } from "../common.mjs";
import APIInterface from "./APIInterface";


// See src/Types.ts
Expand Down Expand Up @@ -170,19 +169,19 @@ function viewTargetsEqual(currViewTarget, nextViewTarget) {
class HeaderForm extends Component {
state = EMPTY_STATE;
componentDidMount() {
this.fetchCanceler = new AbortController();
this.cancelSignal = this.fetchCanceler.signal;
this.api = new APIInterface(this.props.apiUrl, this.cancelSignal);
this.fetchCanceled = false;
this.api = this.props.APIInterface;
this.initState();
this.getMountedFilenames();
this.setUpWebsocket();
}
componentWillUnmount() {
// Cancel the requests since we may have long running requests pending.
this.fetchCanceler.abort();
this.api.abortRequests();
this.fetchCanceled = true;
}
handleFetchError(error, message) {
if (!this.cancelSignal.aborted) {
if (!this.fetchCanceled) {
console.log(message, error.name, error.message);
this.setState({ error: error });
} else {
Expand Down Expand Up @@ -887,6 +886,7 @@ HeaderForm.propTypes = {
setDataOrigin: PropTypes.func.isRequired,
setCurrentViewTarget: PropTypes.func.isRequired,
defaultViewTarget: PropTypes.any, // Header Form State, may be null if no params in URL. see Types.ts
APIInterface: PropTypes.object.isRequired,
};

export default HeaderForm;
12 changes: 6 additions & 6 deletions src/components/TubeMapContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import TubeMap from "./TubeMap";
import * as tubeMap from "../util/tubemap";
import { dataOriginTypes } from "../enums";
import PopUpInfoDialog from "./PopUpInfoDialog";
import APIInterface from "./APIInterface";


class TubeMapContainer extends Component {
Expand All @@ -18,19 +17,19 @@ class TubeMapContainer extends Component {
};

componentDidMount() {
this.fetchCanceler = new AbortController();
this.cancelSignal = this.fetchCanceler.signal;
this.api = new APIInterface(this.props.apiUrl, this.cancelSignal);
this.fetchCanceled = false;
this.api = this.props.APIInterface;
this.getRemoteTubeMapData();
}

componentWillUnmount() {
// Cancel the requests since we may have long running requests pending.
this.fetchCanceler.abort();
this.api.abortRequests();
this.fetchCanceled = true;
}

handleFetchError(error, message) {
if (!this.cancelSignal.aborted) {
if (!this.fetchCanceled) {
console.error(message, error);
this.setState({ error: error, isLoading: false });
} else {
Expand Down Expand Up @@ -278,6 +277,7 @@ TubeMapContainer.propTypes = {
dataOrigin: PropTypes.oneOf(Object.values(dataOriginTypes)).isRequired,
viewTarget: PropTypes.object.isRequired,
visOptions: PropTypes.object.isRequired,
APIInterface: PropTypes.object.isRequired,
};

export default TubeMapContainer;

0 comments on commit 631d7f8

Please sign in to comment.