Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev2 #88

Merged
merged 31 commits into from
Jun 17, 2024
Merged

Dev2 #88

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f03376f
v2 shifting input splitting/pagination logic from fe to be, enable fe…
prabh-t Jun 11, 2024
a785cc3
cleaned up App, removed old statement mgt and updated app flow, requi…
prabh-t Jun 14, 2024
b96be1f
updated result page path
prabh-t Jun 14, 2024
e1cc5c0
constant clean up
prabh-t Jun 14, 2024
f6da194
home and result page clean up, model and logic changes
prabh-t Jun 14, 2024
51b262d
updated protvar service following api changes
prabh-t Jun 14, 2024
80b4bb7
add local store context and provider
prabh-t Jun 14, 2024
e00cd0e
add result history records tracking, sidenav component to list records
prabh-t Jun 14, 2024
cfb3125
pass result type to result page
prabh-t Jun 17, 2024
5e84eb9
updated sidebar with storage listener
prabh-t Jun 17, 2024
6f38289
added event dispatcher when local storage updated
prabh-t Jun 17, 2024
ec26239
removed downloadCount prop, used storage context where needed instead
prabh-t Jun 17, 2024
7b5aa6d
download local storage refactoring
prabh-t Jun 17, 2024
faf2425
added new ResultType type to distinguish between search and protein r…
prabh-t Jun 17, 2024
b49981e
added condition for api endpoint to use based on result type
prabh-t Jun 17, 2024
f8cc1fb
moved result record into separate file
prabh-t Jun 17, 2024
f6b2b65
use storage context in sign up component
prabh-t Jun 17, 2024
609b983
added url in result record
prabh-t Jun 17, 2024
da6a4a6
moved loading check into subcomponent, correct url, now support both …
prabh-t Jun 17, 2024
daa5070
loading logic changed
prabh-t Jun 17, 2024
d536a40
used event listener to listen to updates, use record url, sort records
prabh-t Jun 17, 2024
cad432c
use location pathname, removed unused props
prabh-t Jun 17, 2024
72e76cc
fixed unique key error
prabh-t Jun 17, 2024
cf77367
added custom event dispatch for local record change and handle
prabh-t Jun 17, 2024
e5cc53c
fixed isoform group expand issue and ref allele
prabh-t Jun 17, 2024
b7e3abb
reviewed and updated download page
prabh-t Jun 17, 2024
538ab20
removed unused function
prabh-t Jun 17, 2024
887da2e
create a paged response from a single mapping response
prabh-t Jun 17, 2024
869bda8
fixed query page
prabh-t Jun 17, 2024
145971c
scroll auto
prabh-t Jun 17, 2024
8c35d6f
text change and clean up
prabh-t Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/constants/BrowserPaths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const ABOUT = "/about"
export const CONTACT = "/contact"
export const SEARCH = "/results"
export const RESULT = "/result" //[/:id]
export const QUERY = "/query"
export const HOME = "/"
export const API_ERROR = "/error"
Expand Down
13 changes: 9 additions & 4 deletions src/constants/const.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
export const API_URL = process.env.REACT_APP_API_BASE_URL;
export const NO_OF_ITEMS_PER_PAGE = 25;
export const MAX_IN_PLACE_DOWNLOAD_WITHOUT_EMAIL = 25;
export const PAGE_SIZES = [25, 50, 100]
export const PAGE_SIZE = 25
export const G2P_MAPPING_URI = `${API_URL}/mappings`;
export const DOWNLOAD_URI = `${API_URL}/download/stream`;
export const EMAIL_URI = `${API_URL}/email/process`;
export const API_HEADERS = { "Content-Type": "application/json", Accept: "*" };
export const DEFAULT_HEADERS = {"Content-Type": "application/json", Accept: "*"};

export const CONTENT_TEXT = {"Content-Type": "text/plain"}
export const CONTENT_MULTIPART = {"Content-Type": "multipart/form-data"}
export const DOWNLOAD_STATUS=`${API_URL}/download/status`;
export const LOCAL_DOWNLOADS='PV_downloads';
export const LOCAL_RESULTS='PV_results';
export const DISMISS_BANNER = 'PV_banner';

// TODO resubscribe option - clears the localData
export const SUBSCRIPTION_STATUS = 'PV_subscribed';
export const TITLE='EMBL-EBI ProtVar - Contextualising human missense variation'
export const PV_FTP = 'https://ftp.ebi.ac.uk/pub/databases/ProtVar'
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './styles/index.scss';
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import './styles/index.scss';
import reportWebVitals from './reportWebVitals';

import App from './ui/App';
Expand Down
43 changes: 43 additions & 0 deletions src/provider/LocalStorageContextProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { createContext, useContext, ReactNode } from 'react';

