diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ContestEnroller.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ContestEnroller.kt index 9cbe06a04e..15ff7d7517 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ContestEnroller.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ContestEnroller.kt @@ -157,7 +157,7 @@ private fun contestEnrollerComponent() = FC { props -> }) val (availableOptions, setAvailableOptions) = useState(emptyList()) - useRequest(isDeferred = false) { + useRequest { val availableVariants = get( if (isContestSelector) { "$apiUrl/contests/$organizationName/$projectName/eligible-contests" @@ -171,9 +171,9 @@ private fun contestEnrollerComponent() = FC { props -> it.decodeFromJsonString>() } setAvailableOptions(availableVariants) - }() + } - val enrollRequest = useRequest { + val enrollRequest = useDeferredRequest { val responseFromBackend = get( "$apiUrl/contests/$contestName/enroll?organizationName=$organizationName&projectName=$projectName", headers = Headers(), diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ManageUserRoleCard.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ManageUserRoleCard.kt index d38f219843..46e6a4a5e1 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ManageUserRoleCard.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ManageUserRoleCard.kt @@ -88,7 +88,7 @@ external interface ManageUserRoleCardProps : Props { fun manageUserRoleCardComponent() = FC { props -> val (changeUsersFromGroup, setChangeUsersFromGroup) = useState(true) val (usersFromGroup, setUsersFromGroup) = useState(emptyList()) - val getUsersFromGroup = useRequest(dependencies = arrayOf(changeUsersFromGroup)) { + val getUsersFromGroup = useDeferredRequest { val usersFromDb = get( url = "$apiUrl/${props.groupType}s/${props.groupPath}/users", headers = Headers().also { @@ -103,7 +103,7 @@ fun manageUserRoleCardComponent() = FC { props -> } val (roleChange, setRoleChange) = useState(SetRoleRequest("", Role.NONE)) - val updatePermissions = useRequest(dependencies = arrayOf(roleChange)) { + val updatePermissions = useDeferredRequest { val headers = Headers().apply { set("Accept", "application/json") set("Content-Type", "application/json") @@ -124,7 +124,7 @@ fun manageUserRoleCardComponent() = FC { props -> val (userToAdd, setUserToAdd) = useState(UserInfo("")) val (usersNotFromGroup, setUsersNotFromGroup) = useState(emptyList()) val getUsersNotFromGroup = debounce( - useRequest(dependencies = arrayOf(changeUsersFromGroup, userToAdd)) { + useDeferredRequest { val headers = Headers().apply { set("Accept", "application/json") set("Content-Type", "application/json") @@ -141,7 +141,7 @@ fun manageUserRoleCardComponent() = FC { props -> }, DEFAULT_DEBOUNCE_PERIOD, ) - val addUserToGroup = useRequest { + val addUserToGroup = useDeferredRequest { val headers = Headers().apply { set("Accept", "application/json") set("Content-Type", "application/json") @@ -163,7 +163,7 @@ fun manageUserRoleCardComponent() = FC { props -> } val (userToDelete, setUserToDelete) = useState(UserInfo("")) - val deleteUser = useRequest(dependencies = arrayOf(userToDelete)) { + val deleteUser = useDeferredRequest { val headers = Headers().apply { set("Accept", "application/json") set("Content-Type", "application/json") @@ -184,7 +184,7 @@ fun manageUserRoleCardComponent() = FC { props -> } val (selfRole, setSelfRole) = useState(Role.NONE) - useRequest(isDeferred = false) { + useRequest { val role = get( "$apiUrl/${props.groupType}s/${props.groupPath}/users/roles", headers = Headers().also { @@ -200,7 +200,7 @@ fun manageUserRoleCardComponent() = FC { props -> props.showGlobalRoleWarning() } setSelfRole(getHighestRole(role, props.selfUserInfo.globalRole)) - }() + } runOnlyOnFirstRender { getUsersFromGroup() diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/SelectForm.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/SelectForm.kt index 0df42a306d..bdc7245365 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/SelectForm.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/SelectForm.kt @@ -84,9 +84,9 @@ external interface SelectFormRequiredProps : Props { fun selectFormRequired() = FC> { props -> val (elements, setElements) = useState(listOf()) - useRequest(arrayOf(), isDeferred = false) { + useRequest { setElements((props.getData)()) - }() + } div { className = ClassName("${props.classes} mt-1") diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TestSuiteSourceCreationComponent.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TestSuiteSourceCreationComponent.kt index cf9f68b54c..90392c793e 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TestSuiteSourceCreationComponent.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/TestSuiteSourceCreationComponent.kt @@ -107,7 +107,7 @@ fun ChildrenBuilder.showTestSuiteSourceCreationModal( private fun testSuiteSourceCreationComponent() = FC { props -> val (testSuiteSource, setTestSuiteSource) = useState(TestSuitesSourceDto.empty.copy(organizationName = props.organizationName)) val (saveStatus, setSaveStatus) = useState(null) - val fetchTestSuiteSource = useRequest { + val fetchTestSuiteSource = useDeferredRequest { post( url = "$apiUrl/test-suites-sources/${testSuiteSource.organizationName}/${testSuiteSource.name}/fetch", headers = jsonHeaders, @@ -116,7 +116,7 @@ private fun testSuiteSourceCreationComponent() = FC { pro val (conflictErrorMessage, setConflictErrorMessage) = useState(null) - val onSaveButtonPressed = useRequest { + val onSaveButtonPressed = useDeferredRequest { val response = post( "$apiUrl/${FrontendRoutes.CONTESTS.path}/create", jsonHeaders, diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestInfoMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestInfoMenu.kt index 77391407e8..ddc0637b9d 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestInfoMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestInfoMenu.kt @@ -37,7 +37,7 @@ external interface ContestInfoMenuProps : Props { @Suppress("TOO_LONG_FUNCTION", "LongMethod") private fun contestInfoMenu() = FC { props -> val (contest, setContest) = useState(null) - useRequest(isDeferred = false) { + useRequest { val contestDto = get( "$apiUrl/contests/${props.contestName}", headers = Headers().also { @@ -49,7 +49,7 @@ private fun contestInfoMenu() = FC { props -> it.decodeFromJsonString() } setContest(contestDto) - }() + } div { className = ClassName("d-flex justify-content-center") diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt index b71a3f55f7..276fb87c0d 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt @@ -41,7 +41,7 @@ external interface ContestSummaryMenuProps : Props { ) private fun contestSummaryMenu() = FC { props -> val (results, setResults) = useState>(emptyList()) - useRequest(isDeferred = false) { + useRequest { val projectResults = get( url = "$apiUrl/contests/${props.contestName}/scores", headers = Headers().also { @@ -54,7 +54,7 @@ private fun contestSummaryMenu() = FC { props -> } .sortedByDescending { it.score } setResults(projectResults) - }() + } div { className = ClassName("mb-3") style = jso { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/PublicTestCardComponent.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/PublicTestCardComponent.kt index 7bf1dbae8a..6874cd896c 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/PublicTestCardComponent.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/PublicTestCardComponent.kt @@ -60,7 +60,7 @@ private fun publicTestComponent() = FC { props -> val (avaliableTestSuites, setAvaliableTestSuites) = useState>(emptyList()) val (publicTest, setPublicTest) = useState(null) - useRequest(isDeferred = false) { + useRequest { val response = get( "$apiUrl/contests/${props.contestName}/test-suites", jsonHeaders, @@ -74,7 +74,7 @@ private fun publicTestComponent() = FC { props -> setPublicTest(null) setSelectedTestSuite(null) } - }() + } useRequest(dependencies = arrayOf(selectedTestSuite)) { selectedTestSuite?.let { selectedTestSuite -> @@ -91,7 +91,7 @@ private fun publicTestComponent() = FC { props -> setPublicTest(TestFilesContent.empty) } } - }() + } if (avaliableTestSuites.isEmpty()) { h6 { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/ManageGitCredentialsCard.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/ManageGitCredentialsCard.kt index e04b459cdc..191628bd23 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/ManageGitCredentialsCard.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/ManageGitCredentialsCard.kt @@ -66,7 +66,7 @@ external interface ManageGitCredentialsCardProps : Props { @Suppress("TOO_LONG_FUNCTION", "LongMethod") fun manageGitCredentialsCardComponent() = FC { props -> val (selfRole, setSelfRole) = useState(Role.NONE) - useRequest(isDeferred = false) { + useRequest { val role = get( "$apiUrl/organizations/${props.organizationName}/users/roles", headers = jsonHeaders, @@ -80,7 +80,7 @@ fun manageGitCredentialsCardComponent() = FC { pr props.showGlobalRoleWarning() } setSelfRole(getHighestRole(role, props.selfUserInfo.globalRole)) - }() + } val (gitCredentials, _, fetchGitCredentialsRequest) = prepareFetchGitCredentials(props.organizationName) @@ -194,7 +194,7 @@ fun manageGitCredentialsCardComponent() = FC { pr @Suppress("TYPE_ALIAS") private fun prepareFetchGitCredentials(organizationName: String): RequestWithDependency> { val (gitCredentials, setGitCredentials) = useState(emptyList()) - val fetchGitCredentialsRequest = useRequest { + val fetchGitCredentialsRequest = useDeferredRequest { get( "$apiUrl/organizations/$organizationName/list-git", headers = jsonHeaders, @@ -215,7 +215,7 @@ private fun prepareUpsertGitCredential( fetchGitCredentialsRequest: () -> Unit ): RequestWithDependency { val (gitCredentialToUpsert, setGitCredentialToUpsert) = useState(null) - val upsertGitCredentialRequest = useRequest(dependencies = arrayOf(gitCredentialToUpsert)) { + val upsertGitCredentialRequest = useDeferredRequest { val headers = Headers().apply { set("Accept", "application/json") set("Content-Type", "application/json") @@ -241,7 +241,7 @@ private fun prepareDeleteGitCredential( fetchGitCredentialsRequest: () -> Unit ): RequestWithDependency { val (gitCredentialToDelete, setGitCredentialToDelete) = useState(GitDto("N/A")) - val deleteGitCredentialRequest = useRequest(dependencies = arrayOf(gitCredentialToDelete)) { + val deleteGitCredentialRequest = useDeferredRequest { val response = delete( url = "$apiUrl/organizations/$organizationName/delete-git?url=${gitCredentialToDelete.url}", headers = jsonHeaders, diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/OrganizationTestsMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/OrganizationTestsMenu.kt index 7d25727205..a9cf1745da 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/OrganizationTestsMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/OrganizationTestsMenu.kt @@ -52,7 +52,7 @@ private fun organizationTestsMenu() = FC { props -> val (isTestSuiteSourceCreationModalOpen, setIsTestSuitesSourceCreationModalOpen) = useState(false) val (isSourceCreated, setIsSourceCreated) = useState(false) val (testSuitesSources, setTestSuitesSources) = useState(emptyList()) - val fetchTestSuitesSources = useRequest(dependencies = arrayOf(props.organizationName, isSourceCreated)) { + useRequest(dependencies = arrayOf(props.organizationName, isSourceCreated)) { val response = get( url = "$apiUrl/test-suites-sources/${props.organizationName}/list", headers = Headers().also { @@ -66,9 +66,8 @@ private fun organizationTestsMenu() = FC { props -> setTestSuitesSources(emptyList()) } } - fetchTestSuitesSources() val (testSuiteSourceToFetch, setTestSuiteSourceToFetch) = useState(null) - val triggerFetchTestSuiteSource = useRequest(dependencies = arrayOf(testSuiteSourceToFetch)) { + val triggerFetchTestSuiteSource = useDeferredRequest { testSuiteSourceToFetch?.let { testSuiteSource -> post( url = "$apiUrl/test-suites-sources/${testSuiteSource.organizationName}/${encodeURIComponent(testSuiteSource.name)}/fetch", @@ -83,7 +82,7 @@ private fun organizationTestsMenu() = FC { props -> val (selectedTestSuitesSource, setSelectedTestSuitesSource) = useState(null) val (testSuitesSourceSnapshotKeys, setTestSuitesSourceSnapshotKeys) = useState(emptyList()) - val fetchTestSuitesSourcesSnapshotKeys = useRequest(dependencies = arrayOf(selectedTestSuitesSource)) { + val fetchTestSuitesSourcesSnapshotKeys = useDeferredRequest { selectedTestSuitesSource?.let { testSuitesSource -> val response = get( url = "$apiUrl/test-suites-sources/${testSuitesSource.organizationName}/${encodeURIComponent(testSuitesSource.name)}/list-snapshot", diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectInfoMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectInfoMenu.kt index c14f848051..6af6746038 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectInfoMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectInfoMenu.kt @@ -58,7 +58,7 @@ external interface ProjectInfoMenuProps : Props { ) private fun projectInfoMenu() = FC { props -> val (usersInProject, setUsersInProject) = useState(emptyList()) - useRequest(isDeferred = false) { + useRequest { val users: List = get( url = "$apiUrl/projects/${props.organizationName}/${props.projectName}/users", headers = Headers().also { @@ -70,10 +70,10 @@ private fun projectInfoMenu() = FC { props -> it.decodeFromJsonString() } setUsersInProject(users) - }() + } val (bestResults, setBestResults) = useState(emptyList()) - useRequest(isDeferred = false) { + useRequest { val results: List = get( url = "$apiUrl/contests/${props.organizationName}/${props.projectName}/best", headers = Headers().also { @@ -85,10 +85,10 @@ private fun projectInfoMenu() = FC { props -> it.decodeFromJsonString() } setBestResults(results) - }() + } val (project, setProject) = useState(Project.stub(-1)) - useRequest(isDeferred = false) { + useRequest { val projectFromBackend: Project = get( url = "$apiUrl/projects/get/organization-name?name=${props.projectName}&organizationName=${props.organizationName}", headers = Headers().also { @@ -100,7 +100,7 @@ private fun projectInfoMenu() = FC { props -> it.decodeFromJsonString() } setProject(projectFromBackend) - }() + } div { className = ClassName("d-flex justify-content-center") diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectStatisticMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectStatisticMenu.kt index 3f57c2c0dc..1f37c1be5c 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectStatisticMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/projects/ProjectStatisticMenu.kt @@ -81,7 +81,7 @@ external interface ProjectStatisticMenuProps : Props { private fun projectStatisticMenu() = FC { props -> val (latestExecutionStatisticDtos, setLatestExecutionStatisticDtos) = useState(props.latestExecutionStatisticDtos) - useRequest(arrayOf(props.executionId, props.latestExecutionStatisticDtos, props.isOpen), isDeferred = false) { + useRequest(arrayOf(props.executionId, props.latestExecutionStatisticDtos, props.isOpen)) { if (props.isOpen != true && props.executionId != null) { val testLatestExecutions = get( url = "$apiUrl/testLatestExecutions?executionId=${props.executionId}&status=${TestResultStatus.PASSED}", @@ -95,7 +95,7 @@ private fun projectStatisticMenu() = FC { props -> } setLatestExecutionStatisticDtos(testLatestExecutions) } - }() + } div { className = ClassName("row justify-content-center") diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/testsuiteselector/TestSuiteSelectorBrowserMode.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/testsuiteselector/TestSuiteSelectorBrowserMode.kt index 491a14ac46..4d01113f13 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/testsuiteselector/TestSuiteSelectorBrowserMode.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/testsuiteselector/TestSuiteSelectorBrowserMode.kt @@ -180,7 +180,7 @@ private fun testSuiteSelectorBrowserMode() = FC>(emptyList()) useRequest(dependencies = arrayOf(selectedTestSuiteSource)) { @@ -196,7 +196,7 @@ private fun testSuiteSelectorBrowserMode() = FC>(emptyList()) useRequest(dependencies = arrayOf(selectedTestSuiteVersion)) { @@ -223,7 +223,7 @@ private fun testSuiteSelectorBrowserMode() = FC { props -> val (selectedTestSuites, setSelectedTestSuites) = useState>(emptyList()) val (preselectedTestSuites, setPreselectedTestSuites) = useState>(emptyList()) - useRequest(isDeferred = false) { + useRequest { val testSuitesFromBackend: List = post( url = "$apiUrl/test-suites/get-by-ids", headers = jsonHeaders, @@ -52,7 +52,7 @@ private fun testSuiteSelectorManagerMode() = FC { props -> val (selectedTestSuites, setSelectedTestSuites) = useState>(emptyList()) val (filteredTestSuites, setFilteredTestSuites) = useState>(emptyList()) - useRequest(isDeferred = false) { + useRequest { val testSuitesFromBackend: List = post( url = "$apiUrl/test-suites/get-by-ids", headers = jsonHeaders, @@ -69,11 +69,11 @@ private fun testSuiteSelectorSearchMode() = FC ) .decodeFromJsonString() setSelectedTestSuites(testSuitesFromBackend) - }() + } val (filters, setFilters) = useState(TestSuiteFilters.empty) val getFilteredTestSuites = debounce( - useRequest(dependencies = arrayOf(filters)) { + useDeferredRequest { if (filters.isNotEmpty()) { val testSuitesFromBackend: List = get( url = "$apiUrl/test-suites/filtered${ diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/modal/LogoutModal.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/modal/LogoutModal.kt index 5a48d69136..582a589400 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/modal/LogoutModal.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/modal/LogoutModal.kt @@ -9,7 +9,7 @@ import com.saveourtool.save.frontend.externals.modal.modal import com.saveourtool.save.frontend.utils.loadingHandler import com.saveourtool.save.frontend.utils.post import com.saveourtool.save.frontend.utils.spread -import com.saveourtool.save.frontend.utils.useRequest +import com.saveourtool.save.frontend.utils.useDeferredRequest import csstype.ClassName import org.w3c.fetch.Headers @@ -30,7 +30,7 @@ import kotlinx.browser.window fun logoutModal( closeCallback: () -> Unit ) = FC { props -> - val doLogoutRequest = useRequest { + val doLogoutRequest = useDeferredRequest { val replyToLogout = post( "${window.location.origin}/logout", Headers(), diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/TestExecutionDetailsView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/TestExecutionDetailsView.kt index 13116981b7..9d17b0a625 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/TestExecutionDetailsView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/TestExecutionDetailsView.kt @@ -112,7 +112,7 @@ fun testExecutionDetailsView() = FC { val (testResultDebugInfo, setTestResultDebugInfo) = useState(null) // fixme: after https://github.com/saveourtool/save-cloud/issues/364 can be passed via history state to avoid requests - useRequest(arrayOf(params), isDeferred = false) { + useRequest(arrayOf(params)) { val testExecutionDtoResponse = post( "$apiUrl/test-execution?executionId=$executionId&checkDebugInfo=true", Headers().apply { @@ -133,7 +133,7 @@ fun testExecutionDetailsView() = FC { } else { setStatus("Additional test info is not available (code ${testExecutionDtoResponse.status})") } - }() + } testResultDebugInfo?.let { resultsTable(testResultDebugInfo) diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/RequestUtils.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/RequestUtils.kt index 99201e2db2..fa96c2450c 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/RequestUtils.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/RequestUtils.kt @@ -304,39 +304,28 @@ private fun ComponentWithScope<*, *>.responseHandlerWithValidation( } /** - * Hook to perform requests in functional components. + * Hook to get callbacks to perform requests in functional components. * - * @param dependencies - * @param isDeferred whether this request should be performed right after component is mounted (`isDeferred == false`) - * or called later by some other mechanism (e.g. a button click). * @param request - * @return a function to trigger request execution. If `isDeferred == false`, this function should be called right after the hook is called. + * @return a function to trigger request execution. */ -@Suppress("TOO_LONG_FUNCTION", "MAGIC_NUMBER") -fun useRequest( - dependencies: Array = emptyArray(), - isDeferred: Boolean = true, +fun useDeferredRequest( request: suspend WithRequestStatusContext.() -> R, ): () -> Unit { val scope = CoroutineScope(Dispatchers.Default) + val context = useRequestStatusContext() val (isSending, setIsSending) = useState(false) - val statusContext = useContext(requestStatusContext) - val context = object : WithRequestStatusContext { - override val coroutineScope = CoroutineScope(Dispatchers.Default) - override fun setResponse(response: Response) = statusContext.setResponse(response) - override fun setRedirectToFallbackView(isNeedRedirect: Boolean, response: Response) = statusContext.setRedirectToFallbackView( - isNeedRedirect && response.status == 404.toShort() - ) - override fun setLoadingCounter(transform: (oldValue: Int) -> Int) = statusContext.setLoadingCounter(transform) - } - - useEffect(isSending, *dependencies) { + useEffect(isSending) { if (!isSending) { return@useEffect } scope.launch { request(context) setIsSending(false) + }.invokeOnCompletion { + if (it != null && it !is CancellationException) { + setIsSending(false) + } } cleanup { if (scope.isActive) { @@ -344,17 +333,36 @@ fun useRequest( } } } - val initiateSending: () -> Unit = { if (!isSending) { setIsSending(true) } } - @Suppress("BRACES_BLOCK_STRUCTURE_ERROR") - return if (!isDeferred) { { - useEffect(*dependencies) { initiateSending() } - } } else { - return initiateSending + return initiateSending +} + +/** + * Hook to perform requests in functional components. + * + * @param dependencies + * @param request + */ +fun useRequest( + dependencies: Array = emptyArray(), + request: suspend WithRequestStatusContext.() -> R, +) { + val scope = CoroutineScope(Dispatchers.Default) + val context = useRequestStatusContext() + + useEffect(*dependencies) { + scope.launch { + request(context) + } + cleanup { + if (scope.isActive) { + scope.cancel() + } + } } } @@ -374,6 +382,20 @@ internal suspend fun noopLoadingHandler(request: suspend () -> Response) = reque */ internal fun noopResponseHandler(response: Response) = Unit +@Suppress("TOO_LONG_FUNCTION", "MAGIC_NUMBER") +private fun useRequestStatusContext(): WithRequestStatusContext { + val statusContext = useContext(requestStatusContext) + val context = object : WithRequestStatusContext { + override val coroutineScope = CoroutineScope(Dispatchers.Default) + override fun setResponse(response: Response) = statusContext.setResponse(response) + override fun setRedirectToFallbackView(isNeedRedirect: Boolean, response: Response) = statusContext.setRedirectToFallbackView( + isNeedRedirect && response.status == 404.toShort() + ) + override fun setLoadingCounter(transform: (oldValue: Int) -> Int) = statusContext.setLoadingCounter(transform) + } + return context +} + /** * Perform an HTTP request using Fetch API. Suspending function that returns a [Response] - a JS promise with result. * diff --git a/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/TestUtils.kt b/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/TestUtils.kt index f91249c589..73099c9f56 100644 --- a/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/TestUtils.kt +++ b/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/TestUtils.kt @@ -6,6 +6,7 @@ package com.saveourtool.save.frontend.utils import com.saveourtool.save.frontend.components.RequestStatusContext import com.saveourtool.save.frontend.components.requestStatusContext +import kotlinx.js.timers.setTimeout import org.w3c.fetch.Response import react.FC @@ -14,6 +15,7 @@ import react.useState import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlin.js.Promise val wrapper: FC = FC { val (_, setMockState) = useState(null) @@ -38,3 +40,7 @@ inline fun mockMswResponse(response: dynamic, value: T): dynamic { response.body = Json.encodeToString(value) return response } + +fun wait(millis: Int) = Promise { resolve, _ -> + setTimeout({ resolve(Unit) }, 200) +} diff --git a/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/UseRequestTest.kt b/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/UseRequestTest.kt new file mode 100644 index 0000000000..24205e4926 --- /dev/null +++ b/save-frontend/src/test/kotlin/com/saveourtool/save/frontend/utils/UseRequestTest.kt @@ -0,0 +1,67 @@ +package com.saveourtool.save.frontend.utils + +import com.saveourtool.save.frontend.externals.render +import com.saveourtool.save.frontend.externals.rest +import com.saveourtool.save.frontend.externals.setupWorker +import kotlinx.browser.window +import org.w3c.fetch.Headers +import react.FC +import react.Props +import react.create +import react.useEffect +import react.useState +import kotlin.js.Promise +import kotlin.test.Test +import kotlin.test.assertEquals + +class UseRequestTest { + private var requestCount = 0 + private fun createWorker() = setupWorker( + rest.get("${window.location.origin}/test") { _, res, _ -> + res { response -> + response.status = 200 + requestCount += 1 + response + } + } + ) + + @Test + fun test(): Promise { + val worker = createWorker() + val testComponent = FC { + val (sendSecond, setSendSecond) = useState(false) + val (sendThird, setSendThird) = useState(false) + useRequest(dependencies = arrayOf(sendSecond)) { + get("${window.location.origin}/test", Headers(), ::noopLoadingHandler) + if (!sendSecond) { + assertEquals(1, requestCount) + setSendSecond(true) + } else if (!sendThird) { + assertEquals(2, requestCount) + setSendThird(true) + } + } + val doSendThird = useDeferredRequest { + get("${window.location.origin}/test", Headers(), ::noopLoadingHandler) + } + useEffect(sendThird) { + if (sendThird) { + doSendThird() + } + } + } + + return (worker.start() as Promise<*>).then { + render( + wrapper.create { + testComponent() + } + ) + }.then { + wait(200) + }.then { + assertEquals(3, requestCount) + } + } +}