Skip to content

Commit

Permalink
Merge branch 'private-release/v1.2.4-221' into zowe-release/v1.2.4-221
Browse files Browse the repository at this point in the history
Signed-off-by: Uladzislau <[email protected]>
  • Loading branch information
KUGDev committed Nov 12, 2024
2 parents e6a3a49 + 2ba100d commit a4c2f5a
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 70 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file.

## [Unreleased]

### Bugfixes

* Bugfix: Fixed issue with cross-system folder copy with name conflicts ([c5345051](https://github.com/zowe/zowe-explorer-intellij/commit/c5345051))
* Bugfix: Clarified 401 error ([f36ec547](https://github.com/zowe/zowe-explorer-intellij/commit/f36ec547))
* Bugfix: Fixed issue with USS copy that would lead to incorrect encoding error ([b4cb705a](https://github.com/zowe/zowe-explorer-intellij/commit/b4cb705a))
* Bugfix: Fixed issue with jobs DDs display in console ([8a75f21a](https://github.com/zowe/zowe-explorer-intellij/commit/8a75f21a))
* Bugfix: Fixed issue with copying another user's folder in USS ([66676550](https://github.com/zowe/zowe-explorer-intellij/commit/66676550))
* Bugfix: Some encodings are made unsupported ([080c25ab](https://github.com/zowe/zowe-explorer-intellij/commit/080c25ab))
* Bugfix: Fixed issue during a file upload to a PDS ([954bf01f](https://github.com/zowe/zowe-explorer-intellij/commit/954bf01f))
* Bugfix: Fixed issue with URL field when typing incorrect information, the last slash at the end of https:// is deleted ([91ab962d](https://github.com/zowe/zowe-explorer-intellij/commit/91ab962d))

## [1.2.3-221] (2024-10-03)

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ import org.zowe.explorer.config.connect.*
import org.zowe.explorer.config.connect.ui.ChangePasswordDialog
import org.zowe.explorer.config.connect.ui.ChangePasswordDialogState
import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.dataops.operations.*
import org.zowe.explorer.dataops.operations.ChangePasswordOperation
import org.zowe.explorer.dataops.operations.InfoOperation
import org.zowe.explorer.dataops.operations.ZOSInfoOperation
import org.zowe.explorer.explorer.EXPLORER_NOTIFICATION_GROUP_ID
import org.zowe.explorer.utils.*
import org.zowe.explorer.utils.crudable.Crudable
import org.zowe.explorer.utils.crudable.find
import org.zowe.explorer.utils.crudable.getAll
import org.zowe.explorer.utils.runTask
import org.zowe.explorer.utils.validateConnectionName
import org.zowe.explorer.utils.validateForBlank
import org.zowe.explorer.utils.validateZosmfUrl
import org.zowe.kotlinsdk.ChangePassword
import org.zowe.kotlinsdk.annotations.ZVersion
import java.awt.Component
Expand All @@ -59,10 +64,11 @@ class ConnectionDialog(
* In case of DialogMode.UPDATE takes the last successful state from crudable, takes default state otherwise
*/
private val lastSuccessfulState: ConnectionDialogState =
if(state.mode == DialogMode.UPDATE) crudable.find<ConnectionConfig> { it.uuid == state.connectionUuid }
if (state.mode == DialogMode.UPDATE) crudable.find<ConnectionConfig> { it.uuid == state.connectionUuid }
.findAny()
.orElseGet { state.connectionConfig }
.toDialogState(crudable) else ConnectionDialogState()

companion object {

/** Show Test connection dialog and test the connection regarding the dialog state.
Expand All @@ -81,11 +87,17 @@ class ConnectionDialog(
initialState = initialState,
factory = { ConnectionDialog(crudable, initialState, project) },
test = { state ->
val newTestedConnConfig : ConnectionConfig
val newTestedConnConfig: ConnectionConfig
if (initialState.mode == DialogMode.UPDATE) {
val newState = state.clone()
newState.initEmptyUuids(crudable)
newTestedConnConfig = ConnectionConfig(newState.connectionUuid, newState.connectionName, newState.connectionUrl, newState.isAllowSsl, newState.zVersion)
newTestedConnConfig = ConnectionConfig(
newState.connectionUuid,
newState.connectionName,
newState.connectionUrl,
newState.isAllowSsl,
newState.zVersion
)
CredentialService.instance.setCredentials(
connectionConfigUuid = newState.connectionUuid,
username = newState.username,
Expand All @@ -97,7 +109,8 @@ class ConnectionDialog(
CredentialService.instance.setCredentials(
connectionConfigUuid = state.connectionUuid,
username = state.username,
password = state.password)
password = state.password
)
}
val throwable = runTask(title = "Testing Connection to ${newTestedConnConfig.url}", project = project) {
return@runTask try {
Expand Down Expand Up @@ -169,7 +182,7 @@ class ConnectionDialog(
EXPLORER_NOTIFICATION_GROUP_ID,
"Unable to retrieve USS username",
"Cannot retrieve USS username. An error happened while executing TSO request.\n" +
"When working with USS files the same username will be used that was specified by the user when connecting.",
"When working with USS files the same username will be used that was specified by the user when connecting.",
NotificationType.WARNING
).let {
Notifications.Bus.notify(it, project)
Expand Down Expand Up @@ -222,7 +235,7 @@ class ConnectionDialog(
textField()
.bindText(state::connectionUrl)
.validationOnApply {
it.text = it.text.trim().removeTrailingSlashes()
it.text = it.text.trim()
validateForBlank(it) ?: validateZosmfUrl(it)
}
.also { urlTextField = it.component }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ data class RemoteUssAttributes(
val gid: Long? = null,
val groupId: String? = null,
val modificationTime: String? = null,
val symlinkTarget: String? = null
val symlinkTarget: String? = null,
var charset: Charset = DEFAULT_BINARY_CHARSET
) : MFRemoteFileAttributes<ConnectionConfig, UssRequester>, Copyable {

/**
Expand All @@ -98,7 +99,8 @@ data class RemoteUssAttributes(
gid = ussFile.gid,
groupId = ussFile.groupId,
modificationTime = ussFile.modificationTime,
symlinkTarget = ussFile.target
symlinkTarget = ussFile.target,
charset = DEFAULT_BINARY_CHARSET
)

/**
Expand Down Expand Up @@ -163,8 +165,6 @@ data class RemoteUssAttributes(
|| mode == FileModeValue.READ_WRITE_EXECUTE.mode
}

var charset: Charset = DEFAULT_BINARY_CHARSET

override var contentMode: XIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY)

override val isCopyPossible: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class RemoteUssAttributesService(
gid = newAttributes.gid,
groupId = newAttributes.groupId,
modificationTime = newAttributes.modificationTime,
symlinkTarget = newAttributes.symlinkTarget
symlinkTarget = newAttributes.symlinkTarget,
charset = oldAttributes.charset
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ abstract class AbstractMFLoggerBase<PInfo: MFProcessInfo, LFetcher: LogFetcher<P
this.onFinishHandler = finishHandler
}

/**
* Fetch log files
*/
override fun fetchLog() {
logFetcher.fetchLog(mfProcessInfo)
}

/**
* Sets onNextLog handler.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/org/zowe/explorer/dataops/log/MFLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ interface MFLogger<LFetcher: LogFetcher<out MFProcessInfo>> {
*/
fun onLogFinished(finishHandler: () -> Unit)

/**
* Fetch log files
*/
fun fetchLog()

/**
* Sets handler for event after requesting next portion of mainframe log.
* @param nextLogHandler handler that will be invoked after next request to fetching log from mainframe.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ class InfoOperationRunner : OperationRunner<InfoOperation, SystemsResponse> {
.cancelByIndicator(progressIndicator)
.execute()
if (!response.isSuccessful) {
val headMessage = responseMessageMap[response.message()] ?: response.message()
log.info("Test connection response: $response")
val zosmfMessage = response.message().trim()
val headMessage = if (zosmfMessage.isNotEmpty())
responseMessageMap[zosmfMessage] ?: zosmfMessage
else responseMessageMap["Unauthorized"] ?: zosmfMessage
throw CallException(response, headMessage)
}
return response.body() ?: throw CallException(response, "Cannot parse z/OSMF info request body")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class CrossSystemMemberOrUssFileToPdsMoverFactory : OperationRunnerFactory {
*/
class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) : AbstractFileMover() {

private val sourceContentType = "text/plain; charset=UTF-8"

/**
* Checks that source is member or uss file, dest is partitioned data set, and they are located inside different systems.
* @see OperationRunner.canRun
Expand Down Expand Up @@ -93,17 +95,22 @@ class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) :
else XIBMDataType(XIBMDataType.Type.TEXT)

val sourceContent = sourceFile.contentsToByteArray()
val contentToUpload =
if (sourceFile.fileType.isBinary) sourceContent
else sourceContent.toString(sourceFile.charset).replace("\r\n", "\n")
.toByteArray(DEFAULT_TEXT_CHARSET).addNewLine()

// do not convert bytes to default ISO. z/OSMF does this conversion by calling iconv and produce the desired result
val contentToUpload = if (sourceFile.fileType.isBinary) sourceContent else
sourceContent
.toString(sourceFile.charset)
.replace("\r\n", "\n")
.toByteArray()
.addNewLine()

val response = apiWithBytesConverter<DataAPI>(destConnectionConfig).writeToDatasetMember(
authorizationToken = destConnectionConfig.authToken,
datasetName = destAttributes.name,
memberName = memberName,
content = contentToUpload,
xIBMDataType = xIBMDataType
xIBMDataType = xIBMDataType,
contentType = sourceContentType
).applyIfNotNull(progressIndicator) { indicator ->
cancelByIndicator(indicator)
}.execute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM
sourceFileFetchProvider.reload(sourceQuery)
}

val pathToDir = destAttributes.path + "/" + sourceFile.name
val pathToDir = destAttributes.path + "/" + (operation.newName ?: sourceFile.name)

if (operation.forceOverwriting) {
destFile.children.firstOrNull { it.name == sourceFile.name }?.let {
Expand All @@ -110,7 +110,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM
val createdDirFile = attributesService.getOrCreateVirtualFile(
RemoteUssAttributes(
destAttributes.path,
UssFile(sourceFile.name, "drwxrwxrwx"),
UssFile(operation.newName ?: sourceFile.name, "drwxrwxrwx"),
destConnectionConfig.url,
destConnectionConfig
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFi
from = from,
overwrite = operation.forceOverwriting,
links = CopyDataUSS.Links.ALL,
preserve = CopyDataUSS.Preserve.ALL,
preserve = CopyDataUSS.Preserve.NONE,
recursive = true
),
filePath = FilePath(
Expand Down
109 changes: 78 additions & 31 deletions src/main/kotlin/org/zowe/explorer/ui/build/jobs/JobBuildTreeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.dataops.log.JobLogFetcher
import org.zowe.explorer.dataops.log.JobProcessInfo
import org.zowe.explorer.dataops.log.MFLogger
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.zowe.kotlinsdk.SpoolFile
import java.awt.BorderLayout
import java.util.*
import javax.swing.JComponent
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.TreeNode

val JOBS_LOG_VIEW = DataKey.create<JobBuildTreeView>("jobsLogView")
const val JOBS_LOG_NOTIFICATION_GROUP_ID = "org.zowe.explorer.explorer.ExplorerNotificationGroup"
Expand Down Expand Up @@ -100,20 +103,25 @@ class JobBuildTreeView(
treeConsoleView.component.add(it.component, BorderLayout.PAGE_START)
}

jobLogger.onNextLog {
jobLogger.startLogging()

fun onNextLog() {
val cachedSpoolLog = jobLogger.logFetcher.getCachedLog()
cachedSpoolLog
.minus(spoolFileToLogMap.keys)
.forEach {
if (cachedSpoolLog.count() != spoolFileToLogMap.count()) {
cachedSpoolLog.minus(spoolFileToLogMap.keys).forEach {
treeConsoleView.onEvent(buildId, StartEventImpl(it.key.id, buildId, Date().time, it.key.ddName))
}
cachedSpoolLog
.forEach {
cachedSpoolLog.forEach {
val prevLog = spoolFileToLogMap[it.key] ?: ""
val logToDisplay = if (it.value.length >= prevLog.length) it.value.substring(prevLog.length) else prevLog
treeConsoleView.onEvent(buildId, OutputBuildEventImpl(it.key.id, logToDisplay, true))
spoolFileToLogMap[it.key] = it.value
}
}
}

jobLogger.onNextLog {
onNextLog()
}

jobLogger.onLogFinished {
Expand All @@ -122,43 +130,82 @@ class JobBuildTreeView(
.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"
//Variables were added to set the correct icon depending on the result of the job execution.
//For any execution status, FailureResultImpl will be used to display DDs
val ret = if (rc?.contains("CC") == true) { // 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 -> ReturnCode.SUCCESS
SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING -> ReturnCode.WARNING
else -> ReturnCode.ERROR
}
} else ReturnCode.ERROR

SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING -> {
codeWithWarning = true
SuccessResultImpl()
}
jobLogger.fetchLog()
var finalLogFiles = jobLogger.logFetcher.getCachedLog()
if (finalLogFiles.count() != spoolFileToLogMap.count()) {
onNextLog()
}

else -> FailureResultImpl()
}
} else SuccessResultImpl()
runBlocking {
//TODO Need to be reworked
//It is possible that not all DDs are displayed (but information about them already exists),
// And, accordingly, the correct icon cannot be set for them.
//Sleep for 1 second to wait for display
if ((treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild.childCount < spoolFileToLogMap.count())
delay(1000)

setIconRec(
(treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild,
ret
)
}

jobLogger.logFetcher.getCachedLog()
finalLogFiles
.forEach {
treeConsoleView.onEvent(buildId, FinishEventImpl(it.key.id, buildId, Date().time, it.key.ddName, result))
}
runCatching {
val buildNode = (treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild
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 }
treeConsoleView.onEvent(
buildId,
FinishEventImpl(it.key.id, buildId, Date().time, it.key.ddName, FailureResultImpl())
)
}
treeConsoleView.onEvent(
buildId,
FinishBuildEventImpl(buildId, buildId, Date().time, buildId, FailureResultImpl())
)
}
}

/**
* The function recursively sets the job execution status icon
*/
private fun setIconRec(buildNode: TreeNode, ret: ReturnCode) {
setIcon(buildNode, ret)
if (buildNode.childCount > 0)
for (currChild in buildNode.children()) {
setIcon(currChild, ret)
}
treeConsoleView.onEvent(buildId, FinishBuildEventImpl(buildId, buildId, Date().time, buildId, result))
}

/**
* The function sets the job execution status icon
*/
private fun setIcon(buildNode: TreeNode, ret: ReturnCode) {
val buildExecutionNode = (buildNode as DefaultMutableTreeNode).userObject as ExecutionNode
when (ret) {
ReturnCode.ERROR -> buildExecutionNode.setIconProvider { AllIcons.General.BalloonError }
ReturnCode.WARNING -> buildExecutionNode.setIconProvider { AllIcons.General.BalloonWarning }
ReturnCode.SUCCESS -> buildExecutionNode.setIconProvider { AllIcons.General.InspectionsOK }
}
}

jobLogger.startLogging()
/**
* Enum for job execution status
*/
enum class ReturnCode {
SUCCESS, WARNING, ERROR
}


/**
* Stops requesting logs from mainframe.
*/
Expand Down
Loading

0 comments on commit a4c2f5a

Please sign in to comment.