interface LocalStorageContextProps {
getValue: <T>(key: string) => T | null;
setValue: <T>(key: string, value: T) => void;
deleteValue: (key: string) => void;
}

const LocalStorageContext = createContext<LocalStorageContextProps | undefined>(undefined);

export const useLocalStorageContext = () => {
const context = useContext(LocalStorageContext);
if (!context) {
throw new Error('useLocalStorageContext must be used within a LocalStorageProvider');
}
return context;
};

export const LOCAL_STORAGE_SET = "localStore"

export const LocalStorageProvider: React.FC<{ children: ReactNode }> = ({ children }) => {

const getValue = <T,>(key: string): T | null => {
const storedValue = localStorage.getItem(key);
return storedValue ? JSON.parse(storedValue) : null;
};

const setValue = <T,>(key: string, value: T): void => {
localStorage.setItem(key, JSON.stringify(value));
const event = new CustomEvent(LOCAL_STORAGE_SET, { detail: key });
window.dispatchEvent(event)
};

const deleteValue = (key: string): void => {
localStorage.removeItem(key);
};

return (
<LocalStorageContext.Provider value={{ getValue, setValue, deleteValue }}>
{children}
</LocalStorageContext.Provider>
);
};
186 changes: 128 additions & 58 deletions src/services/ProtVarService.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,158 @@
import axios, {AxiosResponse} from 'axios';
import { setupCache } from 'axios-cache-interceptor/dist/index.bundle';
import {API_HEADERS, API_URL, DOWNLOAD_STATUS, G2P_MAPPING_URI} from "../constants/const";
import {setupCache} from 'axios-cache-interceptor/dist/index.bundle';
import {
API_URL,
CONTENT_MULTIPART,
CONTENT_TEXT,
DEFAULT_HEADERS,
DOWNLOAD_STATUS,
G2P_MAPPING_URI
} from "../constants/const";
import {FunctionalResponse} from "../types/FunctionalResponse";
import {PopulationObservationResponse} from "../types/PopulationObservationResponse";
import {ProteinStructureResponse} from "../types/ProteinStructureResponse";
import MappingResponse from "../types/MappingResponse";
import {DownloadResponse} from "../types/DownloadResponse";
import {DownloadRecord} from "../types/DownloadRecord";
import {IDResponse, PagedMappingResponse, ResultType} from "../types/PagedMappingResponse";


const instance = axios.create({
baseURL: API_URL
baseURL: API_URL
});

const api = setupCache(instance, {})

// Coordinate Mapping
// POST /mappings
export function mappings(inputArr: string[], assembly?: string) {
return api.post<any, string[], AxiosResponse<MappingResponse>>(
G2P_MAPPING_URI,
inputArr,
{
params : { assembly },
headers: API_HEADERS,
}
);
return api.post<any, string[], AxiosResponse<MappingResponse>>(
G2P_MAPPING_URI,
inputArr,
{
params: {assembly},
headers: DEFAULT_HEADERS,
}
);
}

// POST /mapping/input
// IN: text
// OUT: PagedMappingResponse
// See getResult
export function submitInput(text: string, assembly?: string) {
return api.post<PagedMappingResponse>(
`${API_URL}/mapping/input`, text,
{
params: {assembly}, // idOnly defaults to false i.e. PagedMappingResponse is returned
headers: CONTENT_TEXT
}
);
}

export function downloadFileInput(file: File, assembly: string, email: string, jobName: string, functional: boolean, population: boolean, structure: boolean) {
const formData = new FormData();
formData.append('file', file);
return api.post<any, FormData, AxiosResponse<DownloadResponse>>(
`${API_URL}/download/fileInput`,
formData,
{
params : { email, jobName, function: functional, population, structure, assembly },
headers: {
'Content-Type': 'multipart/form-data'
},
}
);
// POST /mapping/input
// IN: text
// OUT: IDResponse
export function submitInputText(text: string, assembly?: string, idOnly: boolean = true) {
return api.post<IDResponse>(
`${API_URL}/mapping/input`, text,
{
params: {assembly, idOnly},
headers: CONTENT_TEXT
}
);
}

export function downloadTextInput(inputArr: string[], assembly: string, email: string, jobName: string, functional: boolean, population: boolean, structure: boolean) {
return api.post<any, string[], AxiosResponse<DownloadResponse>>(
`${API_URL}/download/textInput`,
inputArr,
{
params : { email, jobName, function: functional, population, structure, assembly },
headers: {
'Content-Type': 'application/json',
Accept: '*'
},
}
);
// POST /mapping/input
// IN: file
// OUT: IDResponse
export function submitInputFile(file: File, assembly?: string, idOnly: boolean = true) {
const formData = new FormData();
formData.append('file', file);
return api.post<any, FormData, AxiosResponse<IDResponse>>(
`${API_URL}/mapping/input`,
formData,
{
params: {assembly, idOnly},
headers: CONTENT_MULTIPART,
}
);
}

// GET /mapping/input/{id}
// IN: id
// OUT: PagedMappingResponse
export function getResult(type: ResultType, id: string, page?: number, pageSize?: number, assembly: string|null = null) {
let url = ''
let params = {}

if (type === ResultType.SEARCH) {
url = `${API_URL}/mapping/input/${id}`
params = {page, pageSize, assembly}
} else {
url = `${API_URL}/mapping/protein/${id}`
params = {page, pageSize}
}

return api.get<PagedMappingResponse>(
url,
{
params: params,
headers: DEFAULT_HEADERS,
}
);
}

// Annotation
export function getFunctionalData(url: string) {
return api.get<FunctionalResponse>(url).then(
response => {
if (response.data.interactions && response.data.interactions.length > 1) {
response.data.interactions.sort((a, b) => b.pdockq - a.pdockq);
}
return response;
}
);
return api.get<FunctionalResponse>(url).then(
response => {
if (response.data.interactions && response.data.interactions.length > 1) {
response.data.interactions.sort((a, b) => b.pdockq - a.pdockq);
}
return response;
}
);
}

export function getPopulationData(url: string) {
return api.get<PopulationObservationResponse>(url);
return api.get<PopulationObservationResponse>(url);
}

export function getStructureData(url: string) {
return api.get<ProteinStructureResponse>(url);
return api.get<ProteinStructureResponse>(url);
}

// Download
export function downloadFileInput(file: File, assembly: string, email: string, jobName: string, functional: boolean, population: boolean, structure: boolean) {
const formData = new FormData();
formData.append('file', file);
return api.post<any, FormData, AxiosResponse<DownloadRecord>>(
`${API_URL}/download/fileInput`,
formData,
{
params: {email, jobName, function: functional, population, structure, assembly},
headers: CONTENT_MULTIPART,
}
);
}

export function downloadTextInput(inputArr: string[], assembly: string, email: string, jobName: string, functional: boolean, population: boolean, structure: boolean) {
return api.post<any, string[], AxiosResponse<DownloadRecord>>(
`${API_URL}/download/textInput`,
inputArr,
{
params: {email, jobName, function: functional, population, structure, assembly},
headers: DEFAULT_HEADERS,
}
);
}

export function getDownloadStatus(ids: string[]) {
return api.post(
DOWNLOAD_STATUS,
ids,
{
headers: {
'Content-Type': 'application/json',
Accept: '*'
},
}
);
}
return api.post(
DOWNLOAD_STATUS,
ids,
{
headers: DEFAULT_HEADERS,
}
);
}
31 changes: 31 additions & 0 deletions src/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,34 @@
.trash-btn:hover {
cursor: pointer;
}

.result-op-btn {
float: right;
font-size: 10px;
font-weight: bolder;
color: black;
padding: 4px;
border-radius: 20%;
border-bottom-color: lightgrey;
}
.result-op-btn:hover {
font-weight: bolder;
background-color: #e7e7e7;
cursor: pointer;
}

.download-count {
float: right;
min-width: 24px;
text-align: center;
display: inline-block;
//padding: 0.1rem 0.2rem;
//margin-right: 1rem;
//margin-bottom: 0.3rem;
border: none;
border-radius: 0.75rem;
font-size: 12px;
font-weight: bold;
color: white;
background-color: #00709b;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

export interface DownloadResponse {
export interface DownloadRecord {
inputType: string
requested: Date
downloadId: string
Expand Down
12 changes: 12 additions & 0 deletions src/types/FormData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@ export const initialFormData = {
userInputs: [],
file: null,
assembly: DEFAULT_ASSEMBLY
}

export interface Form {
text: string
file: File | null
assembly: Assembly
}

export const initialForm = {
text: '',
file: null,
assembly: DEFAULT_ASSEMBLY
}
4 changes: 2 additions & 2 deletions src/types/MappingResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export interface GenomeProteinMapping {
genes: Array<Gene>;
//input: string;
}
interface Gene {
export interface Gene {
ensg: string;
reverseStrand: boolean;
geneName: string;
Expand All @@ -94,7 +94,7 @@ interface Gene {
caddScore: number;
}
// TODO clean up unused commented properties below
interface IsoFormMapping {
export interface IsoFormMapping {
accession: string;
canonical: boolean;
canonicalAccession: string;
Expand Down
Loading
Loading