Commit fb00ce9a authored by chengx's avatar chengx Committed by Commit bot

Add more detailed JumpListIcon folder's move operation metric to UMA

JumpListIcons(Old) folders are getting unexpectively huge for some users.
This may be caused by file operation failures.

After analyzing those folders' problematic move operation metric
by landing crrev.com/2522163003/, we find that it is necessary
to dig into every major step in base::move() operation for better
understanding the root cause.

Added code to record more detailed JumpListIcons folder's move operation
status to UMA.

Will revert this CL after gathering user data for further analysis.

BUG=179576
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.win:win10_chromium_x64_rel_ng

Review-Url: https://codereview.chromium.org/2563483004
Cr-Commit-Position: refs/heads/master@{#437756}
parent 97a8683a
......@@ -4,9 +4,12 @@
#include "chrome/browser/win/jumplist.h"
#include <windows.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
......@@ -59,11 +62,13 @@ const int kDelayForJumplistUpdateInMS = 3500;
// JumpList folder's move operation status.
enum MoveOperationResult {
SUCCESS = 0,
MAX_PATH_CHECK_FAILED = 1 << 0,
MOVE_FILE_EX_FAILED = 1 << 1,
COPY_AND_DELETE_DIR_FAILED = 1 << 2,
DELETE_FAILED = 1 << 3,
END = 1 << 4
MOVE_FILE_EX_FAILED = 1 << 0,
COPY_DIR_FAILED = 1 << 1,
DELETE_FILE_RECURSIVE_FAILED = 1 << 2,
DELETE_SOURCE_FOLDER_FAILED = 1 << 3,
DELETE_TARGET_FOLDER_FAILED = 1 << 4,
DELETE_DIR_READ_ONLY = 1 << 5,
END = 1 << 6
};
// Append the common switches to each shell link.
......@@ -225,10 +230,202 @@ bool UpdateJumpList(const wchar_t* app_id,
// A wrapper function for recording UMA histogram.
void RecordFolderMoveResult(int move_operation_status) {
UMA_HISTOGRAM_ENUMERATION("WinJumplist.FolderMoveResults",
UMA_HISTOGRAM_ENUMERATION("WinJumplist.DetailedFolderMoveResults",
move_operation_status, MoveOperationResult::END);
}
// This function is an exact copy of //base for UMA debugging purpose of
// DeleteFile() below.
// Deletes all files and directories in a path.
// Returns false on the first failure it encounters.
bool DeleteFileRecursive(const base::FilePath& path,
const base::FilePath::StringType& pattern,
bool recursive) {
base::FileEnumerator traversal(
path, false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES, pattern);
for (base::FilePath current = traversal.Next(); !current.empty();
current = traversal.Next()) {
// Try to clear the read-only bit if we find it.
base::FileEnumerator::FileInfo info = traversal.GetInfo();
if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) &&
(recursive || !info.IsDirectory())) {
::SetFileAttributes(
current.value().c_str(),
info.find_data().dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
}
if (info.IsDirectory()) {
if (recursive && (!DeleteFileRecursive(current, pattern, true) ||
!::RemoveDirectory(current.value().c_str())))
return false;
} else if (!::DeleteFile(current.value().c_str())) {
return false;
}
}
return true;
}
// This function is a copy from //base for UMA debugging purpose.
// Deletes all files and directories in a path.
// Returns false on the first failure it encounters.
bool DeleteFile(const base::FilePath& path,
bool recursive,
int* move_operation_status) {
base::ThreadRestrictions::AssertIOAllowed();
if (path.empty())
return true;
if (path.value().length() >= MAX_PATH)
return false;
// Handle any path with wildcards.
if (path.BaseName().value().find_first_of(L"*?") !=
base::FilePath::StringType::npos) {
bool ret =
DeleteFileRecursive(path.DirName(), path.BaseName().value(), recursive);
if (!ret) {
*move_operation_status |=
MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
*move_operation_status |=
MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
}
return ret;
}
DWORD attr = ::GetFileAttributes(path.value().c_str());
// We're done if we can't find the path.
if (attr == INVALID_FILE_ATTRIBUTES)
return true;
// We may need to clear the read-only bit.
if ((attr & FILE_ATTRIBUTE_READONLY) &&
!::SetFileAttributes(path.value().c_str(),
attr & ~FILE_ATTRIBUTE_READONLY)) {
*move_operation_status |= MoveOperationResult::DELETE_DIR_READ_ONLY;
*move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
*move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
return false;
}
// Directories are handled differently if they're recursive.
if (!(attr & FILE_ATTRIBUTE_DIRECTORY))
return !!::DeleteFile(path.value().c_str());
// Handle a simple, single file delete.
if (!recursive || DeleteFileRecursive(path, L"*", true)) {
bool ret = !!::RemoveDirectory(path.value().c_str());
if (!ret)
*move_operation_status |=
MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
return ret;
}
*move_operation_status |= MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
*move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
return false;
}
// This function is mostly copied from //base.
// It has some issue when the dest dir string contains the src dir string.
// Temporary fix by replacing the old logic with IsParent() call.
bool CopyDirectoryTemp(const base::FilePath& from_path,
const base::FilePath& to_path,
bool recursive) {
// NOTE(maruel): Previous version of this function used to call
// SHFileOperation(). This used to copy the file attributes and extended
// attributes, OLE structured storage, NTFS file system alternate data
// streams, SECURITY_DESCRIPTOR. In practice, this is not what we want, we
// want the containing directory to propagate its SECURITY_DESCRIPTOR.
base::ThreadRestrictions::AssertIOAllowed();
// NOTE: I suspect we could support longer paths, but that would involve
// analyzing all our usage of files.
if (from_path.value().length() >= MAX_PATH ||
to_path.value().length() >= MAX_PATH) {
return false;
}
// This function does not properly handle destinations within the source.
base::FilePath real_to_path = to_path;
if (base::PathExists(real_to_path)) {
real_to_path = base::MakeAbsoluteFilePath(real_to_path);
if (real_to_path.empty())
return false;
} else {
real_to_path = base::MakeAbsoluteFilePath(real_to_path.DirName());
if (real_to_path.empty())
return false;
}
base::FilePath real_from_path = base::MakeAbsoluteFilePath(from_path);
if (real_from_path.empty())
return false;
// Originally this CopyDirectory function returns false when to_path string
// contains from_path string. While this can be that the to_path is within
// the from_path, e.g., parent-child relationship in terms of folder, it
// also can be the two paths are in the same folder, just that one name
// contains
// the other, e.g. C:\\JumpListIcon and C:\\JumpListIconOld.
// We make this function not return false in the latter situation by calling
// IsParent() function.
if (real_to_path.value().size() >= real_from_path.value().size() &&
real_from_path.IsParent(real_to_path)) {
return false;
}
int traverse_type = base::FileEnumerator::FILES;
if (recursive)
traverse_type |= base::FileEnumerator::DIRECTORIES;
base::FileEnumerator traversal(from_path, recursive, traverse_type);
if (!base::PathExists(from_path)) {
DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
<< from_path.value().c_str();
return false;
}
// TODO(maruel): This is not necessary anymore.
DCHECK(recursive || base::DirectoryExists(from_path));
base::FilePath current = from_path;
bool from_is_dir = base::DirectoryExists(from_path);
bool success = true;
base::FilePath from_path_base = from_path;
if (recursive && base::DirectoryExists(to_path)) {
// If the destination already exists and is a directory, then the
// top level of source needs to be copied.
from_path_base = from_path.DirName();
}
while (success && !current.empty()) {
// current is the source path, including from_path, so append
// the suffix after from_path to to_path to create the target_path.
base::FilePath target_path(to_path);
if (from_path_base != current) {
if (!from_path_base.AppendRelativePath(current, &target_path)) {
success = false;
break;
}
}
if (from_is_dir) {
if (!base::DirectoryExists(target_path) &&
!::CreateDirectory(target_path.value().c_str(), NULL)) {
DLOG(ERROR) << "CopyDirectory() couldn't create directory: "
<< target_path.value().c_str();
success = false;
}
} else if (!base::CopyFile(current, target_path)) {
DLOG(ERROR) << "CopyDirectory() couldn't create file: "
<< target_path.value().c_str();
success = false;
}
current = traversal.Next();
if (!current.empty())
from_is_dir = traversal.GetInfo().IsDirectory();
}
return success;
}
// This function is a temporary fork of Move() and MoveUnsafe() in
// base/files/file_util.h, where UMA functions are added to gather user data.
// As //base is highly mature, we tend not to add this temporary function
......@@ -244,14 +441,12 @@ bool MoveDebugWithUMA(const base::FilePath& from_path,
// This variable records the status of move operations.
int move_operation_status = MoveOperationResult::SUCCESS;
if (folder_delete_fail)
move_operation_status |= MoveOperationResult::DELETE_FAILED;
move_operation_status |= MoveOperationResult::DELETE_TARGET_FOLDER_FAILED;
// NOTE: I suspect we could support longer paths, but that would involve
// analyzing all our usage of files.
if (from_path.value().length() >= MAX_PATH ||
to_path.value().length() >= MAX_PATH) {
move_operation_status |= MoveOperationResult::MAX_PATH_CHECK_FAILED;
RecordFolderMoveResult(move_operation_status);
return false;
}
if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(),
......@@ -271,14 +466,21 @@ bool MoveDebugWithUMA(const base::FilePath& from_path,
// MoveFileEx fails if moving directory across volumes. We will simulate
// the move by using Copy and Delete. Ideally we could check whether
// from_path and to_path are indeed in different volumes.
ret = base::internal::CopyAndDeleteDirectory(from_path, to_path);
if (CopyDirectoryTemp(from_path, to_path, true)) {
if (DeleteFile(from_path, true, &move_operation_status))
ret = true;
} else {
move_operation_status |= MoveOperationResult::COPY_DIR_FAILED;
move_operation_status |=
MoveOperationResult::DELETE_FILE_RECURSIVE_FAILED;
move_operation_status |= MoveOperationResult::DELETE_SOURCE_FOLDER_FAILED;
}
}
if (!ret) {
// Leave a clue about what went wrong so that it can be (at least) picked
// up by a PLOG entry.
::SetLastError(last_error);
move_operation_status |= MoveOperationResult::COPY_AND_DELETE_DIR_FAILED;
}
RecordFolderMoveResult(move_operation_status);
......
......@@ -74736,8 +74736,26 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
<summary>The type of category clicked in the Windows Jumplist</summary>
</histogram>
<histogram name="WinJumplist.DetailedFolderMoveResults"
enum="JumplistIconsDetailedFolderMoveCategory">
<owner>chengx@chromium.org</owner>
<summary>
This metric is recorded when folder JumpListIcons is moved (can be rename or
copy and delete) to JumpListIconsOld. This happens when tabs are closed,
mostly visited URLs get updated, etc. There are several key steps in this
folder move operation. Before the move operation, there is another step that
JumpListIconsOld folder is deleted. The status of these steps are put
together and recorded in this metric. The failure of any of these steps is
suspected to be related to a known issue.
</summary>
</histogram>
<histogram name="WinJumplist.FolderMoveResults"
enum="JumplistIconsFolderMoveCategory">
<obsolete>
Obselete 12/08/2016. DetailedFolderMoveResults is used for more detailed
analysis.
</obsolete>
<owner>chengx@chromium.org</owner>
<summary>
This metric is recorded when folder JumpListIcons is moved (can be rename or
......@@ -91424,6 +91442,201 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</int>
</enum>
<enum name="JumplistIconsDetailedFolderMoveCategory" type="int">
<int value="0"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="1"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="2"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="3"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="4"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="5"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="6"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="7"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="8"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="9"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="10"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="11"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="12"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="13"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="14"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="15"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="16"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="17"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="18"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="19"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Succeed"/>
<int value="20"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="21"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="22"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="23"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Succeed"/>
<int value="24"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="25"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="26"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="27"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Succeed - DelSourceDir Fail"/>
<int value="28"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="29"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="30"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="31"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Succeed - DelFile Fail - DelSourceDir Fail"/>
<int value="32"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="33"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="34"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="35"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="36"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="37"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="38"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="39"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="40"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="41"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="42"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="43"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="44"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="45"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="46"
label="DelTargetDir Succeed - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="47"
label="DelTargetDir Succeed - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="48"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="49"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="50"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="51"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Succeed"/>
<int value="52"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="53"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="54"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="55"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Succeed"/>
<int value="56"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="57"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="58"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="59"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Succeed - DelSourceDir Fail"/>
<int value="60"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="61"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Succeed -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="62"
label="DelTargetDir Fail - MoveFileEx Succeed - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
<int value="63"
label="DelTargetDir Fail - MoveFileEx Fail - CopyFile Fail -
SourceDirRead Fail - DelFile Fail - DelSourceDir Fail"/>
</enum>
<enum name="JumplisticonsfolderCategory" type="int">
<int value="0" label="Del Succeed - Mov Succeed - Create Succeed"/>
<int value="1" label="Del Fail - Mov Succeed - Create Succeed"/>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment