Skip to content

Commit 631d7f8

Browse files
committed
Implement APIInterface as an abstract class
1 parent 55103c7 commit 631d7f8

File tree

6 files changed

+134
-100
lines changed

6 files changed

+134
-100
lines changed

src/APIInterface.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
// Interface for handling function called from the tubemap frontend
3+
// Abstract class expecting different implmentations of the following functions
4+
// Substituting different subclasses should allow the functions to give the same result
5+
export class APIInterface {
6+
// Takes in and process a tube map view(viewTarget) from the tubemap container
7+
// Expects a object to be returned with the necessary information to draw a tubemap from vg
8+
// object should contain keys: graph, gam, region, coloredNodes
9+
async getChunkedData(viewTarget) {
10+
throw new Error("getChunkedData function not implemented");
11+
}
12+
13+
// Returns files used to determine what options are available in the track picker
14+
// Returns object with keys: files, bedFiles
15+
async getFilenames() {
16+
throw new Error("getFilenames function not implemented");
17+
}
18+
19+
// Takes in a bedfile path or a url pointing to a raw bed file
20+
// Returns object with key: bedRegions
21+
// bedRegions contains information extrapolated from each line of the bedfile
22+
async getBedRegions(bedFile) {
23+
throw new Error("getBedRegions function not implemented");
24+
}
25+
26+
// Takes in a graphFile path
27+
// Returns object with key: pathNames
28+
// Returns pathnames available in a graphfile
29+
async getPathNames(graphFile) {
30+
throw new Error("getPathNames function not implemented");
31+
}
32+
33+
// Expects a bed file(or url) and a chunk name
34+
// Attempts to download tracks associated with the chunk name from the bed file if it is a URL
35+
// Returns object with key: tracks
36+
// Returns tracks found from local directories as a tracks object
37+
async getChunkTracks(bedFile, chunk) {
38+
throw new Error("getChunkTracks function not implemented");
39+
}
40+
}
41+
42+
export default APIInterface;

src/App.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Footer from "./components/Footer";
1414
import { dataOriginTypes } from "./enums";
1515
import "./config-client.js";
1616
import { config } from "./config-global.mjs";
17+
import ServerAPI from "./ServerAPI.mjs";
1718

