mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 06:48:12 +08:00
add autosave
This commit is contained in:
@@ -387,3 +387,82 @@ export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveGenesetsAction = () => async (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const { config, genesets, annotations } = state;
|
||||||
|
|
||||||
|
// bail if gene sets not available, or in readonly mode.
|
||||||
|
const genesetsAreAvailable =
|
||||||
|
config?.parameters?.["annotations_genesets"] ?? false;
|
||||||
|
const genesetsReadonly =
|
||||||
|
config?.parameters?.["annotations_genesets_readonly"] ?? true;
|
||||||
|
if (!genesetsAreAvailable || genesetsReadonly) {
|
||||||
|
// our non-save was completed!
|
||||||
|
dispatch({
|
||||||
|
type: "autosave: genesets complete",
|
||||||
|
lastSavedGenesets: genesets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
JSON data structure is an array of arrays, where the first
|
||||||
|
element is gene set name, remainder are the genes. Eg,
|
||||||
|
|
||||||
|
{
|
||||||
|
"genesets": [
|
||||||
|
[ "gs1", ["TNFRSF4","SUMO3","BRWD1"]],
|
||||||
|
[ "gs2", ["DSCR3", "BRWD1", "BACE2", "SIK1", "C21orf33", "ICOSLG", "SUMO3"]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Order of gene sets and genes is significant
|
||||||
|
*/
|
||||||
|
const gsArr = [];
|
||||||
|
for (const [gsName, gsGenes] of genesets.genesets) {
|
||||||
|
gsArr.push([gsName, Array.from(gsGenes)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations;
|
||||||
|
const queryString =
|
||||||
|
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||||
|
? `?annotation-collection-name=${encodeURIComponent(
|
||||||
|
dataCollectionName
|
||||||
|
)}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`${globals.API.prefix}${globals.API.version}genesets${queryString}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: new Headers({
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}),
|
||||||
|
body: JSON.stringify({
|
||||||
|
genesets: gsArr,
|
||||||
|
}),
|
||||||
|
credentials: "include",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
dispatch({
|
||||||
|
type: "autosave: genesets complete",
|
||||||
|
lastSavedGenesets: genesets,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: "autosave: genesets error",
|
||||||
|
message: `HTTP error ${res.status} - ${res.statusText}`,
|
||||||
|
res,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: "autosave: genesets error",
|
||||||
|
message: error.toString(),
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -52,14 +52,21 @@ async function userInfoFetch(dispatch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function genesetsFetch(dispatch) {
|
async function genesetsFetch(dispatch, config) {
|
||||||
return fetchJson("genesets").then((response) => {
|
if (config?.parameters?.["annotations_genesets"] ?? false) {
|
||||||
const genesets = response?.genesets ?? {};
|
fetchJson("genesets").then((response) => {
|
||||||
|
const genesets = response?.genesets ?? {};
|
||||||
|
dispatch({
|
||||||
|
type: "geneset: initial load",
|
||||||
|
init: genesets,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "geneset: initial load",
|
type: "geneset: initial load",
|
||||||
init: genesets,
|
init: [],
|
||||||
});
|
});
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prefetchEmbeddings(annoMatrix) {
|
function prefetchEmbeddings(annoMatrix) {
|
||||||
@@ -84,9 +91,10 @@ const doInitialDataLoad = () =>
|
|||||||
schemaFetch(dispatch),
|
schemaFetch(dispatch),
|
||||||
userColorsFetchAndLoad(dispatch),
|
userColorsFetchAndLoad(dispatch),
|
||||||
userInfoFetch(dispatch),
|
userInfoFetch(dispatch),
|
||||||
genesetsFetch(dispatch),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
genesetsFetch(dispatch, config);
|
||||||
|
|
||||||
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
|
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
|
||||||
const annoMatrix = new AnnoMatrixLoader(baseDataUrl, schema.schema);
|
const annoMatrix = new AnnoMatrixLoader(baseDataUrl, schema.schema);
|
||||||
const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
|
const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
|
||||||
@@ -253,6 +261,7 @@ export default {
|
|||||||
annotationRenameLabelInCategory: annoActions.annotationRenameLabelInCategory,
|
annotationRenameLabelInCategory: annoActions.annotationRenameLabelInCategory,
|
||||||
annotationLabelCurrentSelection: annoActions.annotationLabelCurrentSelection,
|
annotationLabelCurrentSelection: annoActions.annotationLabelCurrentSelection,
|
||||||
saveObsAnnotationsAction: annoActions.saveObsAnnotationsAction,
|
saveObsAnnotationsAction: annoActions.saveObsAnnotationsAction,
|
||||||
|
saveGenesetsAction: annoActions.saveGenesetsAction,
|
||||||
needToSaveObsAnnotations: annoActions.needToSaveObsAnnotations,
|
needToSaveObsAnnotations: annoActions.needToSaveObsAnnotations,
|
||||||
layoutChoiceAction: embActions.layoutChoiceAction,
|
layoutChoiceAction: embActions.layoutChoiceAction,
|
||||||
setCellSetFromSelection: selnActions.setCellSetFromSelection,
|
setCellSetFromSelection: selnActions.setCellSetFromSelection,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import {
|
|||||||
auth: state.config?.authentication,
|
auth: state.config?.authentication,
|
||||||
userInfo: state.userInfo,
|
userInfo: state.userInfo,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||||
|
writableGenesetsEnabled: !(
|
||||||
|
state.config?.parameters?.["annotations_genesets_readonly"] ?? true
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
class FilenameDialog extends React.Component {
|
class FilenameDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -101,7 +104,7 @@ class FilenameDialog extends React.Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
const { filenameText } = this.state;
|
const { filenameText } = this.state;
|
||||||
|
|
||||||
return writableCategoriesEnabled &&
|
return (writableCategoriesEnabled || writableGenesetsEnabled) &&
|
||||||
annotations.promptForFilename &&
|
annotations.promptForFilename &&
|
||||||
!annotations.dataCollectionNameIsReadOnly &&
|
!annotations.dataCollectionNameIsReadOnly &&
|
||||||
!annotations.dataCollectionName &&
|
!annotations.dataCollectionName &&
|
||||||
|
|||||||
@@ -5,11 +5,18 @@ import FilenameDialog from "./filenameDialog";
|
|||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
saveInProgress: state.autosave?.saveInProgress ?? false,
|
obsAnnotationSaveInProgress:
|
||||||
|
state.autosave?.obsAnnotationSaveInProgress ?? false,
|
||||||
|
genesetSaveInProgress: state.autosave?.genesetSaveInProgress ?? false,
|
||||||
error: state.autosave?.error,
|
error: state.autosave?.error,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||||
|
writableGenesetsEnabled: !(
|
||||||
|
state.config?.parameters?.annotations_genesets_readonly ?? true
|
||||||
|
),
|
||||||
annoMatrix: state.annoMatrix,
|
annoMatrix: state.annoMatrix,
|
||||||
|
genesets: state.genesets,
|
||||||
lastSavedAnnoMatrix: state.autosave?.lastSavedAnnoMatrix,
|
lastSavedAnnoMatrix: state.autosave?.lastSavedAnnoMatrix,
|
||||||
|
lastSavedGenesets: state.autosave?.lastSavedGenesets,
|
||||||
}))
|
}))
|
||||||
class Autosave extends React.Component {
|
class Autosave extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -38,18 +45,40 @@ class Autosave extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick = () => {
|
tick = () => {
|
||||||
const { dispatch, saveInProgress } = this.props;
|
const {
|
||||||
if (this.needToSave() && !saveInProgress) {
|
dispatch,
|
||||||
|
obsAnnotationSaveInProgress,
|
||||||
|
genesetSaveInProgress,
|
||||||
|
} = this.props;
|
||||||
|
if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) {
|
||||||
dispatch(actions.saveObsAnnotationsAction());
|
dispatch(actions.saveObsAnnotationsAction());
|
||||||
}
|
}
|
||||||
|
if (!genesetSaveInProgress && this.needToSaveGenesets()) {
|
||||||
|
dispatch(actions.saveGenesetsAction());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
needToSave = () => {
|
saveInProgress() {
|
||||||
/* return true if we need to save, false if we don't */
|
const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props;
|
||||||
|
return obsAnnotationSaveInProgress || genesetSaveInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
needToSaveObsAnnotations = () => {
|
||||||
|
/* return true if we need to save obs cell labels, false if we don't */
|
||||||
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
|
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
|
||||||
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
|
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
needToSaveGenesets = () => {
|
||||||
|
/* return true if we need to save gene ses, false if we do not */
|
||||||
|
const { genesets, lastSavedGenesets } = this.props;
|
||||||
|
return genesets.initialized && genesets !== lastSavedGenesets;
|
||||||
|
};
|
||||||
|
|
||||||
|
needToSave() {
|
||||||
|
return this.needToSaveGenesets() || this.needToSaveObsAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
statusMessage() {
|
statusMessage() {
|
||||||
const { error } = this.props;
|
const { error } = this.props;
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -59,11 +88,7 @@ class Autosave extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { writableCategoriesEnabled, lastSavedAnnoMatrix } = this.props;
|
||||||
writableCategoriesEnabled,
|
|
||||||
saveInProgress,
|
|
||||||
lastSavedAnnoMatrix,
|
|
||||||
} = this.props;
|
|
||||||
const initialDataLoadComplete = lastSavedAnnoMatrix;
|
const initialDataLoadComplete = lastSavedAnnoMatrix;
|
||||||
|
|
||||||
if (!writableCategoriesEnabled) return null;
|
if (!writableCategoriesEnabled) return null;
|
||||||
@@ -74,7 +99,7 @@ class Autosave extends React.Component {
|
|||||||
data-testclass={
|
data-testclass={
|
||||||
!initialDataLoadComplete
|
!initialDataLoadComplete
|
||||||
? "autosave-init"
|
? "autosave-init"
|
||||||
: this.needToSave() || saveInProgress
|
: this.saveInProgress() || this.needToSave()
|
||||||
? "autosave-incomplete"
|
? "autosave-incomplete"
|
||||||
: "autosave-complete"
|
: "autosave-complete"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user