diff --git a/web/src/components/JoinProjectModal/JoinProjectModal.js b/web/src/components/JoinProjectModal/JoinProjectModal.tsx similarity index 70% rename from web/src/components/JoinProjectModal/JoinProjectModal.js rename to web/src/components/JoinProjectModal/JoinProjectModal.tsx index 8c5879290..535cda7e5 100644 --- a/web/src/components/JoinProjectModal/JoinProjectModal.js +++ b/web/src/components/JoinProjectModal/JoinProjectModal.tsx @@ -39,23 +39,24 @@ function JoinProjectForm({ - - - + + {/* @ts-ignore */} + + ) } -function ProjectJoinFollowUp({ onDone, peerFound, checkDone }) { +function ProjectJoinFollowUp({ onDone, checkDone }) { return (
- - {!peerFound && ( + - )}
- -
- {peerFound - ? 'Peers in this project were found so now you will get synced with them.' - : `If a peer is found, you are likely to be able to immediately begin to access the project, although a short sync period in the queue may be required before you can access it.`} -
-
+ +
+ If a peer is found, you are likely to be able to immediately begin to + access the project, although a short sync period in the queue may be + required before you can access it. +
+
) @@ -95,7 +88,6 @@ export default function JoinProjectModal({ setProjectSecret('') setValidatingSecret(false) setInvalidText('') - setPeerFound(false) setCheckDone(false) } const onValidate = async () => { @@ -105,12 +97,12 @@ export default function JoinProjectModal({ } setValidatingSecret(true) try { - const peerWasFound = await onJoinProject(projectSecret) - setPeerFound(peerWasFound) + await onJoinProject(projectSecret) setCheckDone(true) setValidatingSecret(false) } catch (e) { console.log(e) + // TODO: add better detail here setInvalidText('There was an error while joining project: ' + e.message) } } @@ -120,7 +112,6 @@ export default function JoinProjectModal({ } const [checkDone, setCheckDone] = useState(false) - const [peerFound, setPeerFound] = useState(true) const [projectSecret, setProjectSecret] = useState('') const [invalidText, setInvalidText] = useState('') @@ -132,7 +123,10 @@ export default function JoinProjectModal({ setProjectSecret(val) if (!val) { setInvalidText('') - } else if (val.split(' ').length !== 5 || !val.split(' ').every(word => word.length)) { + } else if ( + val.split(' ').length !== 5 || + !val.split(' ').every((word) => word.length) + ) { setInvalidText('Secret must be 5 words.') } } @@ -144,14 +138,9 @@ export default function JoinProjectModal({ onClose={onDone} className="join-project-modal-wrapper" > - + > - deactivateApp: (appId: string, cellId: string) => Promise + deactivateProject: (appId: string, cellId: string) => Promise }) { const [expanded, setExpanded] = useState(false) const [passphrases, setPassphrases] = useState({}) @@ -77,7 +77,7 @@ function PendingProjects({ }, [JSON.stringify(pendingProjects)]) const cancelProjectJoin = async (appId: string, cellId: string) => { - await deactivateApp(appId, cellId) + await deactivateProject(appId, cellId) // remove this project from pendingProjects setPendingProjects((pendingProjects: string[]) => { return pendingProjects.filter((c) => c !== cellId) diff --git a/web/src/migrating/import/import.ts b/web/src/migrating/import/import.ts index 726a93dd1..d9d28ab8d 100644 --- a/web/src/migrating/import/import.ts +++ b/web/src/migrating/import/import.ts @@ -3,19 +3,18 @@ import { cellIdFromString } from '../../utils' import { RootState } from '../../redux/reducer' import { AllProjectsDataExport } from '../export' import { createWhoami } from '../../redux/persistent/profiles/who-am-i/actions' -import { installProjectApp } from '../../projects/installProjectApp' +import { installProject } from '../../projects/installProject' import { joinProjectCellId } from '../../redux/persistent/cells/actions' -import { createProfilesZomeApi, createProjectsZomeApi } from './zomeApiCreators' -import { installProjectAppAndImport } from './installProjectAppAndImport' +import { createProfilesZomeApi } from './zomeApiCreators' +import { installProjectAndImport } from './installProjectAndImport' import ProfilesZomeApi from '../../api/profilesApi' -import ProjectsZomeApi from '../../api/projectsApi' +import { internalJoinProject } from '../../projects/joinProject' export async function internalImportProjectsData( // dependencies profilesZomeApi: ProfilesZomeApi, - projectsZomeApi: ProjectsZomeApi, - _installProjectAppAndImport: typeof installProjectAppAndImport, - _installProjectApp: typeof installProjectApp, + _installProjectAndImport: typeof installProjectAndImport, + _installProject: typeof installProject, store: any, // main input data and callbacks migrationData: string, @@ -60,12 +59,11 @@ export async function internalImportProjectsData( // one completes for await (let projectData of projectsToMigrate) { const passphrase = projectData.projectMeta.passphrase - await _installProjectAppAndImport( + await _installProjectAndImport( myAgentPubKey, projectData, passphrase, store.dispatch, - projectsZomeApi ) stepsSoFar++ onStep(stepsSoFar, totalSteps) @@ -74,8 +72,7 @@ export async function internalImportProjectsData( // join each project that has already been migrated by a peer for await (let projectData of migratedProjectsToJoin) { const passphrase = projectData.projectMeta.passphrase - const [cellIdString, _, __] = await _installProjectApp(passphrase) - store.dispatch(joinProjectCellId(cellIdString)) + await internalJoinProject(passphrase, store.dispatch, _installProject) stepsSoFar++ onStep(stepsSoFar, totalSteps) } @@ -88,12 +85,10 @@ export default async function importProjectsData( ) { const appWebsocket = await getAppWs() const profilesZomeApi = createProfilesZomeApi(appWebsocket) - const projectsZomeApi = createProjectsZomeApi(appWebsocket) return internalImportProjectsData( profilesZomeApi, - projectsZomeApi, - installProjectAppAndImport, - installProjectApp, + installProjectAndImport, + installProject, store, migrationData, onStep diff --git a/web/src/migrating/import/installProjectAppAndImport.ts b/web/src/migrating/import/installProjectAndImport.ts similarity index 50% rename from web/src/migrating/import/installProjectAppAndImport.ts rename to web/src/migrating/import/installProjectAndImport.ts index a52e7c727..525035d2a 100644 --- a/web/src/migrating/import/installProjectAppAndImport.ts +++ b/web/src/migrating/import/installProjectAndImport.ts @@ -1,31 +1,30 @@ import ProjectsZomeApi from '../../api/projectsApi' -import { installProjectApp } from '../../projects/installProjectApp' -import { setMember } from '../../redux/persistent/projects/members/actions' -import { simpleCreateProjectMeta } from '../../redux/persistent/projects/project-meta/actions' +import { finalizeCreateProject } from '../../projects/createProject' +import { getAppWs } from '../../hcWebsockets' +import { installProject } from '../../projects/installProject' import { AgentPubKeyB64 } from '../../types/shared' -import { cellIdFromString } from '../../utils' import { ProjectExportDataV1 } from '../export' import { cloneProjectMeta } from './cloneFunctions' import { createActionHashMapAndImportProjectData } from './createActionHashMapAndImportProjectData' -export async function internalInstallProjectAppAndImport( +export async function internalInstallProjectAndImport( agentAddress: AgentPubKeyB64, projectData: ProjectExportDataV1, passphrase: string, dispatch: any, - _installProjectApp: typeof installProjectApp, - _importProjectData: typeof createActionHashMapAndImportProjectData, + iInstallProject: typeof installProject, + iCreateActionHashMapAndImportProjectData: typeof createActionHashMapAndImportProjectData, + iFinalizeCreateProject: typeof finalizeCreateProject, projectsZomeApi: ProjectsZomeApi ) { // first step is to install the dna - const [projectsCellIdString] = await _installProjectApp(passphrase) - const cellId = cellIdFromString(projectsCellIdString) + const [cellIdString] = await iInstallProject(passphrase) // next step is to import the bulk of the data into that project - const oldToNewAddressMaps = await _importProjectData( + const oldToNewAddressMaps = await iCreateActionHashMapAndImportProjectData( projectData, - projectsCellIdString, + cellIdString, dispatch ) @@ -39,32 +38,31 @@ export async function internalInstallProjectAppAndImport( passphrase )(projectData.projectMeta) delete projectMeta.actionHash - const simpleCreatedProjectMeta = await projectsZomeApi.projectMeta.simpleCreateProjectMeta( - cellId, - projectMeta - ) - dispatch( - simpleCreateProjectMeta(projectsCellIdString, simpleCreatedProjectMeta) + await iFinalizeCreateProject( + cellIdString, + projectMeta, + agentAddress, + dispatch, + projectsZomeApi ) - // this registers the agent to redux as a member of the project - dispatch(setMember(projectsCellIdString, { agentPubKey: agentAddress })) - } -export async function installProjectAppAndImport( +export async function installProjectAndImport( agentAddress: AgentPubKeyB64, projectData: ProjectExportDataV1, passphrase: string, dispatch: any, - projectsZomeApi: ProjectsZomeApi ) { - internalInstallProjectAppAndImport( + const appWebsocket = await getAppWs() + const projectsZomeApi = new ProjectsZomeApi(appWebsocket) + return internalInstallProjectAndImport( agentAddress, projectData, passphrase, dispatch, - installProjectApp, + installProject, createActionHashMapAndImportProjectData, + finalizeCreateProject, projectsZomeApi ) } diff --git a/web/src/projects/createProject.ts b/web/src/projects/createProject.ts new file mode 100644 index 000000000..30f475e20 --- /dev/null +++ b/web/src/projects/createProject.ts @@ -0,0 +1,65 @@ +import { AgentPubKeyB64 } from '@holochain/client' +import ProjectsZomeApi from '../api/projectsApi' +import { setMember } from '../redux/persistent/projects/members/actions' +import { simpleCreateProjectMeta } from '../redux/persistent/projects/project-meta/actions' +import { ProjectMeta } from '../types' +import { cellIdFromString } from '../utils' +import { installProject } from './installProject' +import { CellIdString } from '../types/shared' + +export async function finalizeCreateProject( + cellIdString: CellIdString, + projectMeta: ProjectMeta, + agentAddress: AgentPubKeyB64, + dispatch: any, + projectsZomeApi: ProjectsZomeApi +) { + const cellId = cellIdFromString(cellIdString) + const createdProjectMeta = await projectsZomeApi.projectMeta.simpleCreateProjectMeta( + cellId, + projectMeta + ) + dispatch(simpleCreateProjectMeta(cellIdString, createdProjectMeta)) + // because we are acting optimistically, + // we will directly set ourselves as a member of this cell + dispatch(setMember(cellIdString, { agentPubKey: agentAddress })) +} + +export async function internalCreateProject( + passphrase: string, + projectMeta: ProjectMeta, + agentAddress: AgentPubKeyB64, + dispatch: any, + iInstallProject: typeof installProject, + projectsZomeApi: ProjectsZomeApi +) { + const startTime = Date.now() + const [cellIdString] = await iInstallProject(passphrase) + await finalizeCreateProject( + cellIdString, + projectMeta, + agentAddress, + dispatch, + projectsZomeApi + ) + const endTime = Date.now() + console.log('duration in MS over createProject ', endTime - startTime) + return cellIdString +} + +export async function createProject( + passphrase: string, + projectMeta: ProjectMeta, + agentAddress: AgentPubKeyB64, + dispatch: any, + projectsZomeApi: ProjectsZomeApi +) { + return internalCreateProject( + passphrase, + projectMeta, + agentAddress, + dispatch, + installProject, + projectsZomeApi + ) +} diff --git a/web/src/projects/deactivateProject.ts b/web/src/projects/deactivateProject.ts new file mode 100644 index 000000000..1f756fa9b --- /dev/null +++ b/web/src/projects/deactivateProject.ts @@ -0,0 +1,17 @@ +import { AdminWebsocket } from '@holochain/client' +import { removeProjectCellId } from '../redux/persistent/cells/actions' +import { CellIdString } from '../types/shared' + +export async function deactivateProject( + appId: string, + cellId: CellIdString, + dispatch: any, + adminWs: AdminWebsocket +) { + // deactivate it in holochain + await adminWs.disableApp({ + installed_app_id: appId, + }) + // remove it from our redux state + dispatch(removeProjectCellId(cellId)) +} diff --git a/web/src/projects/installProject.ts b/web/src/projects/installProject.ts new file mode 100644 index 000000000..06e77e4c7 --- /dev/null +++ b/web/src/projects/installProject.ts @@ -0,0 +1,60 @@ +import { AdminWebsocket, CellId, CellType } from '@holochain/client' +import { getAdminWs, getAgentPubKey } from '../hcWebsockets' +import { PROJECT_APP_PREFIX } from '../holochainConfig' +import { passphraseToUid } from '../secrets' +import { CellIdString } from '../types/shared' +import { cellIdToString } from '../utils' + +export async function internalInstallProject( + passphrase: string, + adminWs: AdminWebsocket, +): Promise<[CellIdString, CellId, string]> { + const uid = passphraseToUid(passphrase) + // add a bit of randomness so that + // the same passphrase can be tried multiple different times + // without conflicting + // in order to eventually find their peers + // note that this will leave a graveyard of deactivated apps for attempted + // joins + const installed_app_id = `${PROJECT_APP_PREFIX}-${Math.random() + .toString() + .slice(-6)}-${uid}` + const agent_key = getAgentPubKey() + if (!agent_key) { + throw new Error( + 'Cannot install a new project because no AgentPubKey is known locally' + ) + } + // the dna hash HAS to act deterministically + // in order for the 'joining' of Projects to work + const happPath = window.require + ? await window.require('electron').ipcRenderer.invoke('getProjectsPath') + : './happ/workdir/projects/projects.happ' + // INSTALL + const installedApp = await adminWs.installApp({ + agent_key, + installed_app_id, + // what to do about the membrane_proof? + membrane_proofs: {}, + path: happPath, + network_seed: uid, + }) + const cellInfo = Object.values(installedApp.cell_info)[0][0] + const cellId = + CellType.Provisioned in cellInfo + ? cellInfo[CellType.Provisioned].cell_id + : null + const cellIdString = cellIdToString(cellId) + await adminWs.enableApp({ installed_app_id }) + + //authorize zome calls for the new cell + await adminWs.authorizeSigningCredentials(cellId) + return [cellIdString, cellId, installed_app_id] +} + +export async function installProject( + passphrase: string +): Promise<[CellIdString, CellId, string]> { + const adminWs = await getAdminWs() + return internalInstallProject(passphrase, adminWs) +} diff --git a/web/src/projects/installProjectApp.ts b/web/src/projects/installProjectApp.ts deleted file mode 100644 index b597a7554..000000000 --- a/web/src/projects/installProjectApp.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { CellId, CellType } from "@holochain/client" -import { getAdminWs, getAgentPubKey } from "../hcWebsockets" -import { PROJECT_APP_PREFIX } from "../holochainConfig" -import { passphraseToUid } from "../secrets" -import { CellIdString } from "../types/shared" -import { cellIdToString } from "../utils" - -export async function installProjectApp( - passphrase: string - ): Promise<[CellIdString, CellId, string]> { - const uid = passphraseToUid(passphrase) - // add a bit of randomness so that - // the same passphrase can be tried multiple different times - // without conflicting - // in order to eventually find their peers - // note that this will leave a graveyard of deactivated apps for attempted - // joins - const installed_app_id = `${PROJECT_APP_PREFIX}-${Math.random() - .toString() - .slice(-6)}-${uid}` - const adminWs = await getAdminWs() - const agent_key = getAgentPubKey() - if (!agent_key) { - throw new Error( - 'Cannot install a new project because no AgentPubKey is known locally' - ) - } - // the dna hash HAS to act deterministically - // in order for the 'joining' of Projects to work - const happPath = window.require - ? await window.require('electron').ipcRenderer.invoke('getProjectsPath') - : './happ/workdir/projects/projects.happ' - console.log(happPath) - // INSTALL - const installedApp = await adminWs.installApp({ - agent_key, - installed_app_id, - // what to do about the membrane_proof? - membrane_proofs: {}, - path: happPath, - network_seed: uid, - }) - const cellInfo = Object.values(installedApp.cell_info)[0][0] - const cellId = (CellType.Provisioned in cellInfo) ? cellInfo[CellType.Provisioned].cell_id : null - const cellIdString = cellIdToString(cellId) - await adminWs.enableApp({ installed_app_id }) - - //authorize zome calls for the new cell - await adminWs.authorizeSigningCredentials(cellId) - return [cellIdString, cellId, installed_app_id] - } \ No newline at end of file diff --git a/web/src/projects/joinProject.ts b/web/src/projects/joinProject.ts new file mode 100644 index 000000000..9d9341f52 --- /dev/null +++ b/web/src/projects/joinProject.ts @@ -0,0 +1,47 @@ +import { AppWebsocket, CellId } from '@holochain/client' +import { installProject } from './installProject' +import { PROJECTS_ZOME_NAME } from '../holochainConfig' +import { getAgentPubKey } from '../hcWebsockets' +import { joinProjectCellId } from '../redux/persistent/cells/actions' +import { CellIdString } from '../types/shared' + +export async function internalJoinProject( + passphrase: string, + dispatch: any, + iInstallProject: typeof installProject +) { + const [cellIdString, _cellId, _installedAppId] = await iInstallProject( + passphrase + ) + // this will trigger the fetching of project meta + // checks and other things + dispatch(joinProjectCellId(cellIdString)) + return cellIdString +} + +export async function joinProject( + passphrase: string, + dispatch: any +): Promise { + return internalJoinProject(passphrase, dispatch, installProject) +} + +export function triggerJoinSignal(cellId: CellId, appWs: AppWebsocket) { + // trigger a side effect... + // this will let other project members know you're here + // without 'blocking' the thread or the UX + appWs + .callZome( + { + cap_secret: null, + cell_id: cellId, + zome_name: PROJECTS_ZOME_NAME, + fn_name: 'init_signal', + payload: null, + provenance: getAgentPubKey(), // FIXME: this will need correcting after holochain changes this + }, + 50000 + ) + .then(() => console.log('succesfully triggered init_signal')) + .catch((e) => console.error('failed while triggering init_signal: ', e)) +} diff --git a/web/src/routes/Dashboard/Dashboard.component.tsx b/web/src/routes/Dashboard/Dashboard.component.tsx index 3575afe12..12a633440 100644 --- a/web/src/routes/Dashboard/Dashboard.component.tsx +++ b/web/src/routes/Dashboard/Dashboard.component.tsx @@ -44,9 +44,9 @@ export type DashboardDispatchProps = { cellIdString: CellIdString ) => Promise fetchEntryPointDetails: (cellIdString: CellIdString) => Promise - joinProject: (passphrase: string) => Promise - deactivateApp: (appId: string, cellId: CellIdString) => Promise - importProject: ( + joinProject: (passphrase: string) => Promise + deactivateProject: (appId: string, cellId: CellIdString) => Promise + installProjectAndImport: ( agentAddress: AgentPubKeyB64, projectData: any, passphrase: string @@ -57,7 +57,7 @@ export type DashboardDispatchProps = { export type DashboardProps = DashboardStateProps & DashboardDispatchProps const Dashboard: React.FC = ({ - deactivateApp, + deactivateProject, agentAddress, cells, projects, @@ -68,7 +68,7 @@ const Dashboard: React.FC = ({ updateProjectMeta, createProject, joinProject, - importProject, + installProjectAndImport, setShowInviteMembersModal, }) => { const { setShowUpdateModal } = useContext(UpdateModalContext) @@ -114,7 +114,7 @@ const Dashboard: React.FC = ({ const onJoinProject = (passphrase: string) => joinProject(passphrase) const onImportProject = (projectData: any, passphrase: string) => - importProject(agentAddress, projectData, passphrase) + installProjectAndImport(agentAddress, projectData, passphrase) const setSortBy = (sortBy) => () => { setSelectedSort(sortBy) @@ -230,7 +230,7 @@ const Dashboard: React.FC = ({ pendingProjects={pendingProjects} fetchProjectMeta={fetchProjectMeta} setPendingProjects={setPendingProjects} - deactivateApp={deactivateApp} + deactivateProject={deactivateProject} /> )} {!hasFetchedForAllProjects && diff --git a/web/src/routes/Dashboard/Dashboard.connector.ts b/web/src/routes/Dashboard/Dashboard.connector.ts index fcd92dc3d..4a4ffeb67 100644 --- a/web/src/routes/Dashboard/Dashboard.connector.ts +++ b/web/src/routes/Dashboard/Dashboard.connector.ts @@ -1,25 +1,18 @@ import { connect } from 'react-redux' -import { PROJECTS_ZOME_NAME } from '../../holochainConfig' -import { getAdminWs, getAppWs, getAgentPubKey } from '../../hcWebsockets' +import { getAdminWs, getAppWs } from '../../hcWebsockets' import { fetchEntryPointDetails } from '../../redux/persistent/projects/entry-points/actions' import { fetchMembers, - setMember, } from '../../redux/persistent/projects/members/actions' import { - simpleCreateProjectMeta, fetchProjectMeta, updateProjectMeta, } from '../../redux/persistent/projects/project-meta/actions' import selectEntryPoints from '../../redux/persistent/projects/entry-points/select' import { setActiveProject } from '../../redux/ephemeral/active-project/actions' -import { - joinProjectCellId, - removeProjectCellId, -} from '../../redux/persistent/cells/actions' -import { installProjectAppAndImport } from '../../migrating/import/import' +import { installProjectAndImport } from '../../migrating/import/installProjectAndImport' import { openInviteMembersModal } from '../../redux/ephemeral/invite-members-modal/actions' import ProjectsZomeApi from '../../api/projectsApi' import { cellIdFromString } from '../../utils' @@ -31,77 +24,9 @@ import Dashboard, { } from './Dashboard.component' import { LayeringAlgorithm, ProjectMeta } from '../../types' import selectProjectMembersPresent from '../../redux/persistent/projects/realtime-info-signal/select' -import { installProjectApp } from '../../projects/installProjectApp' - -async function createProject( - passphrase: string, - projectMeta: ProjectMeta, - agentAddress: AgentPubKeyB64, - dispatch: any -) { - const [cellIdString] = await installProjectApp(passphrase) - const cellId = cellIdFromString(cellIdString) - // because we are acting optimistically, - // we will directly set ourselves as a member of this cell - const appWebsocket = await getAppWs() - const projectsZomeApi = new ProjectsZomeApi(appWebsocket) - await dispatch(setMember(cellIdString, { agentPubKey: agentAddress })) - const b1 = Date.now() - const simpleCreatedProjectMeta = await projectsZomeApi.projectMeta.simpleCreateProjectMeta( - cellId, - projectMeta - ) - await dispatch( - simpleCreateProjectMeta(cellIdString, simpleCreatedProjectMeta) - ) - const b2 = Date.now() - console.log('duration in MS over createProjectMeta ', b2 - b1) - return cellIdString -} - -async function joinProject(passphrase: string, dispatch): Promise { - // joinProject - // join a DNA - // then try to find a peer - // either way, just push the project into the 'queue' - const [cellIdString, cellId, installedAppId] = await installProjectApp( - passphrase - ) - const appWs = await getAppWs() - // trigger a side effect... - // this will let other project members know you're here - // without 'blocking' the thread or the UX - appWs - .callZome( - { - cap_secret: null, - cell_id: cellId, - zome_name: PROJECTS_ZOME_NAME, - fn_name: 'init_signal', - payload: null, - provenance: getAgentPubKey(), // FIXME: this will need correcting after holochain changes this - }, - 50000 - ) - .then(() => console.log('succesfully triggered init_signal')) - .catch((e) => console.error('failed while triggering init_signal: ', e)) - // this will trigger the fetching of project meta - // checks and other things - dispatch(joinProjectCellId(cellIdString)) - return false -} - -async function deactivateApp( - appId: string, - cellId: CellIdString, - dispatch: any -) { - const adminWs = await getAdminWs() - await adminWs.disableApp({ - installed_app_id: appId, - }) - await dispatch(removeProjectCellId(cellId)) -} +import { deactivateProject } from '../../projects/deactivateProject' +import { createProject } from '../../projects/createProject' +import { joinProject, triggerJoinSignal } from '../../projects/joinProject' // ACTUAL REDUX FUNCTIONS @@ -137,8 +62,9 @@ function mapDispatchToProps(dispatch): DashboardDispatchProps { setShowInviteMembersModal: (passphrase: string) => { return dispatch(openInviteMembersModal(passphrase)) }, - deactivateApp: async (appId: string, cellId: CellIdString) => { - return deactivateApp(appId, cellId, dispatch) + deactivateProject: async (appId: string, cellId: CellIdString) => { + const adminWs = await getAdminWs() + return deactivateProject(appId, cellId, dispatch, adminWs) }, fetchEntryPointDetails: async (cellIdString: CellIdString) => { const appWebsocket = await getAppWs() @@ -165,12 +91,27 @@ function mapDispatchToProps(dispatch): DashboardDispatchProps { ) return dispatch(fetchProjectMeta(cellIdString, projectMeta)) }, + updateProjectMeta: async ( + projectMeta: ProjectMeta, + actionHash: ActionHashB64, + cellIdString: CellIdString + ) => { + const appWebsocket = await getAppWs() + const projectsZomeApi = new ProjectsZomeApi(appWebsocket) + const cellId = cellIdFromString(cellIdString) + const updatedProjectMeta = await projectsZomeApi.projectMeta.update( + cellId, + { entry: projectMeta, actionHash } + ) + return dispatch(updateProjectMeta(cellIdString, updatedProjectMeta)) + }, createProject: async ( agentAddress: AgentPubKeyB64, project: { name: string; image: string }, passphrase: string ) => { - // matches the createProjectMeta fn and type signature + const appWs = await getAppWs() + const projectsZomeApi = new ProjectsZomeApi(appWs) const projectMeta: ProjectMeta = { ...project, // name and image passphrase, @@ -181,27 +122,17 @@ function mapDispatchToProps(dispatch): DashboardDispatchProps { topPriorityOutcomes: [], isMigrated: null, } - await createProject(passphrase, projectMeta, agentAddress, dispatch) + await createProject(passphrase, projectMeta, agentAddress, dispatch, projectsZomeApi) }, - updateProjectMeta: async ( - projectMeta: ProjectMeta, - actionHash: ActionHashB64, - cellIdString: CellIdString - ) => { - const appWebsocket = await getAppWs() - const projectsZomeApi = new ProjectsZomeApi(appWebsocket) + joinProject: async (passphrase: string) => { + const appWs = await getAppWs() + const cellIdString = await joinProject(passphrase, dispatch) const cellId = cellIdFromString(cellIdString) - const updatedProjectMeta = await projectsZomeApi.projectMeta.update( - cellId, - { entry: projectMeta, actionHash } - ) - return dispatch(updateProjectMeta(cellIdString, updatedProjectMeta)) - }, - joinProject: (passphrase: string) => { - return joinProject(passphrase, dispatch) + triggerJoinSignal(cellId, appWs) + return cellIdString }, - importProject: (agentAddress, projectData, passphrase) => { - return installProjectAppAndImport( + installProjectAndImport: (agentAddress, projectData, passphrase) => { + return installProjectAndImport( agentAddress, projectData, passphrase, diff --git a/web/test/createProject.test.ts b/web/test/createProject.test.ts new file mode 100644 index 000000000..99765710b --- /dev/null +++ b/web/test/createProject.test.ts @@ -0,0 +1,28 @@ +import { finalizeCreateProject } from '../src/projects/createProject' +import mockUnmigratedProjectMeta from './mockProjectMeta' + + +describe('finalizeCreateProject', () => { + it('should dispatch actions', () => { + + const dispatch = jest.fn() + // const cellIdString = 'testCellIdString' + // await finalizeCreateProject() + + // expect(dispatch).toHaveBeenCalledTimes(2) + // expect(dispatch).toHaveBeenNthCalledWith(1, { + // type: 'SIMPLE_CREATE_PROJECT_META', + // payload: mockUnmigratedProjectMeta, + // meta: { cellIdString: mockCellIdString }, + // }) + // expect(dispatch).toHaveBeenNthCalledWith(2, { + // type: 'SET_MEMBER', + // payload: { + // cellIdString: mockCellIdString, + // member: { + // agentPubKey: 'testAgentAddress', + // }, + // }, + // }) + }) +}) \ No newline at end of file diff --git a/web/test/import.test.ts b/web/test/import.test.ts index 883f1cd73..1b4d05e4c 100644 --- a/web/test/import.test.ts +++ b/web/test/import.test.ts @@ -6,7 +6,7 @@ import { OutcomeMember, ProjectMeta, Tag } from '../src/types' import { WireRecord } from '../src/api/hdkCrud' import mockUnmigratedProjectMeta from './mockProjectMeta' import mockBaseRootState from './mockRootState' -import { installProjectApp as _installProjectApp } from '../src/projects/installProjectApp' +import { installProject as _installProject } from '../src/projects/installProject' import { getAppWs as _getAppWs } from '../src/hcWebsockets' import { AppWebsocket } from '@holochain/client' import { RootState } from '../src/redux/reducer' @@ -41,12 +41,13 @@ import { internalCreateActionHashMapAndImportProjectData, } from '../src/migrating/import/createActionHashMapAndImportProjectData' import { - installProjectAppAndImport as _installProjectAppAndImport, - internalInstallProjectAppAndImport, -} from '../src/migrating/import/installProjectAppAndImport' + installProjectAndImport as _installProjectAndImport, + internalInstallProjectAndImport, +} from '../src/migrating/import/installProjectAndImport' import mockWhoami from './mockWhoami' import { cellIdFromString } from '../src/utils' import mockActionHashMaps from './mockActionHashMaps' +import { finalizeCreateProject as _finalizeCreateProject } from '../src/projects/createProject' let store: any // too complex of a type to mock @@ -54,8 +55,9 @@ let createProfilesZomeApi: typeof _createProfilesZomeApi let createProjectsZomeApi: typeof _createProjectsZomeApi let profilesZomeApi: ProfilesZomeApi let projectsZomeApi: ProjectsZomeApi -let installProjectAppAndImport: typeof _installProjectAppAndImport -let installProjectApp: typeof _installProjectApp +let installProjectAndImport: typeof _installProjectAndImport +let installProject: typeof _installProject +let finalizeCreateProject: typeof _finalizeCreateProject let createActionHashMapAndImportProjectData: typeof _createActionHashMapAndImportProjectData let baseRootState: typeof mockBaseRootState let mockGetState: () => RootState @@ -118,14 +120,16 @@ beforeEach(() => { }) profilesZomeApi = createProfilesZomeApi(mockAppWs) projectsZomeApi = createProjectsZomeApi(mockAppWs) - installProjectAppAndImport = jest.fn() + installProjectAndImport = jest.fn() mockCellIdString = '132,45,36,204,129,221,8,19,206,244,229,30,210,95,157,234,241,47,13,85,105,207,55,138,160,87,204,162,244,122,186,195,125,254,5,185,165,224,66[:cell_id_divider:]132,32,36,97,138,27,24,136,8,80,164,189,194,243,82,224,72,205,215,225,2,27,126,146,190,40,102,187,244,75,191,172,155,196,247,226,220,92,1' - installProjectApp = jest + installProject = jest .fn() .mockResolvedValue([mockCellIdString, ['abc'], 'testString']) + finalizeCreateProject = jest.fn() + createActionHashMapAndImportProjectData = jest.fn().mockResolvedValue({ tagActionHashMap: {}, outcomeActionHashMap: {}, @@ -153,9 +157,8 @@ describe('importProjectsData()', () => { it('successfully parses and imports project data and user profile', async () => { await internalImportProjectsData( profilesZomeApi, - projectsZomeApi, - installProjectAppAndImport, - installProjectApp, + installProjectAndImport, + installProject, store, mockMigrationData, onStep @@ -174,17 +177,16 @@ describe('importProjectsData()', () => { sampleGoodDataExport.projects[1].projectMeta.isMigrated ).not.toBeNull() - expect(installProjectAppAndImport).toHaveBeenCalledTimes(1) - expect(installProjectAppAndImport).toHaveBeenCalledWith( + expect(installProjectAndImport).toHaveBeenCalledTimes(1) + expect(installProjectAndImport).toHaveBeenCalledWith( 'testAgentAddress', sampleGoodDataExport.projects[0], sampleGoodDataExport.projects[0].projectMeta.passphrase, store.dispatch, - projectsZomeApi ) - expect(installProjectApp).toHaveBeenCalledTimes(1) - expect(installProjectApp).toHaveBeenCalledWith( + expect(installProject).toHaveBeenCalledTimes(1) + expect(installProject).toHaveBeenCalledWith( sampleGoodDataExport.projects[1].projectMeta.passphrase ) @@ -217,9 +219,8 @@ describe('importProjectsData()', () => { try { await internalImportProjectsData( profilesZomeApi, - projectsZomeApi, - installProjectAppAndImport, - installProjectApp, + installProjectAndImport, + installProject, store, mockMigrationData, onStep @@ -233,9 +234,8 @@ describe('importProjectsData()', () => { try { await internalImportProjectsData( profilesZomeApi, - projectsZomeApi, - installProjectAppAndImport, - installProjectApp, + installProjectAndImport, + installProject, store, mockMigrationData, onStep @@ -249,21 +249,22 @@ describe('importProjectsData()', () => { }) }) -describe('installProjectAppAndImport()', () => { +describe('installProjectAndImport()', () => { const agentAddress = 'testAgentAddress' it('installs DNA and imports project into new cell', async () => { - await internalInstallProjectAppAndImport( + await internalInstallProjectAndImport( agentAddress, sampleGoodDataExport.projects[0], sampleGoodDataExport.projects[0].projectMeta.passphrase, store.dispatch, - installProjectApp, + installProject, createActionHashMapAndImportProjectData, + finalizeCreateProject, projectsZomeApi ) - expect(installProjectApp).toHaveBeenCalledTimes(1) - expect(installProjectApp).toHaveBeenCalledWith( + expect(installProject).toHaveBeenCalledTimes(1) + expect(installProject).toHaveBeenCalledWith( sampleGoodDataExport.projects[0].projectMeta.passphrase ) @@ -274,21 +275,27 @@ describe('installProjectAppAndImport()', () => { store.dispatch ) - expect(store.dispatch).toHaveBeenCalledTimes(2) - expect(store.dispatch).toHaveBeenNthCalledWith(1, { - type: 'SIMPLE_CREATE_PROJECT_META', - payload: projectMeta, - meta: { cellIdString: mockCellIdString }, - }) - expect(store.dispatch).toHaveBeenNthCalledWith(2, { - type: 'SET_MEMBER', - payload: { - cellIdString: mockCellIdString, - member: { - agentPubKey: 'testAgentAddress', - }, - }, - }) + expect(finalizeCreateProject).toHaveBeenCalledTimes(1) + const expectedProjectMeta = { + creatorAgentPubKey: 'testAgentAddress', + // use expect.anything() because of Date.now() + // being used internally + createdAt: expect.anything(), + name: 'new project', + image: '', + passphrase: 'daily plant employee shorten define', + isImported: false, + layeringAlgorithm: 'CoffmanGraham', + topPriorityOutcomes: [], + isMigrated: null + } + expect(finalizeCreateProject).toHaveBeenCalledWith( + mockCellIdString, + expectedProjectMeta, + 'testAgentAddress', + store.dispatch, + projectsZomeApi + ) }) })