Skip to content

Commit

Permalink
RFC83: Make virtual study available for all users on their landing pa…
Browse files Browse the repository at this point in the history
…ges (#4923)

* Render public virtual studies as a separate section

* Use public virtual study metadata to blend it in regular studies
  • Loading branch information
forus authored Jul 18, 2024
1 parent 534daad commit 9ca3198
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 9 deletions.
2 changes: 2 additions & 0 deletions end-to-end-test/local/runtime-config/portal.properties
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ session.service.url=
# session.service.user=user
# session.service.password=pass

session.endpoint.publisher-api-key=SECRETKEY

# disabled tabs, | delimited
# possible values: cancer_types_summary, mutual_exclusivity, plots, mutations, co_expression, enrichments, survival, network, download, bookmark, IGV
disabled_tabs=
Expand Down
171 changes: 171 additions & 0 deletions end-to-end-test/local/specs/virtual-study.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
var assert = require('assert');
var goToUrlAndSetLocalStorage = require('../../shared/specUtils')
.goToUrlAndSetLocalStorage;

const CBIOPORTAL_URL = process.env.CBIOPORTAL_URL.replace(/\/$/, '');
const studyEs0Summary = CBIOPORTAL_URL + '/study/summary?id=study_es_0';

describe('Virtual Study life cycle', function() {
const vsTitle = 'Test VS ' + Date.now();
let link;
let vsId;
const X_PUBLISHER_API_KEY = 'SECRETKEY';

it('Login and navigate to the study_es_0 study summary page', function() {
goToUrlAndSetLocalStorage(studyEs0Summary, true);
});
it('Click Share Virtual Study button', function() {
const studyView = $('.studyView');
const shareVSBtn = studyView.$(
'button[data-tour="action-button-bookmark"]'
);
shareVSBtn.waitForClickable();
shareVSBtn.click();
});
it('Provide the title and save', function() {
const modalDialog = $('.modal-dialog');
modalDialog.waitForDisplayed();
const titleInput = modalDialog.$('input#sniglet');
titleInput.setValue(vsTitle);
const saveBtn = modalDialog.$(
'[data-tour="virtual-study-summary-save-btn"]'
);
saveBtn.click();
modalDialog.$('.text-success').waitForDisplayed();
const linkInput = modalDialog.$('input[type="text"]');
link = linkInput.getValue();
assert.ok(
link.startsWith('http'),
'The value should be link, but was ' + link
);
vsId = link
.split('?')[1]
.split('&')
.map(paramEqValue => paramEqValue.split('='))
.find(([key, value]) => key === 'id')[1];
assert.ok(vsId, 'Virtual Study ID has not to be empty');
});
it('See the VS in My Virtual Studies section on the landing page', function() {
goToUrlAndSetLocalStorage(CBIOPORTAL_URL, true);
const vsSection = $(`//*[text()="${vsTitle}"]/ancestor::ul[1]`);
vsSection.waitForDisplayed();
const sectionTitle = vsSection.$('li label span');
assert.equal(sectionTitle.getText(), 'My Virtual Studies');
});
it('Publish the VS', function() {
const result = browser.executeAsync(
function(cbioUrl, vsId, key, done) {
const url = cbioUrl + '/api/public_virtual_studies/' + vsId;
const headers = new Headers();
headers.append('X-PUBLISHER-API-KEY', key);
fetch(url, {
method: 'POST',
headers: headers,
})
.then(response => {
done({
success: response.ok,
message: 'HTTP Status: ' + response.status,
});
})
.catch(error => {
done({ success: false, message: error.message });
});
},
CBIOPORTAL_URL,
vsId,
X_PUBLISHER_API_KEY
);
assert.ok(result.success, result.message);
});
it('See the VS in Public Virtual Studies section on the landing page', function() {
goToUrlAndSetLocalStorage(CBIOPORTAL_URL, true);
const vsSection = $(`//*[text()="${vsTitle}"]/ancestor::ul[1]`);
vsSection.waitForDisplayed();
const sectionTitle = vsSection.$('li label span');
assert.equal(sectionTitle.getText(), 'Public Virtual Studies');
});
it('Re-publish the VS specifying PubMed ID and type of cancer', function() {
const result = browser.executeAsync(
function(cbioUrl, vsId, key, done) {
const headers = new Headers();
headers.append('X-PUBLISHER-API-KEY', key);
fetch(
cbioUrl +
'/api/public_virtual_studies/' +
vsId +
'?pmid=28783718&typeOfCancerId=aca',
{
method: 'POST',
headers: headers,
}
)
.then(response => {
done({
success: response.ok,
message: 'HTTP Status: ' + response.status,
});
})
.catch(error => {
done({ success: false, message: error.message });
});
},
CBIOPORTAL_URL,
vsId,
X_PUBLISHER_API_KEY
);
assert.ok(result.success, result.message);
});
it('See the VS in the Adrenocortical Adenoma section with PubMed link', function() {
goToUrlAndSetLocalStorage(CBIOPORTAL_URL, true);
const vsRow = $(`//*[text()="${vsTitle}"]/ancestor::li[1]`);
const vsSection = vsRow.parentElement();
vsSection.waitForDisplayed();
const sectionTitle = vsSection.$('li label span');
assert.equal(sectionTitle.getText(), 'Adrenocortical Adenoma');
//has PubMed link
assert.ok(vsRow.$('.fa-book').isExisting());
});
it('Un-publish the VS', function() {
const result = browser.executeAsync(
function(cbioUrl, vsId, key, done) {
const headers = new Headers();
headers.append('X-PUBLISHER-API-KEY', key);
fetch(cbioUrl + '/api/public_virtual_studies/' + vsId, {
method: 'DELETE',
headers: headers,
})
.then(response => {
done({
success: response.ok,
message: 'HTTP Status: ' + response.status,
});
})
.catch(error => {
done({ success: false, message: error.message });
});
},
CBIOPORTAL_URL,
vsId,
X_PUBLISHER_API_KEY
);
assert.ok(result.success, result.message);
});

it('Removing the VS', function() {
goToUrlAndSetLocalStorage(CBIOPORTAL_URL, true);
const vsRow = $(`//*[text()="${vsTitle}"]/ancestor::li[1]`);
vsRow.waitForDisplayed();

const removeBtn = vsRow.$('.fa-trash');
removeBtn.click();
});

it('The VS disappears from the landing page', function() {
goToUrlAndSetLocalStorage(CBIOPORTAL_URL, true);
$('[data-test="cancerTypeListContainer"]').waitForDisplayed();
const vsRowTitle = $(`//*[text()="${vsTitle}"]`);
browser.pause(100);
assert.ok(!vsRowTitle.isExisting());
});
});
16 changes: 16 additions & 0 deletions src/shared/api/session-service/sessionServiceAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default class sessionServiceAPI {
return `${getSessionUrl()}/virtual_study`;
}

getPublicVirtualStudyServiceUrl() {
return getSessionUrl('api/public_virtual_studies');
}

getSessionServiceUrl() {
return `${getSessionUrl()}/main_session`;
}
Expand Down Expand Up @@ -44,6 +48,18 @@ export default class sessionServiceAPI {
);
}

getPublicVirtualStudies(): Promise<Array<VirtualStudy>> {
return (
request
.get(this.getPublicVirtualStudyServiceUrl())
// @ts-ignore: this method comes from caching plugin and isn't in typing
.forceUpdate(true)
.then((res: any) => {
return res.body;
})
);
}

getVirtualStudy(id: string): Promise<VirtualStudy> {
return (
request
Expand Down
2 changes: 2 additions & 0 deletions src/shared/api/session-service/sessionServiceModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface VirtualStudyData {
studies: { id: string; samples: string[] }[];
origin: string[];
studyViewFilter: StudyViewFilter;
typeOfCancerId?: string;
pmid?: string;
}

export type GroupData = Omit<VirtualStudyData, 'studyViewFilter'>;
Expand Down
6 changes: 3 additions & 3 deletions src/shared/api/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,14 @@ export function getGenomeNexusHgvsgUrl(
: `${getServerConfig().genomenexus_website_url}/variant/${hgvsg}`;
}

export function getSessionUrl() {
export function getSessionUrl(path = 'api/session') {
if (getServerConfig() && getServerConfig().hasOwnProperty('apiRoot')) {
// TODO: remove this after switch to AWS. This is a hack to use proxy
// session-service from non apiRoot. We'll have to come up with a better
// solution for auth portals
return buildCBioPortalPageUrl('api/session');
return buildCBioPortalPageUrl(path);
} else {
return buildCBioPortalAPIUrl('api/session');
return buildCBioPortalAPIUrl(path);
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/shared/components/query/CancerStudyTreeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VirtualStudy } from 'shared/api/session-service/sessionServiceModels';

export const CANCER_TYPE_ROOT = 'tissue';
export const VIRTUAL_STUDY_NAME = 'My Virtual Studies';
export const PUBLIC_VIRTUAL_STUDY_NAME = 'Public Virtual Studies';
export const PHYSICAL_STUDY_NAME = 'Studies';

export type CancerTypeWithVisibility = CancerType & {
Expand Down Expand Up @@ -63,6 +64,16 @@ export default class CancerStudyTreeData {
alwaysVisible: true,
};

publicVirtualStudyCategory: CancerTypeWithVisibility = {
id: 'public_virtual_studies_list',
dedicatedColor: '',
name: PUBLIC_VIRTUAL_STUDY_NAME,
parent: CANCER_TYPE_ROOT,
shortName: PUBLIC_VIRTUAL_STUDY_NAME,
cancerTypeId: PUBLIC_VIRTUAL_STUDY_NAME,
alwaysVisible: true,
};

physicalStudyCategory: CancerTypeWithVisibility = {
dedicatedColor: '',
name: PHYSICAL_STUDY_NAME,
Expand All @@ -83,13 +94,15 @@ export default class CancerStudyTreeData {
allStudyTags = [],
priorityStudies = {},
virtualStudies = [],
publicVirtualStudies = [],
maxTreeDepth = 0,
}: {
cancerTypes: CancerTypeWithVisibility[];
studies: CancerStudy[];
allStudyTags: StudyTags[];
priorityStudies?: CategorizedConfigItems;
virtualStudies?: VirtualStudy[];
publicVirtualStudies?: VirtualStudy[];
maxTreeDepth: number;
}) {
let nodes: CancerTreeNode[];
Expand All @@ -99,6 +112,25 @@ export default class CancerStudyTreeData {
// sort by name
cancerTypes = CancerStudyTreeData.sortNodes(cancerTypes);

//map public virtual study to cancer study
const _publicVirtualStudies = publicVirtualStudies.map(
publicVirtualStudy => {
return {
allSampleCount: _.sumBy(
publicVirtualStudy.data.studies,
study => study.samples.length
),
studyId: publicVirtualStudy.id,
name: publicVirtualStudy.data.name,
description: publicVirtualStudy.data.description,
cancerTypeId:
publicVirtualStudy.data.typeOfCancerId ||
PUBLIC_VIRTUAL_STUDY_NAME,
pmid: publicVirtualStudy.data.pmid,
} as CancerStudy;
}
);

//map virtual study to cancer study
const _virtualStudies = virtualStudies
.map(virtualstudy => {
Expand Down Expand Up @@ -140,13 +172,15 @@ export default class CancerStudyTreeData {
}
// add virtual study category, and studies
cancerTypes = [
this.publicVirtualStudyCategory,
this.virtualStudyCategory,
this.physicalStudyCategory,
...this.priorityCategories,
this.rootCancerType,
...cancerTypes,
];
studies = CancerStudyTreeData.sortNodes([
..._publicVirtualStudies,
..._virtualStudies,
...studies,
]);
Expand Down
Loading

0 comments on commit 9ca3198

Please sign in to comment.