1819
const EXAMPLE_TRACKS = [
1920
// Fake tracks for the generated examples.
@@ -46,6 +47,8 @@ class App extends Component {
4647
constructor(props) {
4748
super(props);
4849

50+
this.APIInterface = new ServerAPI(props.apiUrl);
51+
4952
console.log('App component starting up with API URL: ' + props.apiUrl)
5053

5154
// Set defaultViewTarget to either URL params (if present) or the first example
@@ -186,12 +189,14 @@ class App extends Component {
186189
apiUrl={this.props.apiUrl}
187190
defaultViewTarget={this.defaultViewTarget}
188191
getCurrentViewTarget={this.getCurrentViewTarget}
192+
APIInterface={this.APIInterface}
189193
/>
190194
<TubeMapContainer
191195
viewTarget={this.state.viewTarget}
192196
dataOrigin={this.state.dataOrigin}
193197
apiUrl={this.props.apiUrl}
194198
visOptions={this.state.visOptions}
199+
APIInterface={this.APIInterface}
195200
/>
196201
<CustomizationAccordion
197202
visOptions={this.state.visOptions}

src/ServerAPI.mjs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { fetchAndParse } from "./fetchAndParse.js";
2+
import { APIInterface } from "./APIInterface.mjs";
3+
4+
export class ServerAPI extends APIInterface {
5+
constructor(apiUrl) {
6+
super();
7+
this.apiUrl = apiUrl;
8+
this.fetchCanceler = new AbortController();
9+
this.cancelSignal = this.fetchCanceler.signal;
10+
}
11+
12+
async getChunkedData(viewTarget) {
13+
const json = await fetchAndParse(`${this.apiUrl}/getChunkedData`, {
14+
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
15+
method: "POST",
16+
headers: {
17+
"Content-Type": "application/json",
18+
},
19+
body: JSON.stringify(viewTarget),
20+
});
21+
return json;
22+
}
23+
24+
async getFilenames() {
25+
const json = await fetchAndParse(`${this.apiUrl}/getFilenames`, {
26+
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
27+
method: "GET",
28+
headers: {
29+
"Content-Type": "application/json",
30+
},
31+
});
32+
return json;
33+
}
34+
35+
async getBedRegions(bedFile) {
36+
const json = await fetchAndParse(`${this.apiUrl}/getBedRegions`, {
37+
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
38+
method: "POST",
39+
headers: {
40+
"Content-Type": "application/json",
41+
},
42+
body: JSON.stringify({ bedFile }),
43+
});
44+
return json;
45+
}
46+
47+
async getPathNames(graphFile) {
48+
const json = await fetchAndParse(`${this.apiUrl}/getPathNames`, {
49+
signal: this.cancelSignal, // (so we can cancel the fetch request if we will unmount component)
50+
method: "POST",
51+
headers: {
52+
"Content-Type": "application/json",
53+
},
54+
body: JSON.stringify({ graphFile }),
55+
});
56+
return json
57+
}
58+
59+
async getChunkTracks(bedFile, chunk) {
60+
const json = await fetchAndParse(`${this.apiUrl}/getChunkTracks`, {
61+
method: "POST",
62+
headers: {
63+
"Content-Type": "application/json",
64+
},
65+
body: JSON.stringify({ bedFile: bedFile, chunk: chunk }),
66+
});
67+
return json;
68+
}
69+
70+
abortRequests() {
71+
this.fetchCanceler.abort();
72+
}
73+
}
74+
75+
export default ServerAPI;

src/components/APIInterface.js

Lines changed: 0 additions & 88 deletions
This file was deleted.

src/components/HeaderForm.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import TrackPicker from "./TrackPicker";
1111
import BedFileDropdown from "./BedFileDropdown";
1212
import FormHelperText from "@mui/material/FormHelperText";
1313
import { parseRegion, stringifyRegion, isEmpty, readsExist } from "../common.mjs";
14-
import APIInterface from "./APIInterface";
1514

1615

1716
// See src/Types.ts
@@ -170,19 +169,19 @@ function viewTargetsEqual(currViewTarget, nextViewTarget) {
170169
class HeaderForm extends Component {
171170
state = EMPTY_STATE;
172171
componentDidMount() {
173-
this.fetchCanceler = new AbortController();
174-
this.cancelSignal = this.fetchCanceler.signal;
175-
this.api = new APIInterface(this.props.apiUrl, this.cancelSignal);
172+
this.fetchCanceled = false;
173+
this.api = this.props.APIInterface;
176174
this.initState();
177175
this.getMountedFilenames();
178176
this.setUpWebsocket();
179177
}
180178
componentWillUnmount() {
181179
// Cancel the requests since we may have long running requests pending.
182-
this.fetchCanceler.abort();
180+
this.api.abortRequests();
181+
this.fetchCanceled = true;
183182
}
184183
handleFetchError(error, message) {
185-
if (!this.cancelSignal.aborted) {
184+
if (!this.fetchCanceled) {
186185
console.log(message, error.name, error.message);
187186
this.setState({ error: error });
188187
} else {
@@ -887,6 +886,7 @@ HeaderForm.propTypes = {
887886
setDataOrigin: PropTypes.func.isRequired,
888887
setCurrentViewTarget: PropTypes.func.isRequired,
889888
defaultViewTarget: PropTypes.any, // Header Form State, may be null if no params in URL. see Types.ts
889+
APIInterface: PropTypes.object.isRequired,
890890
};
891891

892892
export default HeaderForm;

src/components/TubeMapContainer.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import TubeMap from "./TubeMap";
77
import * as tubeMap from "../util/tubemap";
88
import { dataOriginTypes } from "../enums";
99
import PopUpInfoDialog from "./PopUpInfoDialog";
10-
import APIInterface from "./APIInterface";
1110

1211

1312
class TubeMapContainer extends Component {
@@ -18,19 +17,19 @@ class TubeMapContainer extends Component {
1817
};
1918

2019
componentDidMount() {
21-
this.fetchCanceler = new AbortController();
22-
this.cancelSignal = this.fetchCanceler.signal;
23-
this.api = new APIInterface(this.props.apiUrl, this.cancelSignal);
20+
this.fetchCanceled = false;
21+
this.api = this.props.APIInterface;
2422
this.getRemoteTubeMapData();
2523
}
2624

2725
componentWillUnmount() {
2826
// Cancel the requests since we may have long running requests pending.
29-
this.fetchCanceler.abort();
27+
this.api.abortRequests();
28+
this.fetchCanceled = true;
3029
}
3130

3231
handleFetchError(error, message) {
33-
if (!this.cancelSignal.aborted) {
32+
if (!this.fetchCanceled) {
3433
console.error(message, error);
3534
this.setState({ error: error, isLoading: false });
3635
} else {
@@ -278,6 +277,7 @@ TubeMapContainer.propTypes = {
278277
dataOrigin: PropTypes.oneOf(Object.values(dataOriginTypes)).isRequired,
279278
viewTarget: PropTypes.object.isRequired,
280279
visOptions: PropTypes.object.isRequired,
280+
APIInterface: PropTypes.object.isRequired,
281281
};
282282

283283
export default TubeMapContainer;

0 commit comments

Comments
 (0)