From e7278cbbcef7aa64961aadefc2a1b4bc614e464a Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 2 Jun 2023 03:41:31 +0300 Subject: [PATCH 01/25] IJMP-954: added clarification of DS organization --- .../formainframe/explorer/ui/AllocationDialog.kt | 11 ++++++++++- .../explorer/ui/DatasetPropertiesDialog.kt | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt index 922a3e5c..28704d3f 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt @@ -14,6 +14,7 @@ import com.intellij.openapi.observable.util.whenItemSelected import com.intellij.openapi.project.Project import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.SimpleListCellRenderer import com.intellij.ui.components.JBScrollPane import com.intellij.ui.dsl.builder.* import com.intellij.ui.dsl.gridLayout.HorizontalAlign @@ -98,7 +99,15 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var DatasetOrganization.PS, DatasetOrganization.PO, DatasetOrganization.POE - ) + ), + SimpleListCellRenderer.create("") { + when (it) { + DatasetOrganization.PS -> "Sequential (PS)" + DatasetOrganization.PO -> "Partitioned (PO)" + DatasetOrganization.POE -> "Partitioned Extended (PO-E)" + else -> "" + } + } ) .bindItem(state.allocationParameters::datasetOrganization.toNullableProperty()) .also { datasetOrganizationBox = it.component } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DatasetPropertiesDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DatasetPropertiesDialog.kt index c8d72da2..8a885fcb 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DatasetPropertiesDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/DatasetPropertiesDialog.kt @@ -20,6 +20,7 @@ import eu.ibagroup.formainframe.common.ui.DialogMode import eu.ibagroup.formainframe.common.ui.DialogState import eu.ibagroup.formainframe.common.ui.StatefulComponent import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes +import org.zowe.kotlinsdk.DatasetOrganization import org.zowe.kotlinsdk.HasMigrated import javax.swing.JComponent @@ -94,7 +95,14 @@ class DatasetPropertiesDialog(val project: Project?, override var state: Dataset label("Organization: ") .widthGroup(sameWidthGroup) textField() - .text(dataset.datasetOrganization?.toString() ?: "") + .text( + when (dataset.datasetOrganization) { + DatasetOrganization.PS -> "Sequential (PS)" + DatasetOrganization.PO -> "Partitioned (PO)" + DatasetOrganization.POE -> "Partitioned Extended (PO-E)" + else -> "" + } + ) .applyToComponent { isEditable = false } .horizontalAlign(HorizontalAlign.FILL) } From 8c726aafda5d5549facd7b2ea0f59ac52c75863b Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 13 Jun 2023 07:49:43 +0300 Subject: [PATCH 02/25] IJMP-1103: fixed: Allocate like strange behavior --- .../explorer/actions/AllocateDatasetAction.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt index 825f619c..8e538356 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt @@ -223,11 +223,16 @@ class AllocateLikeAction : AnAction() { allocationParameters.volumeSerial = datasetInfo.volumeSerial allocationParameters.deviceType = datasetInfo.deviceType allocationParameters.dsnType = datasetInfo.dsnameType - allocationParameters.primaryAllocation = datasetInfo.sizeInTracks ?: 100 - allocationParameters.secondaryAllocation = (datasetInfo.sizeInTracks ?: 100) / 2 + allocationParameters.primaryAllocation = + if (datasetInfo.spaceUnits == SpaceUnits.CYLINDERS) { + (datasetInfo.sizeInTracks ?: 15) / 15 + } else { + datasetInfo.sizeInTracks ?: 100 + } + allocationParameters.secondaryAllocation = allocationParameters.primaryAllocation / 2 allocationParameters.directoryBlocks = if (datasetInfo.datasetOrganization == DatasetOrganization.PO || datasetInfo.datasetOrganization == DatasetOrganization.POE) { - (datasetInfo.sizeInTracks ?: 100) / 3 + allocationParameters.primaryAllocation / 3 } else { 0 } From b62c47f3d40c32efbe0777cf2bef15a9d7d1d899 Mon Sep 17 00:00:00 2001 From: Nikita Belabotski Date: Tue, 13 Jun 2023 09:04:36 +0300 Subject: [PATCH 03/25] IJMP-1084: fixed: incorrect permissions shown after change failure --- .../explorer/actions/GetFilePropertiesAction.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt index fac4c5ec..9a22a014 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt @@ -67,13 +67,10 @@ class GetFilePropertiesAction : AnAction() { ), progressIndicator = it ) - } - .onSuccess { - node.parent?.cleanCacheIfPossible(cleanBatchedQuery = false) - } - .onFailure { t -> + }.onFailure { t -> view.explorer.reportThrowable(t, e.project) - } + } + node.parent?.cleanCacheIfPossible(cleanBatchedQuery = false) } } } From ce2e5d1f4f0b7cf23b1f814f234de730f5505a98 Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Thu, 22 Jun 2023 17:20:34 +0300 Subject: [PATCH 04/25] IJMP-1143: add possible job completion statuses --- .../ui/build/jobs/JobBuildTreeView.kt | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/ui/build/jobs/JobBuildTreeView.kt b/src/main/kotlin/eu/ibagroup/formainframe/ui/build/jobs/JobBuildTreeView.kt index e6a1ca10..5e3126f5 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/ui/build/jobs/JobBuildTreeView.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/ui/build/jobs/JobBuildTreeView.kt @@ -41,6 +41,9 @@ import javax.swing.tree.DefaultMutableTreeNode val JOBS_LOG_VIEW = DataKey.create("jobsLogView") const val JOBS_LOG_NOTIFICATION_GROUP_ID = "eu.ibagroup.formainframe.explorer.ExplorerNotificationGroup" +const val SUCCESSFUL_JOB_COMPLETION_CODE = 0 +const val SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING = 4 + /** * Console with BuildTree for display job execution process and results. * @param jobLogInfo job process information necessary to get log and status. @@ -116,12 +119,26 @@ class JobBuildTreeView( jobLogger.onLogFinished { val rc = jobLogger - .logFetcher - .getCachedJobStatus() - ?.returnedCode - ?.uppercase() - val result = if (rc == null || rc.contains(Regex("ERR|ABEND|CANCEL"))) FailureResultImpl() - else SuccessResultImpl() + .logFetcher + .getCachedJobStatus() + ?.returnedCode + ?.uppercase() + var codeWithWarning = false + val result = if (rc == null || rc.contains(Regex("ERR|ABEND|CANCEL|FAIL"))) FailureResultImpl() + else if (rc.contains("CC")) { // result code can be in format "CC nnnn" + val completionCode = rc.split(" ")[1].toInt() + when (completionCode) { + SUCCESSFUL_JOB_COMPLETION_CODE -> SuccessResultImpl() + + SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING -> { + codeWithWarning = true + SuccessResultImpl() + } + + else -> FailureResultImpl() + } + } else SuccessResultImpl() + jobLogger.logFetcher.getCachedLog() .forEach { treeConsoleView.onEvent(buildId, FinishEventImpl(it.key.id, buildId, Date().time, it.key.ddName, result)) @@ -131,6 +148,8 @@ class JobBuildTreeView( val buildExecutionNode = (buildNode as DefaultMutableTreeNode).userObject as ExecutionNode if (result is FailureResultImpl) { buildExecutionNode.setIconProvider { AllIcons.General.BalloonError } + } else if (codeWithWarning) { + buildExecutionNode.setIconProvider { AllIcons.General.BalloonWarning } } else { buildExecutionNode.setIconProvider { AllIcons.General.InspectionsOK } } From 771905c80236988769a40f2709f15e204a10405b Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Tue, 27 Jun 2023 13:25:32 +0300 Subject: [PATCH 05/25] IJMP-1164: requests are separated --- .../formainframe/config/connect/ui/zosmf/ConnectionDialog.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt index 0496816e..2b3ef380 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt @@ -96,7 +96,6 @@ class ConnectionDialog( runCatching { service().performOperation(InfoOperation(newTestedConnConfig), it) }.onSuccess { - state.owner = whoAmI(newTestedConnConfig) ?: "" val systemInfo = service().performOperation(ZOSInfoOperation(newTestedConnConfig)) state.zVersion = when (systemInfo.zosVersion) { "04.25.00" -> ZVersion.ZOS_2_2 @@ -143,6 +142,9 @@ class ConnectionDialog( } addAnyway } else { + runTask(title = "Retrieving user information", project = project) { + state.owner = whoAmI(newTestedConnConfig) ?: "" + } true } } From 33e329663f18d200ffd9e292d6c42f59d79fbf55 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 5 Jul 2023 15:09:06 +0300 Subject: [PATCH 06/25] IJMP-995: fixed: Password is changed only for one connection --- .../config/connect/ui/zosmf/ConnectionDialog.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt index 2b3ef380..d9fa7899 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/config/connect/ui/zosmf/ConnectionDialog.kt @@ -23,16 +23,15 @@ import com.intellij.ui.dsl.gridLayout.HorizontalAlign import eu.ibagroup.formainframe.common.ui.DialogMode import eu.ibagroup.formainframe.common.ui.StatefulDialog import eu.ibagroup.formainframe.common.ui.showUntilDone +import eu.ibagroup.formainframe.config.configCrudable import eu.ibagroup.formainframe.config.connect.* import eu.ibagroup.formainframe.config.connect.ui.ChangePasswordDialog import eu.ibagroup.formainframe.config.connect.ui.ChangePasswordDialogState import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.operations.* +import eu.ibagroup.formainframe.utils.* import eu.ibagroup.formainframe.utils.crudable.Crudable -import eu.ibagroup.formainframe.utils.runTask -import eu.ibagroup.formainframe.utils.validateConnectionName -import eu.ibagroup.formainframe.utils.validateForBlank -import eu.ibagroup.formainframe.utils.validateZosmfUrl +import eu.ibagroup.formainframe.utils.crudable.getAll import org.zowe.kotlinsdk.ChangePassword import org.zowe.kotlinsdk.annotations.ZVersion import java.awt.Component @@ -302,6 +301,16 @@ class ConnectionDialog( val relativePoint = RelativePoint(this, Point(this.width / 2, this.height)) balloon.show(relativePoint, Balloon.Position.below) } + + configCrudable.getAll().filter { it.url == state.connectionUrl } + .filter { CredentialService.instance.getUsernameByKey(it.uuid) == state.username } + .forEach { + CredentialService.instance.setCredentials( + connectionConfigUuid = it.uuid, + username = state.username, + password = state.password + ) + } } true } From 3a508ff87438d0018e37426b1b70bd74ec7621cd Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 5 Jul 2023 15:28:16 +0300 Subject: [PATCH 07/25] IJMP-1221: fixed: "debounce" test is failed sometimes --- .../kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt index abb8ef56..6cd5534d 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/utils/UtilsTestSpec.kt @@ -32,7 +32,7 @@ import eu.ibagroup.formainframe.vfs.MFVirtualFile import eu.ibagroup.formainframe.vfs.MFVirtualFileSystem import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec -import io.kotest.matchers.longs.shouldBeGreaterThan +import io.kotest.matchers.longs.shouldBeGreaterThanOrEqual import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk @@ -911,7 +911,7 @@ class UtilsTestSpec : ShouldSpec({ assertSoftly { test shouldBe "debounce block" - duration.toMillis() shouldBeGreaterThan 500 + duration.toMillis() shouldBeGreaterThanOrEqual 500 } } } From 931b9d2242de429497d484e455112117c9afe83b Mon Sep 17 00:00:00 2001 From: Valiantsin Krus Date: Wed, 12 Jul 2023 12:39:06 +0300 Subject: [PATCH 08/25] IJMP-877: small changes added to make possible to implement filter bar in CICS plugin. --- .../explorer/ExplorerContentProviderBase.kt | 17 +++++++++++------ .../explorer/ui/ExplorerTreeView.kt | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ExplorerContentProviderBase.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ExplorerContentProviderBase.kt index 3c22e9fe..6c788c1e 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ExplorerContentProviderBase.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ExplorerContentProviderBase.kt @@ -43,7 +43,16 @@ abstract class ExplorerContentProviderBase> by rwLocked(listOf()) internal val myFsTreeStructure: CommonExplorerTreeStructure> internal val myStructure: StructureTreeModel>> - internal val myTree: Tree + val myTree: Tree internal val myNodesToInvalidateOnExpand = hashSetOf() internal val ignoreVFileDeleteEvents = AtomicBoolean(false) internal val ignoreVFSChangeEvents = AtomicBoolean(false) @@ -97,7 +97,7 @@ abstract class ExplorerTreeView, collapse: Boolean = false, invalidate: Boolean = true From 6376b0893a99d715392ab099f3619e333c781981 Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Fri, 21 Jul 2023 14:31:21 +0300 Subject: [PATCH 09/25] IJMP-1210: ignore invalid path exception --- .../formainframe/vfs/MFVirtualFileSystemModel.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/vfs/MFVirtualFileSystemModel.kt b/src/main/kotlin/eu/ibagroup/formainframe/vfs/MFVirtualFileSystemModel.kt index 8668635a..d0c17138 100755 --- a/src/main/kotlin/eu/ibagroup/formainframe/vfs/MFVirtualFileSystemModel.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/vfs/MFVirtualFileSystemModel.kt @@ -11,6 +11,7 @@ package eu.ibagroup.formainframe.vfs import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.io.ByteArraySequence import com.intellij.openapi.util.io.FileAttributes @@ -28,11 +29,14 @@ import org.jgrapht.Graphs import org.jgrapht.graph.DirectedMultigraph import java.io.* import java.nio.file.FileAlreadyExistsException +import java.nio.file.InvalidPathException import java.nio.file.NotDirectoryException import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.Condition +private val log = logger() + class FsOperationException(operationName: String, file: MFVirtualFile) : IOException( "Cannot perform $operationName on ${file.path}" ) @@ -616,7 +620,11 @@ class MFVirtualFileSystemModel { VFileContentChangeEvent(requestor, file, oldModStamp, timeStamp, false) ) // When the output stream changes, the contents of the file change, so you need to send vfs changes topic - sendVfsChangesTopic().before(event) + try { + sendVfsChangesTopic().before(event) + } catch (ignored: InvalidPathException) { + log.warn(ignored) + } assertWriteAllowed() file.validWriteLock { super.close() @@ -626,7 +634,11 @@ class MFVirtualFileSystemModel { initialContentConditions.remove(file) } } + try { sendVfsChangesTopic().after(event) + } catch (ignored: InvalidPathException) { + log.warn(ignored) + } } } } From 98150512a5709caa1694ae0727613b3494a335ab Mon Sep 17 00:00:00 2001 From: Arseni Tsikhamirau Date: Tue, 25 Jul 2023 16:47:33 +0200 Subject: [PATCH 10/25] IJMP-1160-Sync-is-not-working-during-indexing 1) fixed --- .../content/synchronizer/SyncAction.kt | 16 ++++++++- .../synchronizer/SyncToolbarProvider.kt | 33 ++++++++++++++++++- .../editor/ChangeContentServiceImpl.kt | 7 +++- .../editor/FileEditorEventsListener.kt | 11 +++++-- src/main/resources/META-INF/plugin.xml | 3 ++ 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt index eb44c6e2..bfbcb661 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt @@ -12,6 +12,7 @@ package eu.ibagroup.formainframe.dataops.content.synchronizer import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileDocumentManager @@ -27,7 +28,6 @@ import eu.ibagroup.formainframe.utils.showSaveAnywayDialog /** Sync action event. It will handle the manual sync button action when it is clicked */ class SyncAction : DumbAwareAction() { - /** * Get a virtual file on which the event was triggered * @param e the event to get the virtual file @@ -99,6 +99,14 @@ class SyncAction : DumbAwareAction() { makeDisabled(e) return } + val isDumbMode = ActionUtil.isDumbMode(e.project) + val editor = getEditor(e) ?: return + if (!isDumbMode && file.isWritable) { + editor.document.setReadOnly(false) + } else { + e.presentation.isEnabledAndVisible = false + return + } val contentSynchronizer = service().getContentSynchronizer(file) val syncProvider = DocumentedSyncProvider(file) val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } @@ -110,4 +118,10 @@ class SyncAction : DumbAwareAction() { && needToUpload } + /** + * Determines if an action is dumb aware or not + */ + override fun isDumbAware(): Boolean { + return true + } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt index db2bf6eb..f3e125eb 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt @@ -10,14 +10,45 @@ package eu.ibagroup.formainframe.dataops.content.synchronizer +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupActivity.RequiredForSmartMode private const val ACTION_GROUP = "eu.ibagroup.formainframe.dataops.content.synchronizer.SyncActionGroup" /** * Class which serves as extension point for editor floating toolbar provider. Defined in plugin.xml */ -class SyncToolbarProvider : AbstractFloatingToolbarProvider(ACTION_GROUP) { +class SyncToolbarProvider : AbstractFloatingToolbarProvider(ACTION_GROUP), RequiredForSmartMode { override val autoHideable = true override val priority = 1 + override val actionGroup: ActionGroup by lazy { + resolveActionGroup() + } + + /** + * Resolves Sync Action Toolbar during IDE startup activity + * @return resolved sync action + */ + private fun resolveActionGroup(): ActionGroup { + ApplicationManager.getApplication() + val actionManager = ActionManager.getInstance() + val action = actionManager.getAction(ACTION_GROUP) + if (action is ActionGroup) return action + val defaultActionGroup = DefaultActionGroup() + actionManager.registerAction(ACTION_GROUP, defaultActionGroup) + return defaultActionGroup + } + + /** + * Runs an activity during IDE startup + * @param project - current project + */ + override fun runActivity(project: Project) { + resolveActionGroup() + } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt index 0fcb104e..99ec123a 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt @@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ReadOnlyModificationException import com.intellij.openapi.fileEditor.FileDocumentManager import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.content.adapters.MFContentAdapter @@ -72,7 +73,11 @@ class ChangeContentServiceImpl : ChangeContentService { val adaptedContent = contentAdapter.prepareContentToMainframe(currentContent, file) runWriteActionInEdt { CommandProcessor.getInstance().runUndoTransparentAction { - editor.document.setText(adaptedContent) + try { + editor.document.setText(adaptedContent) + } catch (e: ReadOnlyModificationException) { + return@runUndoTransparentAction + } } } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt index c0cf5e77..1bba75b4 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt @@ -10,6 +10,7 @@ package eu.ibagroup.formainframe.editor +import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.components.service import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileEditorManager @@ -37,8 +38,14 @@ class FileEditorEventsListener : FileEditorManagerListener { */ override fun fileOpened(source: FileEditorManager, file: VirtualFile) { val editor = source.selectedTextEditor as? EditorEx - editor?.addFocusListener(focusListener) - super.fileOpened(source, file) + if (editor != null) { + editor.addFocusListener(focusListener) + val isDumbMode = ActionUtil.isDumbMode(editor.project) + if (isDumbMode) { + editor.document.setReadOnly(true) + } + super.fileOpened(source, file) + } } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7162b997..79893f91 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -299,6 +299,9 @@ Example of how to see the output:
displayName="For Mainframe" provider="eu.ibagroup.formainframe.config.MainframeConfigurableProvider"/> + + From 0d358d0d5e00754e68b8f733240a9373bc337a58 Mon Sep 17 00:00:00 2001 From: Uladzislau Kalesnikau Date: Fri, 28 Jul 2023 15:09:01 +0300 Subject: [PATCH 11/25] IJMP-924 Reworked AllocateDatasetAction.kt Signed-off-by: Uladzislau --- .../ws/ui/files/FilesWorkingSetDialog.kt | 19 +- .../explorer/actions/AddJobsFilterAction.kt | 38 +- .../explorer/actions/AddMaskAction.kt | 30 +- .../explorer/actions/AllocateActionBase.kt | 190 ++++ .../explorer/actions/AllocateDatasetAction.kt | 253 +----- .../explorer/actions/AllocateLikeAction.kt | 113 +++ .../explorer/actions/EditJobsFilterAction.kt | 32 +- .../explorer/actions/EditMaskAction.kt | 2 +- .../explorer/actions/JobsFilterAction.kt | 52 -- .../explorer/ui/AddOrEditMaskDialog.kt | 19 +- .../explorer/ui/AllocationDialog.kt | 33 +- .../ibagroup/formainframe/utils/miscUtils.kt | 17 + .../formainframe/explorer/ExplorerTestSpec.kt | 293 ------ .../explorer/FilesWorkingSetImplTestSpec.kt | 324 +++++++ .../actions/AllocateDatasetActionTestSpec.kt | 845 ++++++++++++++++++ .../actions/AllocateLikeActionTestSpec.kt | 650 ++++++++++++++ .../actions/EditMaskActionTestSpec.kt | 7 + .../TestAnalyticsServiceImpl.kt | 2 +- 18 files changed, 2275 insertions(+), 644 deletions(-) create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeAction.kt delete mode 100644 src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/JobsFilterAction.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/FilesWorkingSetImplTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetActionTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeActionTestSpec.kt diff --git a/src/main/kotlin/eu/ibagroup/formainframe/config/ws/ui/files/FilesWorkingSetDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/config/ws/ui/files/FilesWorkingSetDialog.kt index 973aa1e4..c52db978 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/config/ws/ui/files/FilesWorkingSetDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/config/ws/ui/files/FilesWorkingSetDialog.kt @@ -14,14 +14,21 @@ import com.intellij.openapi.ui.ValidationInfo import com.intellij.ui.layout.ValidationInfoBuilder import com.intellij.util.ui.ColumnInfo import com.intellij.util.ui.ComboBoxCellEditor -import eu.ibagroup.formainframe.common.ui.* +import eu.ibagroup.formainframe.common.ui.DEFAULT_ROW_HEIGHT +import eu.ibagroup.formainframe.common.ui.DialogMode +import eu.ibagroup.formainframe.common.ui.ValidatingCellRenderer +import eu.ibagroup.formainframe.common.ui.ValidatingColumnInfo +import eu.ibagroup.formainframe.common.ui.ValidatingListTableModel +import eu.ibagroup.formainframe.common.ui.ValidatingTableView import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.connect.getUsername import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig import eu.ibagroup.formainframe.config.ws.MaskState import eu.ibagroup.formainframe.config.ws.ui.AbstractWsDialog import eu.ibagroup.formainframe.config.ws.ui.FilesWorkingSetDialogState import eu.ibagroup.formainframe.utils.MaskType import eu.ibagroup.formainframe.utils.crudable.Crudable +import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey import eu.ibagroup.formainframe.utils.validateDatasetMask import eu.ibagroup.formainframe.utils.validateForBlank import eu.ibagroup.formainframe.utils.validateUssMask @@ -38,7 +45,7 @@ import javax.swing.table.TableCellRenderer * @author Viktar Mushtsin */ class FilesWorkingSetDialog( - crudable: Crudable, + private val crudable: Crudable, state: FilesWorkingSetDialogState ) : AbstractWsDialog( crudable, @@ -231,6 +238,12 @@ class FilesWorkingSetDialog( } } - override fun emptyTableRow(): MaskState = MaskState() + /** + * Returns mask state with predefined mask name as ".*" + */ + override fun emptyTableRow(): MaskState { + val config = crudable.getByUniqueKey(state.connectionUuid) + return MaskState(if (config != null) "${getUsername(config)}.*" else "") + } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddJobsFilterAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddJobsFilterAction.kt index b769c35d..0724fc8e 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddJobsFilterAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddJobsFilterAction.kt @@ -10,31 +10,28 @@ package eu.ibagroup.formainframe.explorer.actions +import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import eu.ibagroup.formainframe.config.connect.ConnectionConfig import eu.ibagroup.formainframe.config.connect.CredentialService import eu.ibagroup.formainframe.config.ws.JobFilterStateWithWS -import eu.ibagroup.formainframe.explorer.ui.* +import eu.ibagroup.formainframe.explorer.JesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.AddJobsFilterDialog +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView +import eu.ibagroup.formainframe.explorer.ui.JesExplorerView +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.utils.getSelectedNodesWorkingSets /** * Action for adding Job Filter from UI. * @author Valiantsin Krus */ -class AddJobsFilterAction : JobsFilterAction() { - - /** - * Is node conforms to the JesFilterNode and the JesWsNode types - * @param node the node to check - */ - override fun isNodeConformsToType(node: ExplorerTreeNode?): Boolean { - return super.isNodeConformsToType(node) || node is JesWsNode - } +class AddJobsFilterAction : AnAction() { /** Opens AddJobsFilterDialog and saves result. */ override fun actionPerformed(e: AnActionEvent) { val view = e.getExplorerView() ?: return - - val ws = getUnits(view).firstOrNull() ?: return + val workingSets = getSelectedNodesWorkingSets(view as ExplorerTreeView<*, *, *>) + val ws = workingSets.firstOrNull() ?: return val owner = ws.connectionConfig?.let { CredentialService.instance.getUsernameByKey(it.uuid) } ?: "" val initialState = JobFilterStateWithWS(ws = ws, owner = owner) val dialog = AddJobsFilterDialog(e.project, initialState) @@ -43,4 +40,19 @@ class AddJobsFilterAction : JobsFilterAction() { } } + /** + * Decides to show the add jobs filter action or not. + * Shows the action if: + * 1. Explorer view is not null + * 2. Item(s) from them same JES working set is(are) selected + */ + override fun update(e: AnActionEvent) { + val view = e.getExplorerView() ?: let { + e.presentation.isEnabledAndVisible = false + return + } + val workingSets = getSelectedNodesWorkingSets(view as ExplorerTreeView<*, *, *>) + e.presentation.isEnabledAndVisible = workingSets.size == 1 + } + } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt index 62f6f996..60e2c192 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AddMaskAction.kt @@ -12,27 +12,28 @@ package eu.ibagroup.formainframe.explorer.actions import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import eu.ibagroup.formainframe.config.connect.ConnectionConfig import eu.ibagroup.formainframe.config.ws.DSMask import eu.ibagroup.formainframe.config.ws.MaskStateWithWS import eu.ibagroup.formainframe.config.ws.UssPath import eu.ibagroup.formainframe.explorer.FilesWorkingSet import eu.ibagroup.formainframe.explorer.ui.AddOrEditMaskDialog -import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView import eu.ibagroup.formainframe.explorer.ui.FileExplorerView import eu.ibagroup.formainframe.explorer.ui.getExplorerView import eu.ibagroup.formainframe.utils.MaskType +import eu.ibagroup.formainframe.utils.getSelectedNodesWorkingSets /** Action to add USS or z/OS mask */ class AddMaskAction : AnAction() { - /** Add mask when the dialog is fulfilled */ + /** Add new mask to the working set, where the action is triggered */ override fun actionPerformed(e: AnActionEvent) { val view = e.getExplorerView() ?: return - val ws = getUnits(view).firstOrNull() ?: return + val workingSets = getSelectedNodesWorkingSets(view as ExplorerTreeView<*, *, *>) + val ws = workingSets.firstOrNull() ?: return val initialState = MaskStateWithWS(ws = ws) - val dialog = AddOrEditMaskDialog(e.project, "Create Mask", initialState) + val dialog = AddOrEditMaskDialog(e.project, "Create Mask", ws.connectionConfig, initialState) if (dialog.showAndGet()) { val state = dialog.state when (state.type) { @@ -46,22 +47,19 @@ class AddMaskAction : AnAction() { return true } - /** Decides to show action or not */ + /** + * Decides to show the add mask action or not. + * Shows the action if: + * 1. Explorer view is not null + * 2. Item(s) from them same files working set is(are) selected + */ override fun update(e: AnActionEvent) { val view = e.getExplorerView() ?: let { e.presentation.isEnabledAndVisible = false return } - e.presentation.isEnabledAndVisible = getUnits(view).size == 1 - } - - /** Finds files working set units for selected nodes in explorer */ - private fun getUnits(view: FileExplorerView): List { - return view.mySelectedNodesData - .map { it.node } - .filterIsInstance>() - .map { it.unit } - .distinct() + val workingSets = getSelectedNodesWorkingSets(view as ExplorerTreeView<*, *, *>) + e.presentation.isEnabledAndVisible = workingSets.size == 1 } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt new file mode 100644 index 00000000..79fd503c --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt @@ -0,0 +1,190 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.components.service +import com.intellij.openapi.progress.runModalTask +import com.intellij.openapi.ui.showOkNoDialog +import eu.ibagroup.formainframe.analytics.AnalyticsService +import eu.ibagroup.formainframe.analytics.events.FileAction +import eu.ibagroup.formainframe.analytics.events.FileEvent +import eu.ibagroup.formainframe.analytics.events.FileType +import eu.ibagroup.formainframe.common.ui.cleanInvalidateOnExpand +import eu.ibagroup.formainframe.common.ui.showUntilDone +import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.ws.DSMask +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationOperation +import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams +import eu.ibagroup.formainframe.explorer.FilesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.AllocationDialog +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.FileFetchNode +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.utils.castOrNull +import eu.ibagroup.formainframe.utils.clone +import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey +import eu.ibagroup.formainframe.utils.service +import org.zowe.kotlinsdk.Dataset +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.DsnameType + +abstract class AllocateActionBase : AnAction() { + + /** + * Returns null if object doesn't contain anything + * needed for allocation algorithm + */ + private fun Int?.toNullIfZero(): Int? { + return if (this == 0) null else this + } + + /** + * Returns null if objects is empty + * needed for allocation algorithm + */ + private fun String.toNullIfEmpty(): String? { + return this.ifBlank { null } + } + + /** + * Creates the initial dialog state with empty dataset parameters + * @param datasetInfo selected dataset info for further manipulations with its parameters to create initial + * dialog state (is not used for a completely new dataset allocation) + * @return [DatasetAllocationParams] default parameters for a new dataset + */ + protected open fun preProcessState(datasetInfo: Dataset): DatasetAllocationParams { + return DatasetAllocationParams() + } + + /** + * Processes parameters which were set for dataset allocation + * set some parameters to needed values depending on specific fields states + * @param state contains parameters that were set by the user or by default + * @return processed params of dataset for allocation + */ + private fun postProcessState(state: DatasetAllocationParams): DatasetAllocationParams { + if (state.allocationParameters.datasetOrganization == DatasetOrganization.PS) { + state.allocationParameters.directoryBlocks = null + } + if (state.allocationParameters.datasetOrganization == DatasetOrganization.POE) { + state.allocationParameters.datasetOrganization = DatasetOrganization.PO + state.allocationParameters.dsnType = DsnameType.LIBRARY + } + state.allocationParameters.directoryBlocks = state.allocationParameters.directoryBlocks.toNullIfZero() + state.allocationParameters.blockSize = state.allocationParameters.blockSize.toNullIfZero() + state.allocationParameters.averageBlockLength = state.allocationParameters.averageBlockLength.toNullIfZero() + state.allocationParameters.recordLength = state.allocationParameters.recordLength.toNullIfZero() + state.allocationParameters.managementClass = state.allocationParameters.managementClass?.toNullIfEmpty() + state.allocationParameters.storageClass = state.allocationParameters.storageClass?.toNullIfEmpty() + state.allocationParameters.deviceType = state.allocationParameters.deviceType?.toNullIfEmpty() + state.allocationParameters.dataClass = state.allocationParameters.dataClass?.toNullIfEmpty() + state.allocationParameters.volumeSerial = state.allocationParameters.volumeSerial?.toNullIfEmpty() + return state.clone() + } + + /** + * Allocates a new dataset (completely new or the same as the selected one if "Allocate like" is selected) + * @param e action event object which contains links to entities with which should operation be performed + * @param datasetInfo the selected dataset info to be copied to a new one if the new dataset + * is being allocated with the same parameters as the selected one ("Allocate like" is selected) + */ + protected fun doAllocateAction(e: AnActionEvent, datasetInfo: Dataset = Dataset()) { + val view = e.getExplorerView() ?: return + val parentNode = view.mySelectedNodesData[0].node + if (parentNode is ExplorerUnitTreeNodeBase<*, *, *> && parentNode.unit is FilesWorkingSet) { + val workingSet = parentNode.unit + val explorer = workingSet.explorer + val config = workingSet.connectionConfig + if (config != null) { + val initialState = preProcessState(datasetInfo) + showUntilDone( + initialState, + { initState -> AllocationDialog(project = e.project, config, initState) } + ) { + val state = postProcessState(it) + var res = false + runModalTask( + title = "Allocating Data Set ${state.datasetName}", + project = e.project, + cancellable = true + ) { progressIndicator -> + runCatching { + service().trackAnalyticsEvent(FileEvent(FileType.DATASET, FileAction.CREATE)) + val dataOpsManager = explorer.componentManager.service() + dataOpsManager.performOperation( + operation = DatasetAllocationOperation(request = state, connectionConfig = config), + progressIndicator + ) + } + .onSuccess { + res = true + var parentProbablyDSMaskNode: ExplorerTreeNode<*, *>? = parentNode + while (parentProbablyDSMaskNode !is DSMaskNode) { + parentProbablyDSMaskNode = parentProbablyDSMaskNode?.parent ?: break + } + val nodeToClean = parentProbablyDSMaskNode?.castOrNull>() + nodeToClean?.let { cleanInvalidateOnExpand(nodeToClean, view) } + + var nodeCleaned = false + runInEdt { + if ( + showOkNoDialog( + title = "Dataset ${state.datasetName} Has Been Created", + message = "Would you like to add mask \"${state.datasetName}\" to ${workingSet.name}", + project = e.project, + okText = "Yes", + noText = "No" + ) + ) { + val filesWorkingSetConfig = + configCrudable.getByUniqueKey(workingSet.uuid)?.clone() + if (filesWorkingSetConfig != null) { + nodeToClean?.cleanCache(recursively = false, cleanBatchedQuery = true, sendTopic = false) + nodeCleaned = true + + filesWorkingSetConfig.dsMasks.add(DSMask().apply { mask = state.datasetName }) + configCrudable.update(filesWorkingSetConfig) + } + } + + if (!nodeCleaned) { + nodeToClean?.cleanCache(recursively = false, cleanBatchedQuery = true) + } + } + initialState.errorMessage = "" + } + .onFailure { t -> + explorer.reportThrowable(t, e.project) + initialState.errorMessage = t.message ?: t.toString() + } + } + res + } + + } + } + } + + /** + * This method is needed for interface implementation + */ + override fun isDumbAware(): Boolean { + return true + } +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt index 5497b261..9be25db7 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt @@ -11,52 +11,19 @@ package eu.ibagroup.formainframe.explorer.actions import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.service -import com.intellij.openapi.progress.runModalTask -import com.intellij.openapi.ui.Messages -import com.intellij.openapi.ui.showOkNoDialog import com.intellij.util.IconUtil -import eu.ibagroup.formainframe.analytics.AnalyticsService -import eu.ibagroup.formainframe.analytics.events.FileAction -import eu.ibagroup.formainframe.analytics.events.FileEvent -import eu.ibagroup.formainframe.analytics.events.FileType -import eu.ibagroup.formainframe.common.ui.cleanInvalidateOnExpand -import eu.ibagroup.formainframe.common.ui.showUntilDone -import eu.ibagroup.formainframe.config.configCrudable -import eu.ibagroup.formainframe.config.ws.DSMask -import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig -import eu.ibagroup.formainframe.dataops.DataOpsManager -import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes -import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationOperation -import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams -import eu.ibagroup.formainframe.explorer.FilesWorkingSet -import eu.ibagroup.formainframe.explorer.ui.AllocationDialog import eu.ibagroup.formainframe.explorer.ui.DSMaskNode -import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode -import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase import eu.ibagroup.formainframe.explorer.ui.FileExplorerView -import eu.ibagroup.formainframe.explorer.ui.FileFetchNode import eu.ibagroup.formainframe.explorer.ui.FileLikeDatasetNode import eu.ibagroup.formainframe.explorer.ui.FilesWorkingSetNode import eu.ibagroup.formainframe.explorer.ui.LibraryNode import eu.ibagroup.formainframe.explorer.ui.getExplorerView -import eu.ibagroup.formainframe.utils.castOrNull -import eu.ibagroup.formainframe.utils.clone -import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey -import eu.ibagroup.formainframe.utils.service -import org.zowe.kotlinsdk.AllocationUnit -import org.zowe.kotlinsdk.DatasetOrganization -import org.zowe.kotlinsdk.DsnameType -import org.zowe.kotlinsdk.RecordFormat -import org.zowe.kotlinsdk.SpaceUnits /** - * Action class for dataset allocation with parameters chosen by user + * Class that represents dataset allocation action with parameters, defined by a user */ -class AllocateDatasetAction : AnAction() { +class AllocateDatasetAction : AllocateActionBase() { /** * Called when allocate option is chosen from context menu, @@ -87,220 +54,4 @@ class AllocateDatasetAction : AnAction() { e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") } - /** - * This method is needed for interface implementation - */ - override fun isDumbAware(): Boolean { - return true - } -} - -/** - * Allocates dataset - * @param e action event object which contains links to entities with which should operation be performed - * @param initialState contains state/parameters with which dataset should be allocated - */ -private fun doAllocateAction(e: AnActionEvent, initialState: DatasetAllocationParams = DatasetAllocationParams()) { - val view = e.getExplorerView() ?: return - val parentNode = view.mySelectedNodesData[0].node - if (parentNode is ExplorerUnitTreeNodeBase<*, *, *> && parentNode.unit is FilesWorkingSet) { - val workingSet = parentNode.unit - val config = parentNode.unit.connectionConfig - if (config != null) { - showUntilDone(initialState, { initState -> - AllocationDialog(project = e.project, config, initState) - }) { - val state = postProcessState(it) - var res = false - runModalTask( - title = "Allocating Data Set ${state.datasetName}", - project = e.project, - cancellable = true - ) { - runCatching { - service().trackAnalyticsEvent(FileEvent(FileType.DATASET, FileAction.CREATE)) - parentNode.unit.explorer.componentManager.service() - .performOperation( - operation = DatasetAllocationOperation( - request = state, - connectionConfig = config - ), - it - ) - } - .onSuccess { - res = true - var p: ExplorerTreeNode<*, *>? = parentNode - while (p !is DSMaskNode) { - p = p?.parent ?: break - } - val nodeToClean = p?.castOrNull>() - nodeToClean?.let { cleanInvalidateOnExpand(nodeToClean, view) } - - var nodeCleaned = false - runInEdt { - if ( - showOkNoDialog( - title = "Dataset ${state.datasetName} Has Been Created", - message = "Would you like to add mask \"${state.datasetName}\" to ${parentNode.unit.name}", - project = e.project, - okText = "Yes", - noText = "No" - ) - ) { - val filesWorkingSetConfig = - configCrudable.getByUniqueKey(workingSet.uuid)?.clone() - if (filesWorkingSetConfig != null) { - nodeToClean?.cleanCache(recursively = false, cleanBatchedQuery = true, sendTopic = false) - nodeCleaned = true - - filesWorkingSetConfig.dsMasks.add(DSMask().apply { mask = state.datasetName }) - configCrudable.update(filesWorkingSetConfig) - } - } - - if (!nodeCleaned) { - nodeToClean?.cleanCache(recursively = false, cleanBatchedQuery = true) - } - } - initialState.errorMessage = "" - } - .onFailure { t -> - parentNode.explorer.reportThrowable(t, e.project) - initialState.errorMessage = t.message ?: t.toString() - } - } - res - } - - } - } -} - -/** - * Returns null if object doesn't contain anything - * needed for allocation algorithm - */ -private fun Int?.toNullIfZero(): Int? { - return if (this == 0) null else this -} - -/** - * Returns null if objects is empty - * needed for allocation algorithm - */ -private fun String.toNullIfEmpty(): String? { - return if (this.isBlank()) null else this -} - -/** - * Processes parameters which were set for dataset allocation - * set some parameters to needed values depending on specific fields states - * @param state contains parameters that were set by the user or by default - * @return processed params of dataset for allocation - */ -private fun postProcessState(state: DatasetAllocationParams): DatasetAllocationParams { - if (state.allocationParameters.datasetOrganization == DatasetOrganization.PS) { - state.allocationParameters.directoryBlocks = null - } - if (state.allocationParameters.datasetOrganization == DatasetOrganization.POE) { - state.allocationParameters.datasetOrganization = DatasetOrganization.PO - state.allocationParameters.dsnType = DsnameType.LIBRARY - } - state.allocationParameters.directoryBlocks = state.allocationParameters.directoryBlocks.toNullIfZero() - state.allocationParameters.blockSize = state.allocationParameters.blockSize.toNullIfZero() - state.allocationParameters.averageBlockLength = state.allocationParameters.averageBlockLength.toNullIfZero() - state.allocationParameters.recordLength = state.allocationParameters.recordLength.toNullIfZero() - state.allocationParameters.managementClass = state.allocationParameters.managementClass?.toNullIfEmpty() - state.allocationParameters.storageClass = state.allocationParameters.storageClass?.toNullIfEmpty() - state.allocationParameters.deviceType = state.allocationParameters.deviceType?.toNullIfEmpty() - state.allocationParameters.dataClass = state.allocationParameters.dataClass?.toNullIfEmpty() - state.allocationParameters.volumeSerial = state.allocationParameters.volumeSerial?.toNullIfEmpty() - return state.clone() -} - -/** - * Action class for dataset allocation based on existing dataset - */ -class AllocateLikeAction : AnAction() { - - /** - * Called when allocate like option is chosen from context menu, - * runs allocate dataset like operation - */ - override fun actionPerformed(e: AnActionEvent) { - val view = e.getExplorerView() ?: let { - e.presentation.isEnabledAndVisible = false - return - } - val selected = view.mySelectedNodesData - val datasetInfo = (selected[0].attributes as RemoteDatasetAttributes).datasetInfo - val initialState = DatasetAllocationParams().apply { - allocationParameters.datasetOrganization = datasetInfo.datasetOrganization ?: DatasetOrganization.PS - allocationParameters.allocationUnit = spaceUnitsToAllocationUnits(datasetInfo.spaceUnits) ?: AllocationUnit.TRK - allocationParameters.blockSize = datasetInfo.blockSize - allocationParameters.recordLength = datasetInfo.recordLength - allocationParameters.recordFormat = datasetInfo.recordFormat ?: RecordFormat.FB - allocationParameters.volumeSerial = datasetInfo.volumeSerial - allocationParameters.deviceType = datasetInfo.deviceType - allocationParameters.dsnType = datasetInfo.dsnameType - allocationParameters.primaryAllocation = - if (datasetInfo.spaceUnits == SpaceUnits.CYLINDERS) { - (datasetInfo.sizeInTracks ?: 15) / 15 - } else { - datasetInfo.sizeInTracks ?: 100 - } - allocationParameters.secondaryAllocation = allocationParameters.primaryAllocation / 2 - allocationParameters.directoryBlocks = - if (datasetInfo.datasetOrganization == DatasetOrganization.PO || datasetInfo.datasetOrganization == DatasetOrganization.POE) { - allocationParameters.primaryAllocation / 3 - } else { - 0 - } - } - doAllocateAction(e, initialState) - } - - /** - * Transforms info about space units of existing datasets to format that is suitable for allocation operation - * @param spaceUnits space units info of existing dataset - * @return processed info about space units - */ - private fun spaceUnitsToAllocationUnits(spaceUnits: SpaceUnits?): AllocationUnit? { - if (spaceUnits == SpaceUnits.TRACKS) { - return AllocationUnit.TRK - } - if (spaceUnits == SpaceUnits.CYLINDERS) { - return AllocationUnit.CYL - } - if (spaceUnits == SpaceUnits.BLOCKS) { - Messages.showWarningDialog( - "Allocation unit BLK is not supported. It will be changed to TRK.", - "Allocation Unit Will Be Changed" - ) - } - return null - } - - /** - * Determines if dataset allocation is possible for chosen object - */ - override fun update(e: AnActionEvent) { - val view = e.getExplorerView() ?: let { - e.presentation.isEnabledAndVisible = false - return - } - val selected = view.mySelectedNodesData - e.presentation.isEnabledAndVisible = selected.size == 1 - && selected[0].attributes is RemoteDatasetAttributes - && !(selected[0].attributes as RemoteDatasetAttributes).isMigrated - e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") - } - - /** - * This method is needed for interface implementation - */ - override fun isDumbAware(): Boolean { - return true - } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeAction.kt new file mode 100644 index 00000000..289fb195 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeAction.kt @@ -0,0 +1,113 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.ui.Messages +import com.intellij.util.IconUtil +import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes +import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import org.zowe.kotlinsdk.AllocationUnit +import org.zowe.kotlinsdk.Dataset +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.SpaceUnits + +/** + * Class that represents the "Allocate like" action + */ +class AllocateLikeAction : AllocateActionBase() { + + /** + * Transforms info about space units of existing datasets to format that is suitable for allocation operation + * @param spaceUnits space units info of existing dataset + * @return processed info about space units + */ + private fun spaceUnitsToAllocationUnits(spaceUnits: SpaceUnits?): AllocationUnit? { + if (spaceUnits == SpaceUnits.TRACKS) { + return AllocationUnit.TRK + } + if (spaceUnits == SpaceUnits.CYLINDERS) { + return AllocationUnit.CYL + } + if (spaceUnits == SpaceUnits.BLOCKS) { + Messages.showWarningDialog( + "Allocation unit BLK is not supported. It will be changed to TRK.", + "Allocation Unit Will Be Changed" + ) + } + return null + } + + /** + * Creates the initial dialog state with dataset attributes that are the same as the selected one's + * @param datasetInfo selected dataset info to get predefined attributes for a new dataset + * @return [DatasetAllocationParams] attributes of a new dataset that are the same as in the selected one + */ + override fun preProcessState(datasetInfo: Dataset): DatasetAllocationParams { + return DatasetAllocationParams().apply { + allocationParameters.datasetOrganization = datasetInfo.datasetOrganization ?: DatasetOrganization.PS + allocationParameters.allocationUnit = spaceUnitsToAllocationUnits(datasetInfo.spaceUnits) ?: AllocationUnit.TRK + allocationParameters.blockSize = datasetInfo.blockSize + allocationParameters.recordLength = datasetInfo.recordLength + allocationParameters.recordFormat = datasetInfo.recordFormat ?: RecordFormat.FB + allocationParameters.volumeSerial = datasetInfo.volumeSerial + allocationParameters.deviceType = datasetInfo.deviceType + allocationParameters.dsnType = datasetInfo.dsnameType + allocationParameters.primaryAllocation = + if (datasetInfo.spaceUnits == SpaceUnits.CYLINDERS) { + (datasetInfo.sizeInTracks ?: 15) / 15 + } else { + datasetInfo.sizeInTracks ?: 100 + } + allocationParameters.secondaryAllocation = allocationParameters.primaryAllocation / 2 + allocationParameters.directoryBlocks = + if (datasetInfo.datasetOrganization == DatasetOrganization.PO || datasetInfo.datasetOrganization == DatasetOrganization.POE) { + allocationParameters.primaryAllocation / 3 + } else { + 0 + } + } + } + + /** + * Called when allocate like option is chosen from context menu, + * runs "allocate dataset like" operation + */ + override fun actionPerformed(e: AnActionEvent) { + val view = e.getExplorerView() ?: let { + return + } + val selectedNodesData = view.mySelectedNodesData + val datasetInfo = (selectedNodesData[0].attributes as RemoteDatasetAttributes).datasetInfo + doAllocateAction(e, datasetInfo) + } + + /** + * Determines if dataset allocation is possible for chosen object + */ + override fun update(e: AnActionEvent) { + val view = e.getExplorerView() ?: let { + e.presentation.isEnabledAndVisible = false + return + } + val selected = view.mySelectedNodesData + val entityAttributes = selected[0].attributes + e.presentation.isEnabledAndVisible = selected.size == 1 + && entityAttributes is RemoteDatasetAttributes + && !entityAttributes.isMigrated + e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") + } + +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditJobsFilterAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditJobsFilterAction.kt index e36e6ced..35e3cf67 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditJobsFilterAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditJobsFilterAction.kt @@ -10,20 +10,24 @@ package eu.ibagroup.formainframe.explorer.actions +import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.ws.JesWorkingSetConfig import eu.ibagroup.formainframe.config.ws.JobFilterStateWithWS import eu.ibagroup.formainframe.config.ws.JobsFilter -import eu.ibagroup.formainframe.config.ws.JesWorkingSetConfig +import eu.ibagroup.formainframe.explorer.JesWorkingSet import eu.ibagroup.formainframe.explorer.ui.EditJobsFilterDialog +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView import eu.ibagroup.formainframe.explorer.ui.JesExplorerView import eu.ibagroup.formainframe.explorer.ui.JesFilterNode import eu.ibagroup.formainframe.explorer.ui.getExplorerView import eu.ibagroup.formainframe.utils.clone import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey +import eu.ibagroup.formainframe.utils.getSelectedNodesWorkingSets /** Action to edit job filter in JES working set tree view */ -class EditJobsFilterAction : JobsFilterAction() { +class EditJobsFilterAction : AnAction() { /** Save changes when the dialog is fulfilled */ override fun actionPerformed(e: AnActionEvent) { @@ -31,7 +35,8 @@ class EditJobsFilterAction : JobsFilterAction() { val node = view.mySelectedNodesData.getOrNull(0)?.node if (node is JesFilterNode) { - val ws = getUnits(view).firstOrNull() ?: return + val workingSets = getSelectedNodesWorkingSets(view as ExplorerTreeView<*, *, *>) + val ws = workingSets.firstOrNull() ?: return val prefix = node.value.prefix val owner = node.value.owner val jobId = node.value.jobId @@ -57,4 +62,25 @@ class EditJobsFilterAction : JobsFilterAction() { } } + /** + * Decides to show the edit jobs filter action or not. + * Shows the action if: + * 1. Explorer view is not null + * 2. Only one item selected + * 3. The selected item is a [JesFilterNode] + */ + override fun update(e: AnActionEvent) { + val view = e.getExplorerView() ?: let { + e.presentation.isEnabledAndVisible = false + return + } + val selected = view.mySelectedNodesData + if (selected.size != 1) { + e.presentation.isEnabledAndVisible = false + return + } + val node = selected[0].node + e.presentation.isEnabledAndVisible = node is JesFilterNode + } + } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt index 3b484053..fd541a7b 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskAction.kt @@ -97,7 +97,7 @@ class EditMaskAction : AnAction() { if (node is DSMaskNode) MaskState(mask = node.value.mask, type = MaskType.ZOS) else MaskState(mask = (node as UssDirNode).value.path, type = MaskType.USS) val initialStateWithWS = MaskStateWithWS(initialState, parentWS) - val dialog = AddOrEditMaskDialog(e.project, "Edit Mask", initialStateWithWS) + val dialog = AddOrEditMaskDialog(e.project, "Edit Mask", parentWS.connectionConfig, initialStateWithWS) if (dialog.showAndGet()) { val changedMaskState = dialog.state wsConfToUpdate = diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/JobsFilterAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/JobsFilterAction.kt deleted file mode 100644 index 08705152..00000000 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/JobsFilterAction.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This program and the accompanying materials are made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v20.html - * - * SPDX-License-Identifier: EPL-2.0 - * - * Copyright IBA Group 2020 - */ - -package eu.ibagroup.formainframe.explorer.actions - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import eu.ibagroup.formainframe.config.connect.ConnectionConfig -import eu.ibagroup.formainframe.explorer.JesWorkingSet -import eu.ibagroup.formainframe.explorer.ui.* - -/** - * Action to manipulate jobs filter from UI - * @author Uladzislau Kalesnikau - */ -abstract class JobsFilterAction : AnAction() { - - /** - * Is node conforms to the JesFilterNode type - * @param node the node to check - */ - open fun isNodeConformsToType(node: ExplorerTreeNode?): Boolean { - return node is JesFilterNode - } - - /** Decides to show action or not */ - override fun update(e: AnActionEvent) { - val view = e.getExplorerView() ?: let { - e.presentation.isEnabledAndVisible = false - return - } - val selected = view.mySelectedNodesData - val node = selected.getOrNull(0)?.node - e.presentation.isVisible = selected.size == 1 && isNodeConformsToType(node) - } - - /** Finds JES working set units for selected nodes in explorer */ - protected fun getUnits(view: JesExplorerView): List { - return view.mySelectedNodesData.map { it.node } - .filterIsInstance>() - .map { it.unit } - .distinct() - } - -} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt index acf81b25..baab707c 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AddOrEditMaskDialog.kt @@ -19,6 +19,8 @@ import com.intellij.ui.dsl.builder.bindText import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.gridLayout.HorizontalAlign import eu.ibagroup.formainframe.common.ui.StatefulComponent +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.connect.getUsername import eu.ibagroup.formainframe.config.ws.MaskStateWithWS import eu.ibagroup.formainframe.utils.MaskType import eu.ibagroup.formainframe.utils.validateDatasetMask @@ -27,13 +29,22 @@ import eu.ibagroup.formainframe.utils.validateUssMask import eu.ibagroup.formainframe.utils.validateWorkingSetMaskName import java.awt.Dimension import javax.swing.JComponent +import javax.swing.JTextField /** * Dialog to add a new or edit an existing mask */ -class AddOrEditMaskDialog(project: Project?, dialogTitle: String, override var state: MaskStateWithWS) : +class AddOrEditMaskDialog( + project: Project?, + dialogTitle: String, + config: ConnectionConfig?, + override var state: MaskStateWithWS +) : DialogWrapper(project), StatefulComponent { + private lateinit var maskField: JTextField + private val HLQ = if (config != null) getUsername(config) else null + companion object { // TODO: Remove when it becomes possible to mock class constructor with init section. @@ -86,6 +97,12 @@ class AddOrEditMaskDialog(project: Project?, dialogTitle: String, override var s .widthGroup(sameWidthGroup) textField() .bindText(state::mask) + .also { + maskField = it.component + if (maskField.text == "") { + maskField.text = if (HLQ != null) "${HLQ}.*" else "" + } + } .validationOnInput { if (!state.isTypeSelectedManually) { state.type = if (it.text.contains("/")) MaskType.USS else MaskType.ZOS diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt index 4c5dcd9d..51555f11 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt @@ -16,14 +16,17 @@ import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ValidationInfo import com.intellij.ui.SimpleListCellRenderer import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.dsl.builder.* +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.toNullableProperty import com.intellij.ui.dsl.gridLayout.HorizontalAlign import com.intellij.ui.layout.selectedValueMatches import eu.ibagroup.formainframe.common.ui.StatefulDialog import eu.ibagroup.formainframe.config.connect.ConnectionConfig import eu.ibagroup.formainframe.config.connect.getUsername import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams -import eu.ibagroup.formainframe.explorer.config.* +import eu.ibagroup.formainframe.explorer.config.Presets import eu.ibagroup.formainframe.utils.validateDataset import eu.ibagroup.formainframe.utils.validateForBlank import eu.ibagroup.formainframe.utils.validateMemberName @@ -61,7 +64,15 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var row { label("Choose preset: ") .widthGroup(sameWidthLabelsGroup) - comboBox(listOf(Presets.CUSTOM_DATASET, Presets.SEQUENTIAL_DATASET, Presets.PDS_DATASET, Presets.PDS_WITH_EMPTY_MEMBER, Presets.PDS_WITH_SAMPLE_JCL_MEMBER)) + comboBox( + listOf( + Presets.CUSTOM_DATASET, + Presets.SEQUENTIAL_DATASET, + Presets.PDS_DATASET, + Presets.PDS_WITH_EMPTY_MEMBER, + Presets.PDS_WITH_SAMPLE_JCL_MEMBER + ) + ) .bindItem(state::presets.toNullableProperty()) .also { presetsBox = it.component } .widthGroup(sameWidthComboBoxGroup) @@ -72,8 +83,9 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var .widthGroup(sameWidthLabelsGroup) textField() .bindText(state::datasetName) - .also { datasetNameField = it.component - datasetNameField.text = HLQ + .also { + datasetNameField = it.component + datasetNameField.text = "${HLQ}." } .onApply { state.datasetName = state.datasetName.uppercase() } .horizontalAlign(HorizontalAlign.FILL) @@ -84,8 +96,9 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var .widthGroup(sameWidthLabelsGroup) textField() .bindText(state::memberName) - .also { memberNameField = it.component - memberNameField.text = "SAMPLE" + .also { + memberNameField = it.component + memberNameField.text = "SAMPLE" } .onApply { state.memberName = state.memberName.uppercase() } .horizontalAlign(HorizontalAlign.FILL) @@ -285,7 +298,7 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var * @param preset - a chosen preset from UI * @return Void */ - private fun doPresetAssignment(preset : Presets) { + private fun doPresetAssignment(preset: Presets) { val dataContainer = Presets.initDataClass(preset) memberNameField.text = "SAMPLE" datasetOrganizationBox.selectedItem = dataContainer.datasetOrganization @@ -315,8 +328,8 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var blockSizeField, averageBlockLengthField, advancedParametersField - ) ?: validateForBlank(memberNameField) ?: - validateMemberName(memberNameField) + ) ?: validateForBlank(memberNameField) + ?: validateMemberName(memberNameField) } override fun getPreferredFocusedComponent(): JComponent? { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/utils/miscUtils.kt b/src/main/kotlin/eu/ibagroup/formainframe/utils/miscUtils.kt index 743e9a45..9e84f2f9 100755 --- a/src/main/kotlin/eu/ibagroup/formainframe/utils/miscUtils.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/utils/miscUtils.kt @@ -14,6 +14,10 @@ import com.google.gson.Gson import com.intellij.util.containers.minimalElements import com.intellij.util.containers.toArray import eu.ibagroup.formainframe.config.ConfigDeclaration +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.explorer.WorkingSet +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView +import eu.ibagroup.formainframe.explorer.ui.ExplorerUnitTreeNodeBase import java.awt.Component import java.awt.MouseInfo import java.awt.Rectangle @@ -279,3 +283,16 @@ fun Component.isComponentUnderMouse(): Boolean { val bounds = Rectangle(0, 0, width, height) return bounds.contains(location) } + +/** + * Get all distinct working sets for the selected nodes. + * In case the items belong to different working sets, it returns all the distinct working sets + * @param view the view where the nodes are selected + */ +fun > getSelectedNodesWorkingSets(view: ExplorerTreeView<*, *, *>): List { + return view.mySelectedNodesData + .map { it.node } + .filterIsInstance>() + .map { it.unit } + .distinct() +} diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt index 64510360..20fb0932 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ExplorerTestSpec.kt @@ -10,32 +10,11 @@ package eu.ibagroup.formainframe.explorer -import com.intellij.openapi.Disposable import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl -import eu.ibagroup.formainframe.config.ConfigService -import eu.ibagroup.formainframe.config.ConfigStateV2 -import eu.ibagroup.formainframe.config.configCrudable -import eu.ibagroup.formainframe.config.connect.ConnectionConfig -import eu.ibagroup.formainframe.config.makeCrudableWithoutListeners -import eu.ibagroup.formainframe.config.ws.DSMask -import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig -import eu.ibagroup.formainframe.config.ws.UssPath -import eu.ibagroup.formainframe.utils.gson -import eu.ibagroup.formainframe.utils.optional -import eu.ibagroup.formainframe.utils.toMutableList -import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec -import io.kotest.matchers.shouldBe import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.spyk -import io.mockk.unmockkAll -import java.util.* -import java.util.stream.Stream class ExplorerTestSpec : ShouldSpec({ beforeSpec { @@ -53,274 +32,6 @@ class ExplorerTestSpec : ShouldSpec({ afterSpec { clearAllMocks() } - context("explorer module: FilesWorkingSetImpl") { - - context("addUssPath") { - val mockedCrud = spyk(makeCrudableWithoutListeners(false) { ConfigStateV2() }) - val uuid1 = "uuid1" - mockkObject(ConfigService) - every { ConfigService.instance.crudable } returns mockk() - every { configCrudable } returns mockk() - mockkObject(gson) - - val mockedFilesWSConfig = mockk() - every { mockedFilesWSConfig.uuid } returns uuid1 - every { mockedFilesWSConfig.name } returns "filesWSuuid1" - every { mockedFilesWSConfig.connectionConfigUuid } returns "connUuid" - every { mockedFilesWSConfig.dsMasks } returns mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())) - every { mockedFilesWSConfig.ussPaths } returns mutableListOf() - - every { gson.toJson(any() as FilesWorkingSetConfig) } returns "mocked_config_to_copy" - - val clonedConfig = FilesWorkingSetConfig( - uuid1, "filesWSuuid1", "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf() - ) - every { gson.fromJson(any() as String, FilesWorkingSetConfig::class.java) } returns clonedConfig - mockkObject(clonedConfig) - - every { mockedCrud.getAll(FilesWorkingSetConfig::class.java) } returns Stream.of( - FilesWorkingSetConfig( - uuid1, - "filesWSuuid1", - "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf(UssPath("/u/test1")) - ), - FilesWorkingSetConfig( - uuid1, - "filesWSuuid1", - "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf() - ) - ) - - fun getMockedFilesWorkingSetConfigNotNull(): FilesWorkingSetConfig { - return mockedFilesWSConfig - } - - fun getMockedFilesWorkingSetConfigNull(): FilesWorkingSetConfig? { - return null - } - - val mockedFileExplorer = - mockk>() - val mockedDisposable = mockk() - val expectedValues = mockedCrud.getAll(FilesWorkingSetConfig::class.java).toMutableList() - - var actual1: Optional? = null - every { configCrudable.update(any() as FilesWorkingSetConfig) } answers { - actual1 = - FilesWorkingSetConfig( - uuid1, - "filesWSuuid1", - "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf(UssPath("/u/test1")) - ).optional - actual1 - } - - // addUssPath when clone and collection.add succeeds - should("add USS path to a config") { - val filesWorkingSetImpl1 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, - mockedDisposable - ) - ) - - every { clonedConfig.ussPaths.add(any() as UssPath) } answers { - true - } - - filesWorkingSetImpl1.addUssPath(UssPath("/u/test1")) - - val expected = expectedValues[0].optional - - assertSoftly { - actual1 shouldBe expected - } - } - - // addUssPath when clone succeeds but collection.add is not - should("add USS path to a config if collection.add is not succeeded") { - val filesWorkingSetImpl1 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, - mockedDisposable - ) - ) - every { clonedConfig.ussPaths.add(any() as UssPath) } answers { - false - } - - filesWorkingSetImpl1.addUssPath(UssPath("/u/test1")) - val actual2 = clonedConfig.optional - val expected = expectedValues[1].optional - - assertSoftly { - actual2 shouldBe expected - } - } - - // addUssPath with null config - should("not add USS path to a config as working set config is null") { - val filesWorkingSetImpl2 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNull() }, - mockedDisposable - ) - ) - - filesWorkingSetImpl2.addUssPath(UssPath("/u/test2")) - val actual3 = clonedConfig.optional - val expected = expectedValues[1].optional - - assertSoftly { - actual3 shouldBe expected - } - } - } - - context("removeUssPath") { - val mockedCrud = spyk(makeCrudableWithoutListeners(false) { ConfigStateV2() }) - val uuid1 = "uuid1" - mockkObject(ConfigService) - every { ConfigService.instance.crudable } returns mockk() - every { configCrudable } returns mockk() - mockkObject(gson) - - val mockedFilesWSConfig = mockk() - every { mockedFilesWSConfig.uuid } returns uuid1 - every { mockedFilesWSConfig.name } returns "filesWSuuid1" - every { mockedFilesWSConfig.connectionConfigUuid } returns "connUuid" - every { mockedFilesWSConfig.dsMasks } returns mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())) - every { mockedFilesWSConfig.ussPaths } returns mutableListOf(UssPath("/u/uss_path_to_remove")) - - every { gson.toJson(any() as FilesWorkingSetConfig) } returns "mocked_config_to_copy" - - val clonedConfig = FilesWorkingSetConfig( - uuid1, - "filesWSuuid1", - "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf(UssPath("/u/uss_path_to_remove")) - ) - every { gson.fromJson(any() as String, FilesWorkingSetConfig::class.java) } returns clonedConfig - mockkObject(clonedConfig) - - every { mockedCrud.getAll(FilesWorkingSetConfig::class.java) } returns Stream.of( - FilesWorkingSetConfig( - uuid1, "filesWSuuid1", "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf() - ), - FilesWorkingSetConfig( - uuid1, "filesWSuuid1", "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf(UssPath("/u/uss_path_to_remove")) - ) - ) - - fun getMockedFilesWorkingSetConfigNotNull(): FilesWorkingSetConfig { - return mockedFilesWSConfig - } - - fun getMockedFilesWorkingSetConfigNull(): FilesWorkingSetConfig? { - return null - } - - val mockedFileExplorer = - mockk>() - val mockedDisposable = mockk() - val expectedValues = mockedCrud.getAll(FilesWorkingSetConfig::class.java).toMutableList() - - var actual4: Optional? = null - every { configCrudable.update(any() as FilesWorkingSetConfig) } answers { - actual4 = - FilesWorkingSetConfig( - uuid1, - "filesWSuuid1", - "connUuid", - mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), - mutableListOf() - ).optional - actual4 - } - - // removeUssPath when clone and collection.remove succeeds - should("remove USS path from a config") { - val filesWorkingSetImpl1 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, - mockedDisposable - ) - ) - - every { clonedConfig.ussPaths.remove(any() as UssPath) } answers { - true - } - - filesWorkingSetImpl1.removeUssPath(UssPath("/u/uss_path_to_remove")) - - val expected = expectedValues[0].optional - - assertSoftly { - actual4 shouldBe expected - } - } - - // removeUssPath when clone succeeds but collection.remove is not - should("remove USS path from a config if collection.remove is not succeeded") { - val filesWorkingSetImpl1 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, - mockedDisposable - ) - ) - every { clonedConfig.ussPaths.remove(any() as UssPath) } answers { - false - } - - filesWorkingSetImpl1.removeUssPath(UssPath("/u/uss_path_to_remove")) - val actual5 = clonedConfig.optional - val expected = expectedValues[1].optional - - assertSoftly { - actual5 shouldBe expected - } - } - - // removeUssPath with null config - should("not remove USS path from a config as working set config is null") { - val filesWorkingSetImpl2 = spyk( - FilesWorkingSetImpl( - uuid1, - mockedFileExplorer, { getMockedFilesWorkingSetConfigNull() }, - mockedDisposable - ) - ) - - filesWorkingSetImpl2.removeUssPath(UssPath("/u/uss_path_to_remove")) - val actual6 = clonedConfig.optional - val expected = expectedValues[1].optional - - assertSoftly { - actual6 shouldBe expected - } - } - } - - unmockkAll() - } context("explorer module: ui/ExplorerPasteProvider") { // performPaste @@ -346,10 +57,6 @@ class ExplorerTestSpec : ShouldSpec({ should("select 'Use binary mode' for a file") {} should("unselect 'Use binary mode' for a file") {} } - context("explorer module: actions/AllocateDatasetAction") { - // actionPerformed - should("perform dataset allocate with default parameters") {} - } context("explorer module: actions/AddMemberAction") { // actionPerformed should("perform dataset member allocate with default parameters") {} diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/FilesWorkingSetImplTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/FilesWorkingSetImplTestSpec.kt new file mode 100644 index 00000000..95f3ee22 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/FilesWorkingSetImplTestSpec.kt @@ -0,0 +1,324 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer + +import com.intellij.openapi.Disposable +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.config.ConfigService +import eu.ibagroup.formainframe.config.ConfigStateV2 +import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.makeCrudableWithoutListeners +import eu.ibagroup.formainframe.config.ws.DSMask +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.config.ws.UssPath +import eu.ibagroup.formainframe.utils.gson +import eu.ibagroup.formainframe.utils.optional +import eu.ibagroup.formainframe.utils.toMutableList +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.spyk +import io.mockk.unmockkAll +import java.util.* +import java.util.stream.Stream + +class FilesWorkingSetImplTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: FilesWorkingSetImpl") { + + context("addUssPath") { + val mockedCrud = spyk(makeCrudableWithoutListeners(false) { ConfigStateV2() }) + val uuid1 = "uuid1" + mockkObject(ConfigService) + every { ConfigService.instance.crudable } returns mockk() + every { configCrudable } returns mockk() + mockkObject(gson) + + val mockedFilesWSConfig = mockk() + every { mockedFilesWSConfig.uuid } returns uuid1 + every { mockedFilesWSConfig.name } returns "filesWSuuid1" + every { mockedFilesWSConfig.connectionConfigUuid } returns "connUuid" + every { mockedFilesWSConfig.dsMasks } returns mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())) + every { mockedFilesWSConfig.ussPaths } returns mutableListOf() + + every { gson.toJson(any() as FilesWorkingSetConfig) } returns "mocked_config_to_copy" + + val clonedConfig = FilesWorkingSetConfig( + uuid1, "filesWSuuid1", "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf() + ) + every { gson.fromJson(any() as String, FilesWorkingSetConfig::class.java) } returns clonedConfig + mockkObject(clonedConfig) + + every { mockedCrud.getAll(FilesWorkingSetConfig::class.java) } returns Stream.of( + FilesWorkingSetConfig( + uuid1, + "filesWSuuid1", + "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf(UssPath("/u/test1")) + ), + FilesWorkingSetConfig( + uuid1, + "filesWSuuid1", + "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf() + ) + ) + + fun getMockedFilesWorkingSetConfigNotNull(): FilesWorkingSetConfig { + return mockedFilesWSConfig + } + + fun getMockedFilesWorkingSetConfigNull(): FilesWorkingSetConfig? { + return null + } + + val mockedFileExplorer = + mockk>() + val mockedDisposable = mockk() + val expectedValues = mockedCrud.getAll(FilesWorkingSetConfig::class.java).toMutableList() + + var actual1: Optional? = null + every { configCrudable.update(any() as FilesWorkingSetConfig) } answers { + actual1 = + FilesWorkingSetConfig( + uuid1, + "filesWSuuid1", + "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf(UssPath("/u/test1")) + ).optional + actual1 + } + + // addUssPath when clone and collection.add succeeds + should("add USS path to a config") { + val filesWorkingSetImpl1 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, + mockedDisposable + ) + ) + + every { clonedConfig.ussPaths.add(any() as UssPath) } answers { + true + } + + filesWorkingSetImpl1.addUssPath(UssPath("/u/test1")) + + val expected = expectedValues[0].optional + + assertSoftly { + actual1 shouldBe expected + } + } + + // addUssPath when clone succeeds but collection.add is not + should("add USS path to a config if collection.add is not succeeded") { + val filesWorkingSetImpl1 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, + mockedDisposable + ) + ) + every { clonedConfig.ussPaths.add(any() as UssPath) } answers { + false + } + + filesWorkingSetImpl1.addUssPath(UssPath("/u/test1")) + val actual2 = clonedConfig.optional + val expected = expectedValues[1].optional + + assertSoftly { + actual2 shouldBe expected + } + } + + // addUssPath with null config + should("not add USS path to a config as working set config is null") { + val filesWorkingSetImpl2 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNull() }, + mockedDisposable + ) + ) + + filesWorkingSetImpl2.addUssPath(UssPath("/u/test2")) + val actual3 = clonedConfig.optional + val expected = expectedValues[1].optional + + assertSoftly { + actual3 shouldBe expected + } + } + } + + context("removeUssPath") { + val mockedCrud = spyk(makeCrudableWithoutListeners(false) { ConfigStateV2() }) + val uuid1 = "uuid1" + mockkObject(ConfigService) + every { ConfigService.instance.crudable } returns mockk() + every { configCrudable } returns mockk() + mockkObject(gson) + + val mockedFilesWSConfig = mockk() + every { mockedFilesWSConfig.uuid } returns uuid1 + every { mockedFilesWSConfig.name } returns "filesWSuuid1" + every { mockedFilesWSConfig.connectionConfigUuid } returns "connUuid" + every { mockedFilesWSConfig.dsMasks } returns mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())) + every { mockedFilesWSConfig.ussPaths } returns mutableListOf(UssPath("/u/uss_path_to_remove")) + + every { gson.toJson(any() as FilesWorkingSetConfig) } returns "mocked_config_to_copy" + + val clonedConfig = FilesWorkingSetConfig( + uuid1, + "filesWSuuid1", + "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf(UssPath("/u/uss_path_to_remove")) + ) + every { gson.fromJson(any() as String, FilesWorkingSetConfig::class.java) } returns clonedConfig + mockkObject(clonedConfig) + + every { mockedCrud.getAll(FilesWorkingSetConfig::class.java) } returns Stream.of( + FilesWorkingSetConfig( + uuid1, "filesWSuuid1", "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf() + ), + FilesWorkingSetConfig( + uuid1, "filesWSuuid1", "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf(UssPath("/u/uss_path_to_remove")) + ) + ) + + fun getMockedFilesWorkingSetConfigNotNull(): FilesWorkingSetConfig { + return mockedFilesWSConfig + } + + fun getMockedFilesWorkingSetConfigNull(): FilesWorkingSetConfig? { + return null + } + + val mockedFileExplorer = + mockk>() + val mockedDisposable = mockk() + val expectedValues = mockedCrud.getAll(FilesWorkingSetConfig::class.java).toMutableList() + + var actual4: Optional? = null + every { configCrudable.update(any() as FilesWorkingSetConfig) } answers { + actual4 = + FilesWorkingSetConfig( + uuid1, + "filesWSuuid1", + "connUuid", + mutableListOf(DSMask("ZOSMFAD.*", mutableListOf())), + mutableListOf() + ).optional + actual4 + } + + // removeUssPath when clone and collection.remove succeeds + should("remove USS path from a config") { + val filesWorkingSetImpl1 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, + mockedDisposable + ) + ) + + every { clonedConfig.ussPaths.remove(any() as UssPath) } answers { + true + } + + filesWorkingSetImpl1.removeUssPath(UssPath("/u/uss_path_to_remove")) + + val expected = expectedValues[0].optional + + assertSoftly { + actual4 shouldBe expected + } + } + + // removeUssPath when clone succeeds but collection.remove is not + should("remove USS path from a config if collection.remove is not succeeded") { + val filesWorkingSetImpl1 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNotNull() }, + mockedDisposable + ) + ) + every { clonedConfig.ussPaths.remove(any() as UssPath) } answers { + false + } + + filesWorkingSetImpl1.removeUssPath(UssPath("/u/uss_path_to_remove")) + val actual5 = clonedConfig.optional + val expected = expectedValues[1].optional + + assertSoftly { + actual5 shouldBe expected + } + } + + // removeUssPath with null config + should("not remove USS path from a config as working set config is null") { + val filesWorkingSetImpl2 = spyk( + FilesWorkingSetImpl( + uuid1, + mockedFileExplorer, { getMockedFilesWorkingSetConfigNull() }, + mockedDisposable + ) + ) + + filesWorkingSetImpl2.removeUssPath(UssPath("/u/uss_path_to_remove")) + val actual6 = clonedConfig.optional + val expected = expectedValues[1].optional + + assertSoftly { + actual6 shouldBe expected + } + } + } + + unmockkAll() + } +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetActionTestSpec.kt new file mode 100644 index 00000000..acd8af68 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetActionTestSpec.kt @@ -0,0 +1,845 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.Presentation +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ComponentManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.showOkNoDialog +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.analytics.AnalyticsService +import eu.ibagroup.formainframe.analytics.events.AnalyticsEvent +import eu.ibagroup.formainframe.common.ui.StatefulDialog +import eu.ibagroup.formainframe.common.ui.cleanInvalidateOnExpand +import eu.ibagroup.formainframe.common.ui.showUntilDone +import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.Operation +import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.FilesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.FileLikeDatasetNode +import eu.ibagroup.formainframe.explorer.ui.FilesWorkingSetNode +import eu.ibagroup.formainframe.explorer.ui.JobNode +import eu.ibagroup.formainframe.explorer.ui.LibraryNode +import eu.ibagroup.formainframe.explorer.ui.NodeData +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.testServiceImpl.TestAnalyticsServiceImpl +import eu.ibagroup.formainframe.utils.service +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.jupiter.api.fail +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.DsnameType +import java.util.* +import javax.swing.Icon +import javax.swing.SwingUtilities +import kotlin.reflect.KFunction + +class AllocateDatasetActionTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: actions/AllocateDatasetAction") { + val anActionEventMock = mockk() + val viewMock = mockk() + val allocateDsActionInst = AllocateDatasetAction() + + context("actionPerformed") { + var isCleanInvalidateOnExpandTriggered = false + var isAnalitycsTracked = false + var isThrowableReported = false + val filesWorkingSetConfigMock = mockk() + val dataOpsManagerMock = mockk() + val componentManagerMock = mockk() + val explorerMock = mockk>() + + val analyticsService = + ApplicationManager.getApplication().service() as TestAnalyticsServiceImpl + analyticsService.testInstance = object : TestAnalyticsServiceImpl() { + override fun trackAnalyticsEvent(event: AnalyticsEvent) { + isAnalitycsTracked = true + super.trackAnalyticsEvent(event) + } + } + + beforeEach { + isCleanInvalidateOnExpandTriggered = false + isAnalitycsTracked = false + isThrowableReported = false + + every { anActionEventMock.getExplorerView() } returns viewMock + every { anActionEventMock.project } returns mockk() + + every { explorerMock.componentManager } returns componentManagerMock + + every { + explorerMock.reportThrowable(any(), any()) + } answers { + isThrowableReported = true + } + + every { + componentManagerMock.hint(DataOpsManager::class).getService(DataOpsManager::class.java) + } returns dataOpsManagerMock + + val cleanInvalidateOnExpandMock: ( + node: ExplorerTreeNode<*, *>, + view: ExplorerTreeView + ) -> Unit = ::cleanInvalidateOnExpand + mockkStatic(cleanInvalidateOnExpandMock as KFunction<*>) + every { + cleanInvalidateOnExpandMock(any>(), any>()) + } answers { + isCleanInvalidateOnExpandTriggered = true + } + } + + afterEach { + clearAllMocks() + unmockkAll() + } + + should("perform allocate PDS dataset action creating a new dataset mask") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (isSendTopic) { + fail("cleanCache should not send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate PS dataset action creating a new dataset mask") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + initState.allocationParameters.datasetOrganization = DatasetOrganization.PS + initState.allocationParameters.managementClass = "test" + initState.allocationParameters.storageClass = "test" + initState.allocationParameters.deviceType = "test" + initState.allocationParameters.dataClass = "test" + initState.allocationParameters.volumeSerial = "test" + initState.allocationParameters.directoryBlocks = 1 + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (isSendTopic) { + fail("cleanCache should not send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + assertSoftly { initState.allocationParameters.directoryBlocks shouldBe null } + } + } + should("perform allocate PO-E dataset action creating a new dataset mask") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + initState.allocationParameters.datasetOrganization = DatasetOrganization.PS + initState.allocationParameters.managementClass = "" + initState.allocationParameters.directoryBlocks = 0 + initState.allocationParameters.datasetOrganization = DatasetOrganization.POE + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (isSendTopic) { + fail("cleanCache should not send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + assertSoftly { initState.allocationParameters.datasetOrganization shouldBe DatasetOrganization.PO } + assertSoftly { initState.allocationParameters.dsnType shouldBe DsnameType.LIBRARY } + } + } + should("perform allocate dataset action without creating a new dataset mask") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (!isSendTopic) { + fail("cleanCache should send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + false + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe false } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate dataset action without refreshing dataset mask as the selected node is a working set") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { nodeMock.parent } returns null + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe false } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate dataset action creating new dataset mask without refreshing the existing on as the connection config is not found") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isUpdateOnConfigCrudableCalled = false + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.ofNullable(null) + + every { nodeMock.parent } returns null + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + every { configCrudable.update(any(), any()) } answers { + isUpdateOnConfigCrudableCalled = true + Optional.of(mockk()) + } + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe false } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isUpdateOnConfigCrudableCalled shouldBe false } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate dataset action with failure on operation performing") { + val workingSetMock = mockk() + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + lateinit var initState: DatasetAllocationParams + val exceptionMsg = "test exception" + var isShowUntilDoneSucceeded = false + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + throw Exception(exceptionMsg) + } + every { workingSetMock.explorer } returns explorerMock + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe false } + assertSoftly { isShowUntilDoneSucceeded shouldBe false } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isThrowableReported shouldBe true } + assertSoftly { initState.errorMessage shouldBe exceptionMsg } + } + } + } + context("update") { + val presentationMock = mockk() + var isPresentationEnabledAndVisible = false + + beforeEach { + every { + presentationMock.isEnabledAndVisible = any() + } answers { + isPresentationEnabledAndVisible = firstArg() + } + every { presentationMock.icon = any() } just Runs + every { anActionEventMock.presentation } returns presentationMock + } + afterEach { + clearAllMocks() + unmockkAll() + } + + should("show the action on update function is triggered for LibraryNode") { + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe true } + } + should("show the action on update function is triggered for FilesWorkingSetNode") { + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe true } + } + should("show the action on update function is triggered for DSMaskNode") { + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe true } + } + should("show the action on update function is triggered for FileLikeDatasetNode") { + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe true } + } + should("not show the action on update function is triggered for JobNode") { + val nodeMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, null) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + should("not show the action on update function is triggered without selected node") { + val selectedNodesData = listOf>() + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + should("not show the action on update function is triggered outside the file explorer view") { + every { anActionEventMock.getExplorerView() } returns null + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + } + } +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeActionTestSpec.kt new file mode 100644 index 00000000..bdb4f589 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateLikeActionTestSpec.kt @@ -0,0 +1,650 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.explorer.actions + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.Presentation +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ComponentManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages.showWarningDialog +import com.intellij.openapi.ui.showOkNoDialog +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.analytics.AnalyticsService +import eu.ibagroup.formainframe.analytics.events.AnalyticsEvent +import eu.ibagroup.formainframe.common.ui.StatefulDialog +import eu.ibagroup.formainframe.common.ui.cleanInvalidateOnExpand +import eu.ibagroup.formainframe.common.ui.showUntilDone +import eu.ibagroup.formainframe.config.configCrudable +import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.Operation +import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes +import eu.ibagroup.formainframe.dataops.attributes.RemoteJobAttributes +import eu.ibagroup.formainframe.dataops.operations.DatasetAllocationParams +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.FilesWorkingSet +import eu.ibagroup.formainframe.explorer.ui.DSMaskNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeNode +import eu.ibagroup.formainframe.explorer.ui.ExplorerTreeView +import eu.ibagroup.formainframe.explorer.ui.FileExplorerView +import eu.ibagroup.formainframe.explorer.ui.JobNode +import eu.ibagroup.formainframe.explorer.ui.LibraryNode +import eu.ibagroup.formainframe.explorer.ui.NodeData +import eu.ibagroup.formainframe.explorer.ui.getExplorerView +import eu.ibagroup.formainframe.testServiceImpl.TestAnalyticsServiceImpl +import eu.ibagroup.formainframe.utils.service +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.junit.jupiter.api.fail +import org.zowe.kotlinsdk.Dataset +import org.zowe.kotlinsdk.DatasetOrganization +import org.zowe.kotlinsdk.RecordFormat +import org.zowe.kotlinsdk.SpaceUnits +import java.util.* +import javax.swing.Icon +import javax.swing.SwingUtilities +import kotlin.reflect.KFunction + +class AllocateLikeActionTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + context("explorer module: actions/AllocateLikeAction") { + val anActionEventMock = mockk() + val allocateDsActionInst = AllocateLikeAction() + + context("actionPerformed") { + var isCleanInvalidateOnExpandTriggered = false + var isAnalitycsTracked = false + var isThrowableReported = false + val filesWorkingSetConfigMock = mockk() + val dataOpsManagerMock = mockk() + val componentManagerMock = mockk() + val explorerMock = mockk>() + + val analyticsService = + ApplicationManager.getApplication().service() as TestAnalyticsServiceImpl + analyticsService.testInstance = object : TestAnalyticsServiceImpl() { + override fun trackAnalyticsEvent(event: AnalyticsEvent) { + isAnalitycsTracked = true + super.trackAnalyticsEvent(event) + } + } + + beforeEach { + isCleanInvalidateOnExpandTriggered = false + isAnalitycsTracked = false + isThrowableReported = false + + every { anActionEventMock.project } returns mockk() + + every { explorerMock.componentManager } returns componentManagerMock + + every { + explorerMock.reportThrowable(any(), any()) + } answers { + isThrowableReported = true + } + + every { + componentManagerMock.hint(DataOpsManager::class).getService(DataOpsManager::class.java) + } returns dataOpsManagerMock + + val cleanInvalidateOnExpandMock: ( + node: ExplorerTreeNode<*, *>, + view: ExplorerTreeView + ) -> Unit = ::cleanInvalidateOnExpand + mockkStatic(cleanInvalidateOnExpandMock as KFunction<*>) + every { + cleanInvalidateOnExpandMock(any>(), any>()) + } answers { + isCleanInvalidateOnExpandTriggered = true + } + } + + afterEach { + clearAllMocks() + unmockkAll() + } + + should("perform allocate PS dataset action without creating a new dataset mask") { + val viewMock = mockk() + val workingSetMock = mockk() + val nodeMock = mockk() + val dsInfo = Dataset() + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isShowUntilDoneSucceeded = false + + every { anActionEventMock.getExplorerView() } returns viewMock + every { dsAttributesMock.datasetInfo } returns dsInfo + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (!isSendTopic) { + fail("cleanCache should send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + false + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate PDS dataset with TRACKS action without creating a new dataset mask") { + val viewMock = mockk() + val workingSetMock = mockk() + val nodeMock = mockk() + val dsInfo = Dataset( + datasetOrganization = DatasetOrganization.PO, + spaceUnits = SpaceUnits.TRACKS, + recordFormat = RecordFormat.F, + sizeInTracks = 10 + ) + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isShowUntilDoneSucceeded = false + + every { anActionEventMock.getExplorerView() } returns viewMock + every { dsAttributesMock.datasetInfo } returns dsInfo + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (!isSendTopic) { + fail("cleanCache should send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + false + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate PDS/E dataset with CYLINDERS action without creating a new dataset mask") { + val viewMock = mockk() + val workingSetMock = mockk() + val nodeMock = mockk() + val dsInfo = Dataset( + datasetOrganization = DatasetOrganization.POE, + spaceUnits = SpaceUnits.CYLINDERS, + recordFormat = RecordFormat.F, + sizeInTracks = 30 + ) + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isShowUntilDoneSucceeded = false + + every { anActionEventMock.getExplorerView() } returns viewMock + every { dsAttributesMock.datasetInfo } returns dsInfo + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (!isSendTopic) { + fail("cleanCache should send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + false + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("perform allocate PS dataset with BLOCKS action without creating a new dataset mask, changing BLOCKS to TRACKS") { + val viewMock = mockk() + val workingSetMock = mockk() + val nodeMock = mockk() + val dsInfo = Dataset(spaceUnits = SpaceUnits.BLOCKS) + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + val dsMaskNodeMock = mockk() + lateinit var initState: DatasetAllocationParams + var isOperationPerformed = false + var isCleanCacheTriggered = false + var isShowUntilDoneSucceeded = false + var isBlocksChangedToTracks = false + + every { anActionEventMock.getExplorerView() } returns viewMock + every { dsAttributesMock.datasetInfo } returns dsInfo + + val showWarningDialogMock: (String, String) -> Unit = ::showWarningDialog + mockkStatic(showWarningDialogMock as KFunction<*>) + every { + hint(Unit::class) + showWarningDialogMock(any(), any()) + } answers { + isBlocksChangedToTracks = true + } + + val showUntilDoneMockk: ( + DatasetAllocationParams, + (DatasetAllocationParams) -> StatefulDialog, + (DatasetAllocationParams) -> Boolean + ) -> DatasetAllocationParams? = ::showUntilDone + mockkStatic(showUntilDoneMockk as KFunction<*>) + every { + hint(DatasetAllocationParams::class) + showUntilDoneMockk( + any(), + any<(DatasetAllocationParams) -> StatefulDialog>(), + any<(DatasetAllocationParams) -> Boolean>() + ) + } answers { + initState = firstArg() + val thirdBlockResult = thirdArg<(DatasetAllocationParams) -> Boolean>() + isShowUntilDoneSucceeded = thirdBlockResult(initState) + initState + } + + mockkObject(configCrudable) + every { + configCrudable.getByUniqueKey(any(), any()) + } returns Optional.of(filesWorkingSetConfigMock) + + every { + dsMaskNodeMock.cleanCache(any(), any(), any(), any()) + } answers { + isCleanCacheTriggered = true + val isSendTopic = lastArg() + if (!isSendTopic) { + fail("cleanCache should send topic in this testcase") + } + } + every { nodeMock.parent } returns dsMaskNodeMock + every { nodeMock.hint(FilesWorkingSet::class).unit } returns workingSetMock + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { workingSetMock.name } returns "test" + every { workingSetMock.uuid } returns "test" + every { workingSetMock.hint(ConnectionConfig::class).connectionConfig } returns mockk() + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + every { workingSetMock.explorer } returns explorerMock + + val showOkNoDialogMock: ( + String, + String, + Project?, + String, + String, + Icon? + ) -> Boolean = ::showOkNoDialog + mockkStatic(showOkNoDialogMock as KFunction<*>) + every { + hint(Boolean::class) + showOkNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + false + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe true } + assertSoftly { isShowUntilDoneSucceeded shouldBe true } + assertSoftly { isAnalitycsTracked shouldBe true } + assertSoftly { isOperationPerformed shouldBe true } + assertSoftly { isCleanCacheTriggered shouldBe true } + assertSoftly { isBlocksChangedToTracks shouldBe true } + assertSoftly { isThrowableReported shouldBe false } + assertSoftly { initState.errorMessage shouldBe "" } + } + } + should("not perform 'allocate like' action as the file explorer view is not found") { + var isOperationPerformed = false + + every { anActionEventMock.getExplorerView() } returns null + + every { + dataOpsManagerMock.hint(Boolean::class).performOperation(any>(), any()) + } answers { + isOperationPerformed = true + true + } + + allocateDsActionInst.actionPerformed(anActionEventMock) + + // Pause to wait until all EDT events are finished + SwingUtilities.invokeAndWait { + assertSoftly { isCleanInvalidateOnExpandTriggered shouldBe false } + assertSoftly { isAnalitycsTracked shouldBe false } + assertSoftly { isOperationPerformed shouldBe false } + assertSoftly { isThrowableReported shouldBe false } + } + } + } + context("update") { + val presentationMock = mockk() + var isPresentationEnabledAndVisible = false + + beforeEach { + every { + presentationMock.isEnabledAndVisible = any() + } answers { + isPresentationEnabledAndVisible = firstArg() + } + every { presentationMock.icon = any() } just Runs + every { anActionEventMock.presentation } returns presentationMock + } + afterEach { + clearAllMocks() + unmockkAll() + } + + should("show the 'allocate like' action on update function is triggered for LibraryNode") { + val viewMock = mockk() + val nodeMock = mockk() + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + + every { dsAttributesMock.isMigrated } returns false + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe true } + } + should("not show the 'allocate like' action as there are more than one nodes selected") { + val viewMock = mockk() + val nodeMock = mockk() + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock, nodeDataMock) + + every { dsAttributesMock.isMigrated } returns false + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + should("not show the 'allocate like' action as the selected dataset is migrated") { + val viewMock = mockk() + val nodeMock = mockk() + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + + every { dsAttributesMock.isMigrated } returns true + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + should("not show the 'allocate like' action as the selected node is not a dataset") { + val viewMock = mockk() + val nodeMock = mockk() + val dsAttributesMock = mockk() + val nodeDataMock = NodeData(nodeMock, null, dsAttributesMock) + val selectedNodesData = listOf(nodeDataMock) + + every { viewMock.mySelectedNodesData } returns selectedNodesData + every { anActionEventMock.getExplorerView() } returns viewMock + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + should("not show the 'allocate like' action as the view is not a file explorer view") { + every { anActionEventMock.getExplorerView() } returns null + + allocateDsActionInst.update(anActionEventMock) + + assertSoftly { isPresentationEnabledAndVisible shouldBe false } + } + } + } +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt index d2fc30da..77f38c7c 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/actions/EditMaskActionTestSpec.kt @@ -16,6 +16,7 @@ import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl import eu.ibagroup.formainframe.config.ConfigService import eu.ibagroup.formainframe.config.connect.ConnectionConfig +import eu.ibagroup.formainframe.config.connect.CredentialService import eu.ibagroup.formainframe.config.ws.DSMask import eu.ibagroup.formainframe.config.ws.FilesWorkingSetConfig import eu.ibagroup.formainframe.config.ws.MaskState @@ -68,6 +69,7 @@ class EditMaskActionTestSpec : ShouldSpec({ val explorerTreeNodeMock = mockk>() val filesWorkingSetMock = mockk() val configServiceMock = mockk() + val credentialServiceMock = mockk() val crudableMock = mockk() val filesWorkingSetConfigMock = mockk() @@ -86,6 +88,7 @@ class EditMaskActionTestSpec : ShouldSpec({ every { anActionEventMock.getExplorerView() } returns fileExplorerViewMock every { filesWorkingSetMock.uuid } returns uuid + every { filesWorkingSetMock.connectionConfig } returns null every { explorerTreeNodeMock.value } returns filesWorkingSetMock every { @@ -96,6 +99,10 @@ class EditMaskActionTestSpec : ShouldSpec({ every { ConfigService.instance } returns configServiceMock every { configServiceMock.crudable } returns crudableMock + mockkObject(CredentialService) + every { CredentialService.instance } returns credentialServiceMock + every { credentialServiceMock.getUsernameByKey(any()) } returns "test" + every { anActionEventMock.project } returns mockk() mockkObject(AddOrEditMaskDialog) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/testServiceImpl/TestAnalyticsServiceImpl.kt b/src/test/kotlin/eu/ibagroup/formainframe/testServiceImpl/TestAnalyticsServiceImpl.kt index e0fe462e..934c2b90 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/testServiceImpl/TestAnalyticsServiceImpl.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/testServiceImpl/TestAnalyticsServiceImpl.kt @@ -13,7 +13,7 @@ package eu.ibagroup.formainframe.testServiceImpl import eu.ibagroup.formainframe.analytics.AnalyticsService import eu.ibagroup.formainframe.analytics.events.AnalyticsEvent -class TestAnalyticsServiceImpl : AnalyticsService { +open class TestAnalyticsServiceImpl : AnalyticsService { /** * Test instance for the AnalyticsService. From d34de3a71a39a04884b09af1f5b77072ac06c128 Mon Sep 17 00:00:00 2001 From: Arseni Tsikhamirau Date: Mon, 31 Jul 2023 13:21:23 +0200 Subject: [PATCH 12/25] IJMP-1160-Sync-is-not-working-during-indexing 1) fixed and tests are written --- .../content/synchronizer/SyncAction.kt | 17 ++-- .../editor/ChangeContentServiceImpl.kt | 20 ++++- .../editor/FileEditorEventsListener.kt | 7 ++ .../eu/ibagroup/formainframe/editor/utils.kt | 19 ++++ .../dataops/SyncToolbarProviderTestSpec.kt | 84 +++++++++++++++++ .../editor/EditorUtilsTestSpec.kt | 89 +++++++++++++++++++ 6 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt index bfbcb661..243c25be 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt @@ -14,17 +14,14 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.components.service -import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.progress.runBackgroundableTask import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.vfs.VirtualFile import eu.ibagroup.formainframe.config.ConfigService import eu.ibagroup.formainframe.dataops.DataOpsManager -import eu.ibagroup.formainframe.utils.checkEncodingCompatibility -import eu.ibagroup.formainframe.utils.runReadActionInEdtAndWait -import eu.ibagroup.formainframe.utils.runWriteActionInEdtAndWait -import eu.ibagroup.formainframe.utils.showSaveAnywayDialog +import eu.ibagroup.formainframe.utils.* /** Sync action event. It will handle the manual sync button action when it is clicked */ class SyncAction : DumbAwareAction() { @@ -54,8 +51,8 @@ class SyncAction : DumbAwareAction() { * Get an editor on which the event was triggered * @param e the event to get the editor */ - private fun getEditor(e: AnActionEvent): Editor? { - return e.getData(CommonDataKeys.EDITOR) + private fun getEditor(e: AnActionEvent): EditorEx? { + return e.getData(CommonDataKeys.EDITOR).castOrNull() } /** @@ -99,14 +96,18 @@ class SyncAction : DumbAwareAction() { makeDisabled(e) return } - val isDumbMode = ActionUtil.isDumbMode(e.project) val editor = getEditor(e) ?: return + + // TODO: remove in v1.*.*-223 and greater + val isDumbMode = ActionUtil.isDumbMode(e.project) if (!isDumbMode && file.isWritable) { editor.document.setReadOnly(false) + editor.isViewer = false } else { e.presentation.isEnabledAndVisible = false return } + val contentSynchronizer = service().getContentSynchronizer(file) val syncProvider = DocumentedSyncProvider(file) val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt index 99ec123a..a57ea1ee 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt @@ -48,13 +48,29 @@ class ChangeContentServiceImpl : ChangeContentService { override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) { if (action is EditorPasteAction || (action is PasteAction && event.place == "EditorPopup")) { val editor = event.getData(CommonDataKeys.EDITOR) ?: return - processMfContent(editor) + + // TODO: remove below check in v1.*.*-223 and greater + val isFileWritable = requestDocumentWriting(editor) + if (isFileWritable) { + processMfContent(editor) + } + + // TODO: use in v1.*.*-223 and greater + //processMfContent(editor) } } override fun afterEditorTyping(c: Char, dataContext: DataContext) { val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return - processMfContent(editor) + + // TODO: remove below check in v1.*.*-223 and greater + val isFileWritable = requestDocumentWriting(editor) + if (isFileWritable) { + processMfContent(editor) + } + + // TODO: use in v1.*.*-223 and greater + //processMfContent(editor) } } ) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt index 1bba75b4..5cf6c061 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt @@ -38,14 +38,21 @@ class FileEditorEventsListener : FileEditorManagerListener { */ override fun fileOpened(source: FileEditorManager, file: VirtualFile) { val editor = source.selectedTextEditor as? EditorEx + + // TODO: remove in v1.*.*-223 and greater if (editor != null) { editor.addFocusListener(focusListener) val isDumbMode = ActionUtil.isDumbMode(editor.project) if (isDumbMode) { editor.document.setReadOnly(true) + editor.isViewer = true } super.fileOpened(source, file) } + + // TODO: use in v1.*.*-223 and greater + //editor?.addFocusListener(focusListener) + //super.fileOpened(source, file) } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt index b35d12e1..d7a13be5 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt @@ -11,7 +11,10 @@ package eu.ibagroup.formainframe.editor import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorModificationUtil import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.MessageDialogBuilder import com.intellij.openapi.util.Key @@ -70,4 +73,20 @@ fun VirtualFile.isMfVirtualFile(): Boolean { */ fun VirtualFile.isUssVirtualFile(): Boolean { return this.get()?.keys?.find { it.toString() == USS_VIRTUAL_FILE_KEY_NAME } != null +} + +/** + * Function checks the current editor status for the opened document and returns true if document is writable, false otherwise. + * If document is read-only shows the information hint on the TOP of the current caret position + * @param editor - current opened file editor + */ +fun requestDocumentWriting(editor: Editor): Boolean { + val writeAccess = FileDocumentManager.getInstance().requestWritingStatus(editor.document, editor.project) + if (!writeAccess.hasWriteAccess()) { + EditorModificationUtil.setReadOnlyHint(editor, "File is read-only while indexing is in progress.").apply { + EditorModificationUtil.checkModificationAllowed(editor) + } + return false + } + return true } \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt new file mode 100644 index 00000000..cc660e65 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt @@ -0,0 +1,84 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.dataops + +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.project.Project +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.dataops.content.synchronizer.SyncToolbarProvider +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.* + +class SyncToolbarProviderTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + + context("sync action toolbar") { + + val mockedProject = mockk() + val mockedActionManagerInstance = mockk() + val syncToolbarProviderForTest = spyk(SyncToolbarProvider()) + mockkStatic(ActionManager::getInstance) + + should("run activity and resolve action group toolbar when action group is not null") { + var isResolved = false + val mockedActionGroupForTest = mockk() + every { ActionManager.getInstance() } returns mockedActionManagerInstance + every { mockedActionManagerInstance.getAction(any() as String)} answers { + isResolved = true + mockedActionGroupForTest + } + syncToolbarProviderForTest.runActivity(mockedProject) + + assertSoftly { + isResolved shouldBe true + } + } + + should("run activity and resolve action group toolbar when action group is simple action") { + var isResolved = false + val mockedNotActionGroupForTest = mockk() + every { ActionManager.getInstance() } returns mockedActionManagerInstance + every { mockedActionManagerInstance.getAction(any() as String)} returns mockedNotActionGroupForTest + every { mockedActionManagerInstance.registerAction(any() as String, any() as DefaultActionGroup) } answers { + isResolved = true + } + + syncToolbarProviderForTest.runActivity(mockedProject) + + assertSoftly { + isResolved shouldBe true + } + } + + unmockkAll() + } +}) \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt new file mode 100644 index 00000000..4dbb166a --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt @@ -0,0 +1,89 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.editor + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorModificationUtil +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.* + +class EditorUtilsTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + + context("Utils common test spec") { + + val editorMock = mockk() + val documentMock = mockk() + val projectMock = mockk() + every { editorMock.document } returns documentMock + every { editorMock.project } returns projectMock + mockkStatic(FileDocumentManager::getInstance) + mockkStatic(EditorModificationUtil::checkModificationAllowed) + + should("requestDocumentWriting. Check if current document is writable") { + var isFileWritable = false + every { FileDocumentManager.getInstance().requestWritingStatus(documentMock, projectMock) } answers { + isFileWritable = true + FileDocumentManager.WriteAccessStatus.WRITABLE + } + + requestDocumentWriting(editorMock) + + assertSoftly { + isFileWritable shouldBe true + } + + } + + should("requestDocumentWriting. Check if current document is not writable") { + var isFileWritable = true + every { FileDocumentManager.getInstance().requestWritingStatus(documentMock, projectMock) } answers { + FileDocumentManager.WriteAccessStatus.NON_WRITABLE + } + every { EditorModificationUtil.setReadOnlyHint(editorMock, any() as String) } just Runs + every { EditorModificationUtil.checkModificationAllowed(editorMock) } answers { + isFileWritable = false + false + } + + requestDocumentWriting(editorMock) + + assertSoftly { + isFileWritable shouldBe false + } + + } + + unmockkAll() + } +}) \ No newline at end of file From df523764730c774751c9ec55540d16c1faec7878 Mon Sep 17 00:00:00 2001 From: Arseni Tsikhamirau Date: Tue, 1 Aug 2023 11:31:35 +0200 Subject: [PATCH 13/25] IJMP-1233-Sync-is-not-working-during-indexing 1) changed to support IDEA version 223 --- .../content/synchronizer/SyncAction.kt | 17 ++-- .../synchronizer/SyncToolbarProvider.kt | 33 ++++++- .../editor/ChangeContentServiceImpl.kt | 7 +- .../eu/ibagroup/formainframe/editor/utils.kt | 19 ++++ src/main/resources/META-INF/plugin.xml | 3 + .../dataops/SyncToolbarProviderTestSpec.kt | 84 +++++++++++++++++ .../editor/EditorUtilsTestSpec.kt | 89 +++++++++++++++++++ 7 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt create mode 100644 src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt index eb44c6e2..c5d473e6 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt @@ -13,17 +13,14 @@ package eu.ibagroup.formainframe.dataops.content.synchronizer import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.components.service -import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.progress.runBackgroundableTask import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.vfs.VirtualFile import eu.ibagroup.formainframe.config.ConfigService import eu.ibagroup.formainframe.dataops.DataOpsManager -import eu.ibagroup.formainframe.utils.checkEncodingCompatibility -import eu.ibagroup.formainframe.utils.runReadActionInEdtAndWait -import eu.ibagroup.formainframe.utils.runWriteActionInEdtAndWait -import eu.ibagroup.formainframe.utils.showSaveAnywayDialog +import eu.ibagroup.formainframe.utils.* /** Sync action event. It will handle the manual sync button action when it is clicked */ class SyncAction : DumbAwareAction() { @@ -54,8 +51,8 @@ class SyncAction : DumbAwareAction() { * Get an editor on which the event was triggered * @param e the event to get the editor */ - private fun getEditor(e: AnActionEvent): Editor? { - return e.getData(CommonDataKeys.EDITOR) + private fun getEditor(e: AnActionEvent): EditorEx? { + return e.getData(CommonDataKeys.EDITOR).castOrNull() } /** @@ -110,4 +107,10 @@ class SyncAction : DumbAwareAction() { && needToUpload } + /** + * Determines if an action is dumb aware or not + */ + override fun isDumbAware(): Boolean { + return true + } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt index db2bf6eb..f3e125eb 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncToolbarProvider.kt @@ -10,14 +10,45 @@ package eu.ibagroup.formainframe.dataops.content.synchronizer +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupActivity.RequiredForSmartMode private const val ACTION_GROUP = "eu.ibagroup.formainframe.dataops.content.synchronizer.SyncActionGroup" /** * Class which serves as extension point for editor floating toolbar provider. Defined in plugin.xml */ -class SyncToolbarProvider : AbstractFloatingToolbarProvider(ACTION_GROUP) { +class SyncToolbarProvider : AbstractFloatingToolbarProvider(ACTION_GROUP), RequiredForSmartMode { override val autoHideable = true override val priority = 1 + override val actionGroup: ActionGroup by lazy { + resolveActionGroup() + } + + /** + * Resolves Sync Action Toolbar during IDE startup activity + * @return resolved sync action + */ + private fun resolveActionGroup(): ActionGroup { + ApplicationManager.getApplication() + val actionManager = ActionManager.getInstance() + val action = actionManager.getAction(ACTION_GROUP) + if (action is ActionGroup) return action + val defaultActionGroup = DefaultActionGroup() + actionManager.registerAction(ACTION_GROUP, defaultActionGroup) + return defaultActionGroup + } + + /** + * Runs an activity during IDE startup + * @param project - current project + */ + override fun runActivity(project: Project) { + resolveActionGroup() + } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt index 0fcb104e..99ec123a 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt @@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ReadOnlyModificationException import com.intellij.openapi.fileEditor.FileDocumentManager import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.content.adapters.MFContentAdapter @@ -72,7 +73,11 @@ class ChangeContentServiceImpl : ChangeContentService { val adaptedContent = contentAdapter.prepareContentToMainframe(currentContent, file) runWriteActionInEdt { CommandProcessor.getInstance().runUndoTransparentAction { - editor.document.setText(adaptedContent) + try { + editor.document.setText(adaptedContent) + } catch (e: ReadOnlyModificationException) { + return@runUndoTransparentAction + } } } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt index b35d12e1..d7a13be5 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/utils.kt @@ -11,7 +11,10 @@ package eu.ibagroup.formainframe.editor import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorModificationUtil import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.MessageDialogBuilder import com.intellij.openapi.util.Key @@ -70,4 +73,20 @@ fun VirtualFile.isMfVirtualFile(): Boolean { */ fun VirtualFile.isUssVirtualFile(): Boolean { return this.get()?.keys?.find { it.toString() == USS_VIRTUAL_FILE_KEY_NAME } != null +} + +/** + * Function checks the current editor status for the opened document and returns true if document is writable, false otherwise. + * If document is read-only shows the information hint on the TOP of the current caret position + * @param editor - current opened file editor + */ +fun requestDocumentWriting(editor: Editor): Boolean { + val writeAccess = FileDocumentManager.getInstance().requestWritingStatus(editor.document, editor.project) + if (!writeAccess.hasWriteAccess()) { + EditorModificationUtil.setReadOnlyHint(editor, "File is read-only while indexing is in progress.").apply { + EditorModificationUtil.checkModificationAllowed(editor) + } + return false + } + return true } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f3791b1f..c1b3feff 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -299,6 +299,9 @@ Example of how to see the output:
displayName="For Mainframe" provider="eu.ibagroup.formainframe.config.MainframeConfigurableProvider"/> + + diff --git a/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt new file mode 100644 index 00000000..cc660e65 --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/dataops/SyncToolbarProviderTestSpec.kt @@ -0,0 +1,84 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.dataops + +import com.intellij.openapi.actionSystem.ActionGroup +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.project.Project +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import eu.ibagroup.formainframe.dataops.content.synchronizer.SyncToolbarProvider +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.* + +class SyncToolbarProviderTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + + context("sync action toolbar") { + + val mockedProject = mockk() + val mockedActionManagerInstance = mockk() + val syncToolbarProviderForTest = spyk(SyncToolbarProvider()) + mockkStatic(ActionManager::getInstance) + + should("run activity and resolve action group toolbar when action group is not null") { + var isResolved = false + val mockedActionGroupForTest = mockk() + every { ActionManager.getInstance() } returns mockedActionManagerInstance + every { mockedActionManagerInstance.getAction(any() as String)} answers { + isResolved = true + mockedActionGroupForTest + } + syncToolbarProviderForTest.runActivity(mockedProject) + + assertSoftly { + isResolved shouldBe true + } + } + + should("run activity and resolve action group toolbar when action group is simple action") { + var isResolved = false + val mockedNotActionGroupForTest = mockk() + every { ActionManager.getInstance() } returns mockedActionManagerInstance + every { mockedActionManagerInstance.getAction(any() as String)} returns mockedNotActionGroupForTest + every { mockedActionManagerInstance.registerAction(any() as String, any() as DefaultActionGroup) } answers { + isResolved = true + } + + syncToolbarProviderForTest.runActivity(mockedProject) + + assertSoftly { + isResolved shouldBe true + } + } + + unmockkAll() + } +}) \ No newline at end of file diff --git a/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt new file mode 100644 index 00000000..4dbb166a --- /dev/null +++ b/src/test/kotlin/eu/ibagroup/formainframe/editor/EditorUtilsTestSpec.kt @@ -0,0 +1,89 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package eu.ibagroup.formainframe.editor + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorModificationUtil +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import io.mockk.* + +class EditorUtilsTestSpec : ShouldSpec({ + beforeSpec { + // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val projectDescriptor = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR + val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "for-mainframe") + val fixture = fixtureBuilder.fixture + val myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture( + fixture, + LightTempDirTestFixtureImpl(true) + ) + myFixture.setUp() + } + afterSpec { + clearAllMocks() + } + + context("Utils common test spec") { + + val editorMock = mockk() + val documentMock = mockk() + val projectMock = mockk() + every { editorMock.document } returns documentMock + every { editorMock.project } returns projectMock + mockkStatic(FileDocumentManager::getInstance) + mockkStatic(EditorModificationUtil::checkModificationAllowed) + + should("requestDocumentWriting. Check if current document is writable") { + var isFileWritable = false + every { FileDocumentManager.getInstance().requestWritingStatus(documentMock, projectMock) } answers { + isFileWritable = true + FileDocumentManager.WriteAccessStatus.WRITABLE + } + + requestDocumentWriting(editorMock) + + assertSoftly { + isFileWritable shouldBe true + } + + } + + should("requestDocumentWriting. Check if current document is not writable") { + var isFileWritable = true + every { FileDocumentManager.getInstance().requestWritingStatus(documentMock, projectMock) } answers { + FileDocumentManager.WriteAccessStatus.NON_WRITABLE + } + every { EditorModificationUtil.setReadOnlyHint(editorMock, any() as String) } just Runs + every { EditorModificationUtil.checkModificationAllowed(editorMock) } answers { + isFileWritable = false + false + } + + requestDocumentWriting(editorMock) + + assertSoftly { + isFileWritable shouldBe false + } + + } + + unmockkAll() + } +}) \ No newline at end of file From 06c30144c5266553748bcc175a2f63fbe811e80f Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Thu, 3 Aug 2023 15:20:24 +0300 Subject: [PATCH 14/25] IJMP-1101: added spec for http requests --- src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt index 227a6312..3959ba05 100755 --- a/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt @@ -130,7 +130,7 @@ private fun OkHttpClient.Builder.setupClient(): OkHttpClient.Builder { it.request().newBuilder().addHeader("X-CSRF-ZOSMF-HEADER", "").build().let { request -> it.proceed(request) } - }.connectionSpecs(mutableListOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS)) + }.connectionSpecs(mutableListOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT)) } /** From a357a4eddb1939111daf6f604e0166ea0c910e52 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 9 Aug 2023 18:40:49 +0200 Subject: [PATCH 15/25] IJMP-1260 Release/v1.1.0-221 ready Signed-off-by: Uladzislau --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6ef6856..be9616fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,59 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file. +## `1.1.0 (2023-08-xx)` + +* Feature: GitHub issue #14: UX: Edit WS mask ([0d358d0d](https://github.com/zowe/zowe-explorer-intellij/commit/0d358d0d)) +* Feature: GitHub issue #23: Double click on a working set or connection ([e7f040d7](https://github.com/zowe/zowe-explorer-intellij/commit/e7f040d7)) +* Feature: GitHub issue #49: Plugin logging ([76b6b175](https://github.com/zowe/zowe-explorer-intellij/commit/76b6b175)) +* Feature: GitHub issue #52: Presets for creating datasets ([6b8a5ca6](https://github.com/zowe/zowe-explorer-intellij/commit/6b8a5ca6)) +* Feature: GitHub issue #111: "Rename" in dialog window should be "Edit" for DS and USS masks ([c39f7c01](https://github.com/zowe/zowe-explorer-intellij/commit/c39f7c01)) +* Feature: GitHub issue #112: Migrate all UI tests from real data usage to mock server ([531d0e94](https://github.com/zowe/zowe-explorer-intellij/commit/531d0e94)) +* Feature: GitHub issue #113: Change user password feature ([0706abcb](https://github.com/zowe/zowe-explorer-intellij/commit/0706abcb)) +* Feature: GitHub issue #122: "whoami" on connection creation ([23ad877b](https://github.com/zowe/zowe-explorer-intellij/commit/23ad877b)) +* Feature: GitHub issue #123: Implement "No items found" for USS and DS masks ([762827d0](https://github.com/zowe/zowe-explorer-intellij/commit/762827d0)) +* Feature: GitHub issue #124: Clarify DS organization ([361f5f0a](https://github.com/zowe/zowe-explorer-intellij/commit/361f5f0a)) +* Feature: GitHub issue #125: 80 LRECL by default ([62598726](https://github.com/zowe/zowe-explorer-intellij/commit/62598726)) +* Feature: GitHub issue #126: Copy + rename ([440b65d9](https://github.com/zowe/zowe-explorer-intellij/commit/440b65d9)) +* Feature: GitHub issue #130: JDK search index broken in IntelliJ after dataset is open +* Feature: GitHub issue #136: CLEARTEXT communication not enabled for client ([a2958223](https://github.com/zowe/zowe-explorer-intellij/commit/a2958223)) +* Feature: GitHub issue #140: Exception in Zowe Explorer (1.0.2-221) for Android Studio(Android Studio Flamingo | 2022.2.1 Patch 2) ([3ce3813e](https://github.com/zowe/zowe-explorer-intellij/commit/3ce3813e)) +* Feature: GitHub issue #144: Incorrect encoding should not be changed directly, until a user is decided to change it when we suggest ([02ae0c62](https://github.com/zowe/zowe-explorer-intellij/commit/02ae0c62)) +* Feature: GitHub issue #145: Migrated dataset properties should not be visible if they are not available ([18b13ff1](https://github.com/zowe/zowe-explorer-intellij/commit/18b13ff1)) +* Feature: GitHub issue #146: Hints for creating working sets after connection is created ([dcd92dfa](https://github.com/zowe/zowe-explorer-intellij/commit/dcd92dfa)) +* Feature: GitHub issue #147: "Duplicate" for member ([1493f1e8](https://github.com/zowe/zowe-explorer-intellij/commit/1493f1e8)) +* Feature: GitHub issue #148: Warning about incompatible encodings ([3bd29c7a](https://github.com/zowe/zowe-explorer-intellij/commit/3bd29c7a)) +* Feature: Separate info and tso requests during connection test ([83aa6bf2](https://github.com/zowe/zowe-explorer-intellij/commit/83aa6bf2)) +* Feature: Rework configs in the plug-in to accept new configurables ([d0ff5b3d](https://github.com/zowe/zowe-explorer-intellij/commit/d0ff5b3d)) +* Feature: Rework file sync with MF ([c43b8198](https://github.com/zowe/zowe-explorer-intellij/commit/c43b8198)) +* Feature: Presets: improvement ([7255dfce](https://github.com/zowe/zowe-explorer-intellij/commit/7255dfce)) +* Feature: VFS_CHANGES to MF_VFS_CHANGES ([0dd8b1b4](https://github.com/zowe/zowe-explorer-intellij/commit/0dd8b1b4)) +* Feature: Change XML and JSON comparison on different plugin versions ([7f480747](https://github.com/zowe/zowe-explorer-intellij/commit/7f480747)) +* Feature: Substitute R2Z with Zowe Kotlin SDK ([df6d11c1](https://github.com/zowe/zowe-explorer-intellij/commit/df6d11c1)) +* Feature: Enhance configuration for CICS connections ([cfa94f88](https://github.com/zowe/zowe-explorer-intellij/commit/cfa94f88)) +* Feature: Unit tests ([019bef00](https://github.com/zowe/zowe-explorer-intellij/commit/019bef00), [fa903c1c](https://github.com/zowe/zowe-explorer-intellij/commit/fa903c1c), [709a5e2f](https://github.com/zowe/zowe-explorer-intellij/commit/709a5e2f), [07ecd283](https://github.com/zowe/zowe-explorer-intellij/commit/07ecd283), [93febb62](https://github.com/zowe/zowe-explorer-intellij/commit/93febb62), [517c2cff](https://github.com/zowe/zowe-explorer-intellij/commit/517c2cff), [29cd85e4](https://github.com/zowe/zowe-explorer-intellij/commit/29cd85e4), [3850fcec](https://github.com/zowe/zowe-explorer-intellij/commit/3850fcec), [5646963e](https://github.com/zowe/zowe-explorer-intellij/commit/5646963e), [be3b5086](https://github.com/zowe/zowe-explorer-intellij/commit/be3b5086), [5043f5da](https://github.com/zowe/zowe-explorer-intellij/commit/5043f5da), [fdf67e1c](https://github.com/zowe/zowe-explorer-intellij/commit/fdf67e1c)) + + +* Bugfix: Tooltip on JES Working set shows 'Working set' ([9937c128](https://github.com/zowe/zowe-explorer-intellij/commit/9937c128)) +* Bugfix: "debounce" test is failed sometimes ([8bb98da8](https://github.com/zowe/zowe-explorer-intellij/commit/8bb98da8)) +* Bugfix: Allocate like strange behavior ([a0bf3da4](https://github.com/zowe/zowe-explorer-intellij/commit/a0bf3da4)) +* Bugfix: Change permissions: incorrect permissions shown after change failure ([2143e4ba](https://github.com/zowe/zowe-explorer-intellij/commit/2143e4ba)) +* Bugfix: GitHub issue #138: Job is identified as successful while it ends with RC=12 ([99491858](https://github.com/zowe/zowe-explorer-intellij/commit/99491858)) +* Bugfix: Empty pds is not deleted after its move to pds ([e88f830f](https://github.com/zowe/zowe-explorer-intellij/commit/e88f830f)) +* Bugfix: It shows that the job is still running after successfull purge ([d4a4289e](https://github.com/zowe/zowe-explorer-intellij/commit/d4a4289e)) +* Bugfix: Errors for several actions in JES Explorer ([3496acc1](https://github.com/zowe/zowe-explorer-intellij/commit/3496acc1)) +* Bugfix: Error if move empty PS to PDS ([15b262c0](https://github.com/zowe/zowe-explorer-intellij/commit/15b262c0)) +* Bugfix: Password is changed only for one connection ([a52830c8](https://github.com/zowe/zowe-explorer-intellij/commit/a52830c8)) +* Bugfix: Userid for new ws/jws is not changed in FileExplorer/JesExplorer after changes in corresponding connection ([ea0c9706](https://github.com/zowe/zowe-explorer-intellij/commit/ea0c9706)) +* Bugfix: FileNotFoundException on configs search (The system cannot find the file specified) ([76badb81](https://github.com/zowe/zowe-explorer-intellij/commit/76badb81)) +* Bugfix: Content of uss-file is changed to UTF-8 while copying it from remote to local ([ed86553e](https://github.com/zowe/zowe-explorer-intellij/commit/ed86553e)) +* Bugfix: Copy/paste and DnD of PS dataset from one host to uss-folder on another host does not work ([1522e08a](https://github.com/zowe/zowe-explorer-intellij/commit/1522e08a)) +* Bugfix: validateForGreaterValue should show correct message ([f841692e](https://github.com/zowe/zowe-explorer-intellij/commit/f841692e)) +* Bugfix: JCL highlight does not work on mainframe files ([80c8288a](https://github.com/zowe/zowe-explorer-intellij/commit/80c8288a)) +* Bugfix: IDE error when rename member/dataset to existing one/to the same ([e8dbaa8a](https://github.com/zowe/zowe-explorer-intellij/commit/e8dbaa8a)) +* Bugfix: ClassCastException: class java.util.ArrayList cannot be cast to class com.intellij.openapi.vfs.VirtualFile ([cdc0a458](https://github.com/zowe/zowe-explorer-intellij/commit/cdc0a458)) + + ## `1.0.2 (2023-06-13)` * Feature: Returned support for IntelliJ 2022.1 ([6329c788](https://github.com/zowe/zowe-explorer-intellij/commit/6329c788)) From 9a03e8f8923558b7b1d04ccd60c3f6e0b717e5af Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 9 Aug 2023 18:45:35 +0200 Subject: [PATCH 16/25] IJMP-1260 Release v1.1.0-221 --- CHANGELOG.md | 2 +- build.gradle.kts | 62 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be9616fe..e067b1bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,11 +35,11 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file. * Feature: Unit tests ([019bef00](https://github.com/zowe/zowe-explorer-intellij/commit/019bef00), [fa903c1c](https://github.com/zowe/zowe-explorer-intellij/commit/fa903c1c), [709a5e2f](https://github.com/zowe/zowe-explorer-intellij/commit/709a5e2f), [07ecd283](https://github.com/zowe/zowe-explorer-intellij/commit/07ecd283), [93febb62](https://github.com/zowe/zowe-explorer-intellij/commit/93febb62), [517c2cff](https://github.com/zowe/zowe-explorer-intellij/commit/517c2cff), [29cd85e4](https://github.com/zowe/zowe-explorer-intellij/commit/29cd85e4), [3850fcec](https://github.com/zowe/zowe-explorer-intellij/commit/3850fcec), [5646963e](https://github.com/zowe/zowe-explorer-intellij/commit/5646963e), [be3b5086](https://github.com/zowe/zowe-explorer-intellij/commit/be3b5086), [5043f5da](https://github.com/zowe/zowe-explorer-intellij/commit/5043f5da), [fdf67e1c](https://github.com/zowe/zowe-explorer-intellij/commit/fdf67e1c)) +* Bugfix: GitHub issue #138: Job is identified as successful while it ends with RC=12 ([99491858](https://github.com/zowe/zowe-explorer-intellij/commit/99491858)) * Bugfix: Tooltip on JES Working set shows 'Working set' ([9937c128](https://github.com/zowe/zowe-explorer-intellij/commit/9937c128)) * Bugfix: "debounce" test is failed sometimes ([8bb98da8](https://github.com/zowe/zowe-explorer-intellij/commit/8bb98da8)) * Bugfix: Allocate like strange behavior ([a0bf3da4](https://github.com/zowe/zowe-explorer-intellij/commit/a0bf3da4)) * Bugfix: Change permissions: incorrect permissions shown after change failure ([2143e4ba](https://github.com/zowe/zowe-explorer-intellij/commit/2143e4ba)) -* Bugfix: GitHub issue #138: Job is identified as successful while it ends with RC=12 ([99491858](https://github.com/zowe/zowe-explorer-intellij/commit/99491858)) * Bugfix: Empty pds is not deleted after its move to pds ([e88f830f](https://github.com/zowe/zowe-explorer-intellij/commit/e88f830f)) * Bugfix: It shows that the job is still running after successfull purge ([d4a4289e](https://github.com/zowe/zowe-explorer-intellij/commit/d4a4289e)) * Bugfix: Errors for several actions in JES Explorer ([3496acc1](https://github.com/zowe/zowe-explorer-intellij/commit/3496acc1)) diff --git a/build.gradle.kts b/build.gradle.kts index cb34d815..dc40797b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -91,27 +91,59 @@ tasks { untilBuild.set("222.*") changeNotes.set( """ - WARNING: version 1.0 introduces breaking change. You won't be able to use the plugin with IntelliJ version less than 2022.1 -
-
New features:
    -
  • Returned support for IntelliJ 2022.1
  • -
  • Focus on dataset name field in allocation dialog
  • +
  • GitHub issue #14: UX: Edit WS mask
  • +
  • GitHub issue #23: Double click on a working set or connection
  • +
  • GitHub issue #49: Plugin logging
  • +
  • GitHub issue #52: Presets for creating datasets
  • +
  • GitHub issue #111: "Rename" in dialog window should be "Edit" for DS and USS masks
  • +
  • GitHub issue #112: Migrate all UI tests from real data usage to mock server
  • +
  • GitHub issue #113: Change user password feature
  • +
  • GitHub issue #122: "whoami" on connection creation
  • +
  • GitHub issue #123: Implement "No items found" for USS and DS masks
  • +
  • GitHub issue #124: Clarify DS organization
  • +
  • GitHub issue #125: 80 LRECL by default
  • +
  • GitHub issue #126: Copy + rename
  • +
  • GitHub issue #130: JDK search index broken in IntelliJ after dataset is open
  • +
  • GitHub issue #136: CLEARTEXT communication not enabled for client
  • +
  • GitHub issue #140: Exception in Zowe Explorer (1.0.2-221) for Android Studio(Android Studio Flamingo | 2022.2.1 Patch 2)
  • +
  • GitHub issue #144: Incorrect encoding should not be changed directly, until a user is decided to change it when we suggest
  • +
  • GitHub issue #145: Migrated dataset properties should not be visible if they are not available
  • +
  • GitHub issue #146: Hints for creating working sets after connection is created
  • +
  • GitHub issue #147: "Duplicate" for member
  • +
  • GitHub issue #148: Warning about incompatible encodings
  • +
  • Separate info and tso requests during connection test
  • +
  • Rework configs in the plug-in to accept new configurables
  • +
  • Rework file sync with MF
  • +
  • Presets: improvement
  • +
  • VFS_CHANGES to MF_VFS_CHANGES
  • +
  • Change XML and JSON comparison on different plugin versions
  • +
  • Substitute R2Z with Zowe Kotlin SDK
  • +
  • Enhance configuration for CICS connections
  • +
  • Unit tests

Fixed bugs:
    -
  • Memory leak bug
  • -
  • GitHub issue #132: IDE internal error - NPE
  • -
  • Access denied error when copy from remote to local file when local has folder with the same name
  • -
  • Paste to dataset with LRECL does not move exceeding characters to a new line
  • -
  • USS file with 0 permissions is not accessible and no error message displayed
  • -
  • Refresh does not work for job filter with one job after purge
  • -
  • Name conflict message if move uss-file from folder to mask and then back
  • -
  • File cash conflict
  • -
  • Cancel button does not work for TSO connection test during
  • -
  • Unknown file type after delete member after move
  • + <
  • GitHub issue #138: Job is identified as successful while it ends with RC=12
  • +
  • Tooltip on JES Working set shows 'Working set'
  • +
  • "debounce" test is failed sometimes
  • +
  • Allocate like strange behavior
  • +
  • Change permissions: incorrect permissions shown after change failure
  • +
  • Empty pds is not deleted after its move to pds
  • +
  • It shows that the job is still running after successfull purge
  • +
  • Errors for several actions in JES Explorer
  • +
  • Error if move empty PS to PDS
  • +
  • Password is changed only for one connection
  • +
  • Userid for new ws/jws is not changed in FileExplorer/JesExplorer after changes in corresponding connection
  • +
  • FileNotFoundException on configs search (The system cannot find the file specified)
  • +
  • Content of uss-file is changed to UTF-8 while copying it from remote to local
  • +
  • Copy/paste and DnD of PS dataset from one host to uss-folder on another host does not work
  • +
  • validateForGreaterValue should show correct message
  • +
  • JCL highlight does not work on mainframe files
  • +
  • IDE error when rename member/dataset to existing one/to the same
  • +
  • ClassCastException: class java.util.ArrayList cannot be cast to class com.intellij.openapi.vfs.VirtualFile
""" ) } From 6ce5f6badb87426fd907adf3a1b31b882bc38f20 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Thu, 10 Aug 2023 13:26:50 +0200 Subject: [PATCH 17/25] Some fixes to 223 version --- .../content/synchronizer/SyncAction.kt | 10 ---------- .../editor/ChangeContentServiceImpl.kt | 20 +++---------------- .../editor/FileEditorEventsListener.kt | 16 ++------------- .../explorer/actions/AllocateActionBase.kt | 1 - 4 files changed, 5 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt index 243c25be..13232933 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/SyncAction.kt @@ -98,16 +98,6 @@ class SyncAction : DumbAwareAction() { } val editor = getEditor(e) ?: return - // TODO: remove in v1.*.*-223 and greater - val isDumbMode = ActionUtil.isDumbMode(e.project) - if (!isDumbMode && file.isWritable) { - editor.document.setReadOnly(false) - editor.isViewer = false - } else { - e.presentation.isEnabledAndVisible = false - return - } - val contentSynchronizer = service().getContentSynchronizer(file) val syncProvider = DocumentedSyncProvider(file) val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt index a57ea1ee..afd62a15 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/ChangeContentServiceImpl.kt @@ -49,28 +49,14 @@ class ChangeContentServiceImpl : ChangeContentService { if (action is EditorPasteAction || (action is PasteAction && event.place == "EditorPopup")) { val editor = event.getData(CommonDataKeys.EDITOR) ?: return - // TODO: remove below check in v1.*.*-223 and greater - val isFileWritable = requestDocumentWriting(editor) - if (isFileWritable) { - processMfContent(editor) - } - - // TODO: use in v1.*.*-223 and greater - //processMfContent(editor) + processMfContent(editor) } } override fun afterEditorTyping(c: Char, dataContext: DataContext) { val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return - // TODO: remove below check in v1.*.*-223 and greater - val isFileWritable = requestDocumentWriting(editor) - if (isFileWritable) { - processMfContent(editor) - } - - // TODO: use in v1.*.*-223 and greater - //processMfContent(editor) + processMfContent(editor) } } ) @@ -101,4 +87,4 @@ class ChangeContentServiceImpl : ChangeContentService { adaptContentFunc?.let { it() } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt index 5cf6c061..c74899c2 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/editor/FileEditorEventsListener.kt @@ -39,20 +39,8 @@ class FileEditorEventsListener : FileEditorManagerListener { override fun fileOpened(source: FileEditorManager, file: VirtualFile) { val editor = source.selectedTextEditor as? EditorEx - // TODO: remove in v1.*.*-223 and greater - if (editor != null) { - editor.addFocusListener(focusListener) - val isDumbMode = ActionUtil.isDumbMode(editor.project) - if (isDumbMode) { - editor.document.setReadOnly(true) - editor.isViewer = true - } - super.fileOpened(source, file) - } - - // TODO: use in v1.*.*-223 and greater - //editor?.addFocusListener(focusListener) - //super.fileOpened(source, file) + editor?.addFocusListener(focusListener) + super.fileOpened(source, file) } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt index 79fd503c..064c2182 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateActionBase.kt @@ -39,7 +39,6 @@ import eu.ibagroup.formainframe.explorer.ui.getExplorerView import eu.ibagroup.formainframe.utils.castOrNull import eu.ibagroup.formainframe.utils.clone import eu.ibagroup.formainframe.utils.crudable.getByUniqueKey -import eu.ibagroup.formainframe.utils.service import org.zowe.kotlinsdk.Dataset import org.zowe.kotlinsdk.DatasetOrganization import org.zowe.kotlinsdk.DsnameType From 73f505cadc7f8bd070c778e6f1ba331f7cd90ce3 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Thu, 10 Aug 2023 19:13:29 +0200 Subject: [PATCH 18/25] Changes for release/v1.1.0-221 --- .../explorer/ui/ChangeEncodingDialogTestSpec.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ChangeEncodingDialogTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ChangeEncodingDialogTestSpec.kt index e0306b32..b9780e89 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ChangeEncodingDialogTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ChangeEncodingDialogTestSpec.kt @@ -204,7 +204,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ every { reloadIn(any(), virtualFileMock, charsetMock) } returns Unit val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val reloadAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.reload") } reloadAction?.actionPerformed(actionEventMock) @@ -215,7 +214,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ every { reloadIn(any(), virtualFileMock, charsetMock) } returns Unit val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val reloadAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.reload") } reloadAction?.actionPerformed(actionEventMock) @@ -247,7 +245,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ } val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val reloadAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.reload") } reloadAction?.actionPerformed(actionEventMock) @@ -279,7 +276,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ } val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val reloadAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.reload") } reloadAction?.actionPerformed(actionEventMock) @@ -304,7 +300,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ } val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val reloadAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.reload") } reloadAction?.actionPerformed(actionEventMock) @@ -316,7 +311,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ every { saveIn(any(), virtualFileMock, charsetMock) } returns Unit val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val convertAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.convert") } convertAction?.actionPerformed(actionEventMock) @@ -348,7 +342,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ } val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val convertAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.convert") } convertAction?.actionPerformed(actionEventMock) @@ -373,7 +366,6 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ } val actions = createActionsRef.invoke(changeEncodingDialog).castOrNull>() - // TODO: change it.getValue(Action.NAME) to it.getName() in v1.*.*-231 and greater val convertAction = actions?.first { it.getValue(Action.NAME) == IdeBundle.message("button.convert") } convertAction?.actionPerformed(actionEventMock) @@ -382,4 +374,4 @@ class ChangeEncodingDialogTestSpec : ShouldSpec({ unmockkAll() } -}) \ No newline at end of file +}) From 99b514f0c7b48d05e18905fd2c18a846eeb9c040 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Fri, 11 Aug 2023 11:38:03 +0200 Subject: [PATCH 19/25] IntelliJ IDEA supported versions bump --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6d2cd4bf..5155ef0c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -88,7 +88,7 @@ tasks { patchPluginXml { sinceBuild.set("231.8109") - untilBuild.set("231.*") + untilBuild.set("232.*") changeNotes.set( """ New features: From 5372525bb5943c9fbdb6e32096b4bce1a3e6f314 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Mon, 14 Aug 2023 18:40:07 +0200 Subject: [PATCH 20/25] Release/v1.1.0-221 ready --- .../explorer/ui/ExplorerPasteProvider.kt | 141 ++--- .../ui/ExplorerPasteProviderTestSpec.kt | 505 ++++++++++-------- .../explorer/ui/UssFileNodeTestSpec.kt | 80 +-- .../formainframe/explorer/ui/dummyClasses.kt | 159 +----- 4 files changed, 413 insertions(+), 472 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt index e26190a4..666a56f8 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt @@ -48,9 +48,9 @@ object ExplorerDataKeys { * @param destinationFile child of file to be copy to that have conflict with source file. * @author Valiantsin Krus */ -class ConflictResolution ( - val sourceFile: VirtualFile, - val destinationFile: VirtualFile +class ConflictResolution( + val sourceFile: VirtualFile, + val destinationFile: VirtualFile ) { private var overwrite: Boolean = false private var skip: Boolean = false @@ -296,8 +296,8 @@ class ExplorerPasteProvider : PasteProvider { message = "${dialogMessagePrefix}Do you want to $dialogActionMessage these files?", project = project, icon = if (dialogTitlePrefix == "Moving") null else AllIcons.General.WarningDialog - ).let { - if (!it) { + ).let { proceed -> + if (!proceed) { copyPasteSupport.removeFromBuffer { nodeData -> nodeData.file?.let { fileNotNull -> sourceFiles.contains(fileNotNull) } ?: false } @@ -340,7 +340,7 @@ class ExplorerPasteProvider : PasteProvider { ) ) { conflictsResolutions.addAll( - ussToPdsWarnings.map { ConflictResolution(it.first, it.second).apply { resolveBySkip() } } + ussToPdsWarnings.map { ConflictResolution(it.first, it.second).apply { resolveBySkip() } } ) } // specific conflicts resolution end @@ -349,7 +349,7 @@ class ExplorerPasteProvider : PasteProvider { .map { destFile -> sourceFiles.mapNotNull { sourceFile -> val conflictResolution = conflictsResolutions - .find { it.sourceFile == sourceFile && it.destinationFile == destFile } + .find { it.sourceFile == sourceFile && it.destinationFile == destFile } if (conflictResolution?.shouldSkip() == true) { if (isDragAndDrop) { copyPasteSupport.removeFromBuffer { it.file == sourceFile } @@ -437,35 +437,35 @@ class ExplorerPasteProvider : PasteProvider { // val overwriteDestinationSourceList = mutableListOf>() val listOfAllConflicts = pasteDestinations - .mapNotNull { destFile -> - destFile.children - ?.map conflicts@{ destChild -> - val filteredSourceFiles = sourceFiles.filter { source -> - val sourceAttributes = dataOpsManager.tryToGetAttributes(source) - val destAttributes = dataOpsManager.tryToGetAttributes(destChild) - if ( - destAttributes is RemoteMemberAttributes && - (sourceAttributes is RemoteUssAttributes || source is VirtualFileImpl) - ) { - val memberName = source.name.filter { it.isLetterOrDigit() }.take(8).uppercase() - if (memberName.isNotEmpty()) memberName == destChild.name else "EMPTY" == destChild.name - } else if ( - destAttributes is RemoteMemberAttributes && - sourceAttributes is RemoteDatasetAttributes - ) { - sourceAttributes.name.split(".").last() == destChild.name - } else { - source.name == destChild.name - } - } - val foundConflicts = mutableListOf>() - if (filteredSourceFiles.isNotEmpty()) { - filteredSourceFiles.forEach { foundConflict -> foundConflicts.add(Pair(destFile, foundConflict)) } - } - foundConflicts + .mapNotNull { destFile -> + destFile.children + ?.map conflicts@{ destChild -> + val filteredSourceFiles = sourceFiles.filter { source -> + val sourceAttributes = dataOpsManager.tryToGetAttributes(source) + val destAttributes = dataOpsManager.tryToGetAttributes(destChild) + if ( + destAttributes is RemoteMemberAttributes && + (sourceAttributes is RemoteUssAttributes || source is VirtualFileImpl) + ) { + val memberName = source.name.filter { it.isLetterOrDigit() }.take(8).uppercase() + if (memberName.isNotEmpty()) memberName == destChild.name else "EMPTY" == destChild.name + } else if ( + destAttributes is RemoteMemberAttributes && + sourceAttributes is RemoteDatasetAttributes + ) { + sourceAttributes.name.split(".").last() == destChild.name + } else { + source.name == destChild.name } - } - .flatten() + } + val foundConflicts = mutableListOf>() + if (filteredSourceFiles.isNotEmpty()) { + filteredSourceFiles.forEach { foundConflict -> foundConflicts.add(Pair(destFile, foundConflict)) } + } + foundConflicts + } + } + .flatten() val conflicts = mutableListOf>() listOfAllConflicts.forEach { conflictList -> conflicts.addAll(conflictList) } @@ -481,18 +481,18 @@ class ExplorerPasteProvider : PasteProvider { if (conflicts.isNotEmpty() || conflictsThatCannotBeOverwritten.isNotEmpty()) { val choice = Messages.showDialog( - project, - "Please, select", - "Name conflicts in ${conflicts.size + conflictsThatCannotBeOverwritten.size} file(s)", - arrayOf( - //"Decide for Each", - "Skip for All", - "Overwrite for All", - "Decide for Each" - ), - 0, - AllIcons.General.QuestionDialog, - null + project, + "Please, select", + "Name conflicts in ${conflicts.size + conflictsThatCannotBeOverwritten.size} file(s)", + arrayOf( + //"Decide for Each", + "Skip for All", + "Overwrite for All", + "Decide for Each" + ), + 0, + AllIcons.General.QuestionDialog, + null ) when (choice) { @@ -500,7 +500,7 @@ class ExplorerPasteProvider : PasteProvider { 1 -> { result.addAll(conflicts.map { ConflictResolution(it.first, it.second).apply { resolveByOverwrite() } }) result.addAll( - conflictsThatCannotBeOverwritten.map { ConflictResolution(it.first, it.second).apply { resolveBySkip() } } + conflictsThatCannotBeOverwritten.map { ConflictResolution(it.first, it.second).apply { resolveBySkip() } } ) if (conflictsThatCannotBeOverwritten.isNotEmpty()) { val startMessage = "There are some conflicts that cannot be resolved:" @@ -513,16 +513,17 @@ class ExplorerPasteProvider : PasteProvider { } } Messages.showDialog( - project, - createHtmlMessageWithItemsList(startMessage, conflictsToShow, finishMessage), - "Not Resolvable Conflicts", - arrayOf("Ok"), - 0, - Messages.getErrorIcon(), - null + project, + createHtmlMessageWithItemsList(startMessage, conflictsToShow, finishMessage), + "Not Resolvable Conflicts", + arrayOf("Ok"), + 0, + Messages.getErrorIcon(), + null ) } } + 2 -> result.addAll(askUserAboutConflictResolution(conflicts, conflictsThatCannotBeOverwritten, project)) else -> throw Exception("Selected option is not supported.") } @@ -540,9 +541,9 @@ class ExplorerPasteProvider : PasteProvider { * @return List of [ConflictResolution] that indicates list of conflicts and how to resolve them. */ private fun askUserAboutConflictResolution( - conflicts: List>, - conflictsThatCannotBeOverwritten: List>, - project: Project? + conflicts: List>, + conflictsThatCannotBeOverwritten: List>, + project: Project? ): List { val result = mutableListOf() val allConflicts = arrayListOf>().apply { @@ -572,12 +573,12 @@ class ExplorerPasteProvider : PasteProvider { if (!conflictsThatCannotBeOverwritten.contains(conflict)) { // Conflicts between text/binary files. val choice = Messages.showDialog( - project, - "Cannot move '${conflict.second.name}' to ${conflict.first.name}\n\n$newNameMessage", - "Name Conflict", - arrayOf("Skip", "Overwrite", "Use new name"), - 0, - Messages.getWarningIcon() + project, + "Cannot move '${conflict.second.name}' to ${conflict.first.name}\n\n$newNameMessage", + "Name Conflict", + arrayOf("Skip", "Overwrite", "Use new name"), + 0, + Messages.getWarningIcon() ) val resolution = ConflictResolution(conflict.second, conflict.first) when (choice) { @@ -595,12 +596,12 @@ class ExplorerPasteProvider : PasteProvider { "File '${conflict.second.name}' cannot replace directory '${conflict.second.name}'" } val choice = Messages.showDialog( - project, - "$messageToShow\n\n$newNameMessage", - "Name Conflict", - arrayOf("Skip", "Use new name"), - 0, - Messages.getWarningIcon() + project, + "$messageToShow\n\n$newNameMessage", + "Name Conflict", + arrayOf("Skip", "Use new name"), + 0, + Messages.getWarningIcon() ) val resolution = ConflictResolution(conflict.second, conflict.first) when (choice) { diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt index 31e23ca8..c61178d9 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt @@ -16,9 +16,9 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DoNotAskOption +import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages -import com.intellij.openapi.ui.messages.MessagesService +import com.intellij.openapi.ui.showYesNoDialog import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory @@ -44,15 +44,22 @@ import eu.ibagroup.formainframe.vfs.MFVirtualFile import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe -import io.mockk.* -import java.awt.Component -import java.lang.IllegalStateException +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk import java.util.* import java.util.concurrent.atomic.AtomicBoolean import javax.swing.Icon import javax.swing.tree.DefaultMutableTreeNode +import kotlin.reflect.KFunction class ExplorerPasteProviderTestSpec : ShouldSpec({ + beforeSpec { // FIXTURE SETUP TO HAVE ACCESS TO APPLICATION INSTANCE val factory = IdeaTestFixtureFactory.getFixtureFactory() @@ -82,7 +89,8 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ ExplorerPasteProvider(), recordPrivateCalls = true ) - var dataOpsManagerService = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl + var dataOpsManagerService = + ApplicationManager.getApplication().service() as TestDataOpsManagerImpl every { mockedFileExplorer.componentManager } returns ApplicationManager.getApplication() dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(mockedFileExplorer.componentManager) { override fun tryToGetAttributes(file: VirtualFile): FileAttributes { @@ -106,11 +114,12 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ mockkObject(FileExplorerContentProvider) //mockkObject(FileExplorerContentProvider::getInstance) - every { FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) } returns mockedFileExplorerView + every { + FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) + } returns mockedFileExplorerView - var isPastePerformed : Boolean - var exceptionIsThrown : Boolean - var isShowYesNoDialogCalled = false + var isPastePerformed: Boolean + var exceptionIsThrown: Boolean var isLockPerformed = false var isUnlockPerformed = false var isCallbackCalled = false @@ -126,34 +135,11 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ val mockedExplorerBase = mockk>() every { mockedFileExplorerView.explorer } returns mockedExplorerBase - every { mockedFileExplorerView.explorer.reportThrowable(any() as Throwable, mockedProject) } answers { + every { mockedFileExplorerView.explorer.reportThrowable(any(), mockedProject) } answers { exceptionIsThrown = true isPastePerformed = false } - mockkObject(MessagesService) - every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - return 0 - } - } - val nodeToRefreshSource = mockk() val nodeToRefreshTarget = mockk() beforeEach { @@ -298,7 +284,18 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ } should("perform paste without conflicts USS file -> PDS without skipping") { + var isShowYesNoDialogCalled = false isPastePerformed = false + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) + every { + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } + every { mockedFileExplorerView.isCut } returns AtomicBoolean(true) // source to paste @@ -352,77 +349,64 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any>(), any>(), any() ) - } returns - mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) + } returns mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe true } } should("perform paste without conflicts USS file -> PDS with skipping") { + var isShowYesNoDialogCalled = false isPastePerformed = false + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - isPastePerformed = true - return 1 - } - } + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + false + } +// val showDialogSpecificMock: ( +// Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? +// ) -> Int = Messages::showDialog +// mockkStatic(showDialogSpecificMock as KFunction<*>) +// every { +// showDialogSpecificMock( +// any(), any(), any(), any>(), any(), any() as Icon?, any() +// ) +// } answers { +// isShowYesNoDialogCalled = true +// isPastePerformed = true +// 1 +// } every { mockedFileExplorerView.isCut } returns AtomicBoolean(false) mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe true } } should("perform paste without conflicts PS file -> PDS without skipping") { + var isShowYesNoDialogCalled = false isPastePerformed = false + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - return 0 - } - } + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } every { mockedFileExplorerView.isCut } returns AtomicBoolean(true) @@ -487,12 +471,38 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe true } } should("perform paste to the same USS folder with conflicts and not overwriting") { + var isShowYesNoDialogCalled = false + var isShowDialogCalled = false isPastePerformed = false + + val showDialogMock: ( + Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + ) -> Int = Messages::showDialog + mockkStatic(showDialogMock as KFunction<*>) + every { + showDialogMock( + any(), any(), any(), any>(), any(), any() as Icon?, any() + ) + } answers { + isShowDialogCalled = true + 0 + } + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) + every { + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } + // source to paste val mockedSourceNodeData = mockk>() val mockedSourceNode = mockk() @@ -534,16 +544,15 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any>(), any>(), any() ) - } returns - mutableListOf(Pair(mockedSourceFile, mockedSourceFile)) - every { mockedSourceFile.findChild(any() as String) } returns null + } returns mutableListOf(Pair(mockedSourceFile, mockedSourceFile)) + every { mockedSourceFile.findChild(any()) } returns null mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowDialogCalled shouldBe true + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe false } } @@ -557,74 +566,72 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ } } - should("Declining move/download files") { - isPastePerformed = true + should("decline move/download files") { + var isShowYesNoDialogCalled = false + isPastePerformed = false every { mockedDataContext.getData(IS_DRAG_AND_DROP_KEY) } returns true + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - isPastePerformed = false - return 1 - } - } + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + false + } mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe false } } - should("Perform paste to the same USS folder with conflicts and accept overwriting") { + should("perform paste to the same USS folder with conflicts and accept overwriting") { + var isShowYesNoDialogCalled = false + var isShowDialogCalled = false isPastePerformed = false + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - return 0 - } - } + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } - mockkStatic(Messages::class) - every { Messages.showDialog(any() as Project, any() as String, any() as String, any() as Array, any() as Int, any() as Icon, null) } returns 1 + val showDialogMock: ( + Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + ) -> Int = Messages::showDialog + mockkStatic(showDialogMock as KFunction<*>) + every { + showDialogMock( + any(), any(), any(), any>(), any(), any() as Icon?, any() + ) + } answers { + isShowDialogCalled = true + 1 + } mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true + isShowDialogCalled shouldBe true isPastePerformed shouldBe true } } should("return if unrecognized dialog message") { isPastePerformed = true - mockkStatic(Messages::class) - every { Messages.showDialog(any() as Project, any() as String, any() as String, any() as Array, any() as Int, any() as Icon, null) } answers { + val showDialogSpecificMock: ( + Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + ) -> Int = Messages::showDialog + mockkStatic(showDialogSpecificMock as KFunction<*>) + every { + showDialogSpecificMock( + any(), any(), any(), any>(), any(), any() as Icon?, any() + ) + } answers { isPastePerformed = false 3 } @@ -698,32 +705,34 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ } should("perform paste with conflicts USS files (>5) -> Local folder + remote but declining download") { + var isShowYesNoDialogCalled = false isPastePerformed = true + every { mockedFileExplorerView.isCut } returns AtomicBoolean(false) - mockkStatic(Messages::class) - every { Messages.showDialog(any() as Project, any() as String, any() as String, any() as Array, any() as Int, any() as Icon, null) } returns 1 + + val showDialogSpecificMock: ( + Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + ) -> Int = Messages::showDialog + mockkStatic(showDialogSpecificMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - isPastePerformed = false - return 1 - } - } + showDialogSpecificMock( + any(), any(), any(), any>(), any(), any() as Icon?, any() + ) + } answers { + isShowYesNoDialogCalled = true + isPastePerformed = false + 1 + } + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) + every { + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + val dialogTitle = firstArg() + isShowYesNoDialogCalled = true + !dialogTitle.contains("Downloading Files") + } // source1 to paste val mockedSourceNodeData1 = mockk>() @@ -819,10 +828,13 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ val mockedTargetFile2Attributes = mockk() val mockedStructureTreeModelNodeTarget = mockk() val mockedNodeTarget = mockk() + every { mockedTargetFile1.parent } returns null every { mockedTargetFile1.name } returns "test_folder" every { mockedTargetFile1.children } returns arrayOf(childDestinationVirtualFile1) + every { mockedTargetFile3.parent } returns null every { mockedTargetFile3.name } returns "test_folder2" every { mockedTargetFile3.children } returns arrayOf(childDestinationVirtualFile3) + every { mockedTargetFile2.parent } returns null every { mockedTargetFile2.name } returns "test_mf_folder" every { mockedTargetFile2.children } returns arrayOf(childDestinationVirtualFile2) every { mockedStructureTreeModelNodeTarget.userObject } returns mockedNodeTarget @@ -835,7 +847,14 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ val copyPasteNodeData4 = mockk>() val copyPasteNodeData5 = mockk>() val copyPasteNodeData6 = mockk>() - val copyPasteNodeDataList = listOf(copyPasteNodeData1, copyPasteNodeData2, copyPasteNodeData3, copyPasteNodeData4, copyPasteNodeData5, copyPasteNodeData6) + val copyPasteNodeDataList = listOf( + copyPasteNodeData1, + copyPasteNodeData2, + copyPasteNodeData3, + copyPasteNodeData4, + copyPasteNodeData5, + copyPasteNodeData6 + ) val mockedCopyPasteBuffer = LinkedList(copyPasteNodeDataList) every { copyPasteNodeData1.node } returns mockedSourceNode1 every { copyPasteNodeData1.file } returns mockedSourceFile1 @@ -858,24 +877,31 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.copyPasteBuffer } returns mockedCopyPasteBuffer // selected node data ( always remote files ) - every { mockedFileExplorerView.mySelectedNodesData } returns listOf(mockedSourceNodeData1, mockedSourceNodeData2, mockedSourceNodeData3, - mockedSourceNodeData4, mockedSourceNodeData5, mockedSourceNodeData6) + every { mockedFileExplorerView.mySelectedNodesData } returns listOf( + mockedSourceNodeData1, mockedSourceNodeData2, mockedSourceNodeData3, + mockedSourceNodeData4, mockedSourceNodeData5, mockedSourceNodeData6 + ) every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any>(), any>(), any() ) } returns - mutableListOf(Pair(mockedTargetFile1, mockedSourceFile1), Pair(mockedTargetFile1, mockedSourceFile2), Pair(mockedTargetFile1, mockedSourceFile3), - Pair(mockedTargetFile1, mockedSourceFile4), Pair(mockedTargetFile1, mockedSourceFile5), Pair(mockedTargetFile1, mockedSourceFile6), - Pair(mockedTargetFile2, mockedSourceFile1), Pair(mockedTargetFile2, mockedSourceFile2), Pair(mockedTargetFile2, mockedSourceFile3), + mutableListOf( + Pair(mockedTargetFile1, mockedSourceFile1), + Pair(mockedTargetFile1, mockedSourceFile2), + Pair(mockedTargetFile1, mockedSourceFile3), + Pair(mockedTargetFile1, mockedSourceFile4), + Pair(mockedTargetFile1, mockedSourceFile5), + Pair(mockedTargetFile1, mockedSourceFile6), + Pair(mockedTargetFile2, mockedSourceFile1), + Pair(mockedTargetFile2, mockedSourceFile2), + Pair(mockedTargetFile2, mockedSourceFile3), Pair(mockedTargetFile3, mockedSourceFile2) - ) + ) - every { mockedTargetFile1.findChild(any() as String) } returns childDestinationVirtualFile1 - every { mockedTargetFile3.findChild(any() as String) } returns childDestinationVirtualFile3 + every { mockedTargetFile1.findChild(any()) } returns childDestinationVirtualFile1 + every { mockedTargetFile3.findChild(any()) } returns childDestinationVirtualFile3 every { dataOpsManagerService.testInstance.tryToGetAttributes(childDestinationVirtualFile1) } returns null every { dataOpsManagerService.testInstance.tryToGetAttributes(childDestinationVirtualFile2) } returns childDestMFFile2Attr @@ -892,39 +918,32 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedDataContext.getData(IS_DRAG_AND_DROP_KEY) } returns true every { mockedDataContext.getData(CommonDataKeys.PROJECT) } returns mockedProject - every { mockedDataContext.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) } returns arrayOf(mockedTargetFile1, mockedTargetFile2) + every { + mockedDataContext.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) + } returns arrayOf(mockedTargetFile1, mockedTargetFile2) every { mockedDataContext.getData(DRAGGED_FROM_PROJECT_FILES_ARRAY) } returns emptyList() mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe false } } should("perform paste without conflicts Local -> USS") { + var isShowYesNoDialogCalled = false isPastePerformed = false + every { mockedFileExplorerView.isCut } returns AtomicBoolean(true) + + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isShowYesNoDialogCalled = true - return 0 - } - } + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } // source to paste val mockedSourceFile = mockk() @@ -961,12 +980,9 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any>(), any>(), any() ) - } returns - mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) + } returns mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) every { dataOpsManagerService.testInstance.tryToGetAttributes(childDestinationVirtualFile) } returns childDestMFFileAttr every { dataOpsManagerService.testInstance.tryToGetAttributes(mockedSourceFile) } returns mockedSourceAttributes @@ -979,13 +995,24 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true isPastePerformed shouldBe true } } should("perform operation throws error") { + var isShowYesNoDialogCalled = false exceptionIsThrown = false + val showYesNoDialogMock: (String, String, Project?, String, String, Icon?) -> Boolean = ::showYesNoDialog + mockkStatic(showYesNoDialogMock as KFunction<*>) + every { + showYesNoDialogMock(any(), any(), any(), any(), any(), any()) + } answers { + isShowYesNoDialogCalled = true + true + } + dataOpsManagerService = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl every { mockedFileExplorer.componentManager } returns ApplicationManager.getApplication() dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(mockedFileExplorer.componentManager) { @@ -1047,15 +1074,13 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any>(), any>(), any() ) - } returns - mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) + } returns mutableListOf(Pair(mockedTargetFile, mockedSourceFile)) mockedExplorerPasteProvider.performPaste(mockedDataContext) assertSoftly { + isShowYesNoDialogCalled shouldBe true exceptionIsThrown shouldBe true } } @@ -1097,7 +1122,9 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ should("paste is not enabled and possible if project is null") { var isPasteEnabled = true var isPastePossible = true - every { FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) } returns mockedFileExplorerView + every { + FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) + } returns mockedFileExplorerView every { mockedDataContext.getData(CommonDataKeys.PROJECT) } answers { isPasteEnabled = false isPastePossible = false @@ -1139,7 +1166,9 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ } every { mockedDataContext.getData(CommonDataKeys.PROJECT) } returns mockedProject every { mockedDataContext.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) } returns null - every { FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) } returns mockedFileExplorerView + every { + FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) + } returns mockedFileExplorerView mockedExplorerPasteProvider.isPasteEnabled(mockedDataContext) mockedExplorerPasteProvider.isPastePossible(mockedDataContext) assertSoftly { @@ -1157,7 +1186,9 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ true } every { mockedDataContext.getData(CommonDataKeys.PROJECT) } returns mockedProject - every { FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) } returns mockedFileExplorerView + every { + FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) + } returns mockedFileExplorerView mockedExplorerPasteProvider.isPasteEnabled(mockedDataContext) mockedExplorerPasteProvider.isPastePossible(mockedDataContext) assertSoftly { @@ -1182,7 +1213,9 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ // prepare base mocks // val mockedDataOpsManager = mockk() // every { dataOpsManagerService.testInstance } returns mockedDataOpsManager - every { FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) } returns mockedFileExplorerView + every { + FileExplorerContentProvider.getInstance().getExplorerView(any() as Project) + } returns mockedFileExplorerView every { mockedFileExplorerView.copyPasteSupport } returns mockedCopyPasterProvider every { mockedCopyPasterProvider.getSourceFilesFromClipboard() } returns emptyList() @@ -1216,7 +1249,11 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ // CopyPaste node buffer - fun addMockedSourceFile(fileName: String, isPastePossible: Boolean = false, isDirectory: Boolean = false): MFVirtualFile { + fun addMockedSourceFile( + fileName: String, + isPastePossible: Boolean = false, + isDirectory: Boolean = false + ): MFVirtualFile { val mockedSourceAttributes = mockk() val mockedSourceFile = mockk() every { mockedSourceFile.name } returns fileName @@ -1264,11 +1301,11 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { mockedCopyPasterProvider.getDestinationSourceFilePairs( - any() as List, - any() as List, - any() as Boolean + any() as List, + any() as List, + any() as Boolean ) - } answers { copyPasteNodeDataList.mapNotNull { nodeData -> nodeData.file?.let{ Pair(mockedTargetFile, it) } } } + } answers { copyPasteNodeDataList.mapNotNull { nodeData -> nodeData.file?.let { Pair(mockedTargetFile, it) } } } should("Skip 2 files one by one") { addMockedSourceFile("file.txt") @@ -1281,11 +1318,19 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { Messages.showYesNoDialog( - any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? + any() as Project?, any(), any(), any(), any(), any() as Icon? ) } returns Messages.YES every { - Messages.showDialog(any() as Project?, any() as String, any() as String, any() as Array, 0, any() as Icon?, null) + Messages.showDialog( + any() as Project?, + any() as String, + any() as String, + any() as Array, + 0, + any() as Icon?, + null + ) } answers { if (!decideOptionSelected) { decideOptionSelected = true @@ -1313,11 +1358,19 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { Messages.showYesNoDialog( - any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? + any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? ) } returns Messages.YES every { - Messages.showDialog(any() as Project?, any() as String, any() as String, any() as Array, 0, any() as Icon?, null) + Messages.showDialog( + any() as Project?, + any() as String, + any() as String, + any() as Array, + 0, + any() as Icon?, + null + ) } answers { if (!decideOptionSelected) { decideOptionSelected = true @@ -1367,11 +1420,19 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { Messages.showYesNoDialog( - any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? + any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? ) } returns Messages.YES every { - Messages.showDialog(any() as Project?, any() as String, any() as String, any() as Array, 0, any() as Icon?, null) + Messages.showDialog( + any() as Project?, + any() as String, + any() as String, + any() as Array, + 0, + any() as Icon?, + null + ) } answers { if (!decideOptionSelected) { decideOptionSelected = true @@ -1418,11 +1479,19 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ every { Messages.showYesNoDialog( - any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? + any() as Project?, any() as String, any() as String, any() as String, any() as String, any() as Icon? ) } returns Messages.YES every { - Messages.showDialog(any() as Project?, any() as String, any() as String, any() as Array, 0, any() as Icon?, null) + Messages.showDialog( + any() as Project?, + any() as String, + any() as String, + any() as Array, + 0, + any() as Icon?, + null + ) } answers { if (!decideOptionSelected) { decideOptionSelected = true @@ -1457,4 +1526,4 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/UssFileNodeTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/UssFileNodeTestSpec.kt index c9fc98f2..f880ca7a 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/UssFileNodeTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/UssFileNodeTestSpec.kt @@ -19,8 +19,8 @@ import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileNavigator import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DoNotAskOption -import com.intellij.openapi.ui.messages.MessagesService +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.showYesNoDialog import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightProjectDescriptor @@ -33,7 +33,12 @@ import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes import eu.ibagroup.formainframe.dataops.content.synchronizer.ContentSynchronizer import eu.ibagroup.formainframe.dataops.content.synchronizer.DocumentedSyncProvider import eu.ibagroup.formainframe.dataops.content.synchronizer.SyncProvider -import eu.ibagroup.formainframe.explorer.* +import eu.ibagroup.formainframe.explorer.Explorer +import eu.ibagroup.formainframe.explorer.ExplorerUnit +import eu.ibagroup.formainframe.explorer.FileExplorer +import eu.ibagroup.formainframe.explorer.FileExplorerContentProvider +import eu.ibagroup.formainframe.explorer.UIComponentManager +import eu.ibagroup.formainframe.explorer.WorkingSet import eu.ibagroup.formainframe.testServiceImpl.TestDataOpsManagerImpl import eu.ibagroup.formainframe.utils.isBeingEditingNow import eu.ibagroup.formainframe.utils.service @@ -43,10 +48,17 @@ import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import io.mockk.* -import java.awt.Component +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk import javax.swing.Icon import javax.swing.tree.TreePath +import kotlin.reflect.KFunction class UssFileNodeTestSpec : ShouldSpec({ beforeSpec { @@ -210,29 +222,16 @@ class UssFileNodeTestSpec : ShouldSpec({ } var isErrorMessageInDialogCalled = false - mockkObject(MessagesService) + val showDialogSpecificMock: ( + Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + ) -> Int = Messages::showDialog + mockkStatic(showDialogSpecificMock as KFunction<*>) every { - MessagesService.getInstance() - } returns - object : TestMessagesService() { - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - isErrorMessageInDialogCalled = true - return 1 - } - - } + showDialogSpecificMock(any(), any(), any(), any>(), any(), any(), any()) + } answers { + isErrorMessageInDialogCalled = true + 1 + } ussFileNode.navigate(requestFocus) assertSoftly { isSyncWithRemotePerformed shouldBe true } @@ -460,7 +459,8 @@ class UssFileNodeTestSpec : ShouldSpec({ .INSTANCE .getExplorerContentProvider(explorerToTest::class.java) } returns null - val mockedUssNodeToTest = UssFileNode(virtualFileMock, mockedProject, parentNode, explorerUnitToTest, treeStructure) + val mockedUssNodeToTest = + UssFileNode(virtualFileMock, mockedProject, parentNode, explorerUnitToTest, treeStructure) val ussFileMockToSpyTest = spyk(mockedUssNodeToTest, recordPrivateCalls = true) every { ussFileMockToSpyTest["shouldUpdateData"]() } returns true every { ussFileMockToSpyTest["shouldPostprocess"]() } returns false @@ -508,7 +508,13 @@ class UssFileNodeTestSpec : ShouldSpec({ val treeStructure = mockk() every { treeStructure.registerNode(any()) } just Runs - val objectMock = object: ExplorerTreeNode(virtualFileMock, mockedProject, parentNode, explorer, treeStructure) { + val objectMock = object : ExplorerTreeNode( + virtualFileMock, + mockedProject, + parentNode, + explorer, + treeStructure + ) { override fun update(presentation: PresentationData) { return } @@ -553,7 +559,13 @@ class UssFileNodeTestSpec : ShouldSpec({ } } should("get path of the node if parent is not null") { - val objectParentMock = object: ExplorerTreeNode(virtualFileMock, mockedProject, parentNode, explorer, treeStructure) { + val objectParentMock = object : ExplorerTreeNode( + virtualFileMock, + mockedProject, + parentNode, + explorer, + treeStructure + ) { override fun update(presentation: PresentationData) { return } @@ -565,7 +577,13 @@ class UssFileNodeTestSpec : ShouldSpec({ mockkObject(objectParentMock, recordPrivateCalls = true) - val objectMockToTest = object: ExplorerTreeNode(virtualFileMock, mockedProject, objectParentMock, explorer, treeStructure) { + val objectMockToTest = object : ExplorerTreeNode( + virtualFileMock, + mockedProject, + objectParentMock, + explorer, + treeStructure + ) { override fun update(presentation: PresentationData) { return } diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt index 8be90880..2e8b5334 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt @@ -13,21 +13,15 @@ package eu.ibagroup.formainframe.explorer.ui import com.intellij.openapi.Disposable import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor -import com.intellij.openapi.fileEditor.* +import com.intellij.openapi.fileEditor.EditorDataProvider +import com.intellij.openapi.fileEditor.FileEditor +//import com.intellij.openapi.fileEditor.FileEditorComposite // TODO: needed in 1.*.*-223 and greater +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorNavigatable +import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DoNotAskOption -import com.intellij.openapi.ui.InputValidator -import com.intellij.openapi.ui.messages.MessagesService -import com.intellij.openapi.util.Pair -import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VirtualFile -import com.intellij.util.Function -import com.intellij.util.PairFunction -import java.awt.Component -import javax.swing.Icon -import javax.swing.JCheckBox import javax.swing.JComponent -import javax.swing.JTextField open class TestFileEditorManager : FileEditorManager() { // TODO: needed in 1.*.*-223 and greater @@ -124,144 +118,3 @@ open class TestFileEditorManager : FileEditorManager() { } } - -open class TestMessagesService : MessagesService { - override fun showChooseDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - values: Array?, - initialValue: String?, - icon: Icon? - ): Int { - TODO("Not yet implemented") - } - - override fun showEditableChooseDialog( - message: String?, - title: String?, - icon: Icon?, - values: Array?, - initialValue: String?, - validator: InputValidator? - ): String? { - TODO("Not yet implemented") - } - - override fun showErrorDialog(project: Project?, message: String?, title: String) { - TODO("Not yet implemented") - } - - override fun showInputDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - icon: Icon?, - initialValue: String?, - validator: InputValidator?, - selection: TextRange?, - comment: String? - ): String? { - TODO("Not yet implemented") - } - - override fun showInputDialogWithCheckBox( - message: String?, - title: String?, - checkboxText: String?, - checked: Boolean, - checkboxEnabled: Boolean, - icon: Icon?, - initialValue: String?, - validator: InputValidator? - ): Pair { - TODO("Not yet implemented") - } - - override fun showMessageDialog( - project: Project?, - parentComponent: Component?, - message: String?, - title: String?, - options: Array, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean, - helpId: String? - ): Int { - TODO("Not yet implemented") - } - - override fun showMoreInfoMessageDialog( - project: Project?, - message: String?, - title: String?, - moreInfo: String?, - options: Array?, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon? - ): Int { - TODO("Not yet implemented") - } - - override fun showMultilineInputDialog( - project: Project?, - message: String?, - title: String?, - initialValue: String?, - icon: Icon?, - validator: InputValidator? - ): String? { - TODO("Not yet implemented") - } - - override fun showPasswordDialog( - project: Project?, - message: String?, - title: String?, - icon: Icon?, - validator: InputValidator? - ): String? { - TODO("Not yet implemented") - } - - override fun showPasswordDialog( - parentComponent: Component, - message: String?, - title: String?, - icon: Icon?, - validator: InputValidator? - ): CharArray? { - TODO("Not yet implemented") - } - - override fun showTextAreaDialog( - textField: JTextField?, - title: String?, - dimensionServiceKey: String?, - parser: Function?>?, - lineJoiner: Function?, String?>? - ) { - TODO("Not yet implemented") - } - - override fun showTwoStepConfirmationDialog( - message: String?, - title: String?, - options: Array?, - checkboxText: String?, - checked: Boolean, - defaultOptionIndex: Int, - focusedOptionIndex: Int, - icon: Icon?, - exitFunc: PairFunction? - ): Int { - TODO("Not yet implemented") - } - -} \ No newline at end of file From 29007b9c008c3fac26849b7144d33e76777ac6e7 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Mon, 14 Aug 2023 18:41:57 +0200 Subject: [PATCH 21/25] Small cosmetics --- .../explorer/ui/ExplorerPasteProviderTestSpec.kt | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt index c61178d9..6f97ebe1 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProviderTestSpec.kt @@ -372,19 +372,6 @@ class ExplorerPasteProviderTestSpec : ShouldSpec({ isShowYesNoDialogCalled = true false } -// val showDialogSpecificMock: ( -// Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? -// ) -> Int = Messages::showDialog -// mockkStatic(showDialogSpecificMock as KFunction<*>) -// every { -// showDialogSpecificMock( -// any(), any(), any(), any>(), any(), any() as Icon?, any() -// ) -// } answers { -// isShowYesNoDialogCalled = true -// isPastePerformed = true -// 1 -// } every { mockedFileExplorerView.isCut } returns AtomicBoolean(false) From d0e505b1c97beae793fe0fef5ea3496e78001615 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Mon, 14 Aug 2023 18:47:14 +0200 Subject: [PATCH 22/25] Release/v1.1.0-223 ready --- .../kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt index e51d4eba..51514c80 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/explorer/ui/dummyClasses.kt @@ -15,7 +15,7 @@ import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.EditorDataProvider import com.intellij.openapi.fileEditor.FileEditor -//import com.intellij.openapi.fileEditor.FileEditorComposite // TODO: needed in 1.*.*-223 and greater +import com.intellij.openapi.fileEditor.FileEditorComposite import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorNavigatable import com.intellij.openapi.fileEditor.OpenFileDescriptor From 67d6b2be41febf509fff6376f7730160329bbdd5 Mon Sep 17 00:00:00 2001 From: Uladzislau Date: Wed, 16 Aug 2023 19:06:18 +0200 Subject: [PATCH 23/25] Small fix --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index dc40797b..13bb1301 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -159,6 +159,7 @@ tasks { finalizedBy("koverHtmlReport") systemProperty("idea.force.use.core.classloader", "true") systemProperty("idea.use.core.classloader.for.plugin.path", "true") + systemProperty("java.awt.headless", "true") afterSuite( KotlinClosure2({ desc, result -> From c9e5d8e1c36e6ae847041d0fb63a1356dd1d59fa Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Fri, 18 Aug 2023 14:16:44 +0300 Subject: [PATCH 24/25] Non-null assertion operator removed --- .../kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt index 3959ba05..a541c9d4 100755 --- a/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/api/ZosmfApiImpl.kt @@ -76,14 +76,15 @@ class ZosmfApiImpl : ZosmfApi { useBytesConverter: Boolean ): Api { val zosmfUrl = ZosmfUrl(url, isAllowSelfSigned) + val defaultApi = Pair, MutableMap>(hashMapOf(), hashMapOf()) if (!apis.containsKey(apiClass)) { synchronized(apis) { if (!apis.containsKey(apiClass)) { - apis[apiClass] = Pair(hashMapOf(), hashMapOf()) + apis[apiClass] = defaultApi } } } - val apiClassMap = apis[apiClass]!! + val apiClassMap = apis[apiClass] ?: defaultApi synchronized(apiClassMap) { if (!useBytesConverter && !apiClassMap.first.containsKey(zosmfUrl)) { apiClassMap.first[zosmfUrl] = buildApi(zosmfUrl.url, getOkHttpClient(zosmfUrl.isAllowSelfSigned), apiClass) @@ -94,7 +95,6 @@ class ZosmfApiImpl : ZosmfApi { } return if (!useBytesConverter) apiClassMap.first[zosmfUrl] as Api else apiClassMap.second[zosmfUrl] as Api } - } private val gsonFactory = GsonConverterFactory.create(GsonBuilder().create()) From f5ce666829eb62b622a8577cba0585ab1ab801ab Mon Sep 17 00:00:00 2001 From: Dzianis Lisiankou Date: Thu, 7 Sep 2023 11:42:26 +0300 Subject: [PATCH 25/25] Release/v1.1.0-221 prepared --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e067b1bb..df4ce0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file. -## `1.1.0 (2023-08-xx)` +## `1.1.0 (2023-09-07)` * Feature: GitHub issue #14: UX: Edit WS mask ([0d358d0d](https://github.com/zowe/zowe-explorer-intellij/commit/0d358d0d)) * Feature: GitHub issue #23: Double click on a working set or connection ([e7f040d7](https://github.com/zowe/zowe-explorer-intellij/commit/e7f040d7))