{"action":"created","comment":{"url":"https://api.github.com/repos/apache/spark/pulls/comments/34866416","id":34866416,"diff_hunk":"@@ -672,164 +658,605 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage(\"stage\") {\n     </script>\n   }\n \n-  def taskRow(\n-      hasAccumulators: Boolean,\n-      hasInput: Boolean,\n-      hasOutput: Boolean,\n-      hasShuffleRead: Boolean,\n-      hasShuffleWrite: Boolean,\n-      hasBytesSpilled: Boolean,\n-      currentTime: Long)(taskData: TaskUIData): Seq[Node] = {\n-    taskData match { case TaskUIData(info, metrics, errorMessage) =>\n-      val duration = if (info.status == \"RUNNING\") info.timeRunning(currentTime)\n-        else metrics.map(_.executorRunTime).getOrElse(1L)\n-      val formatDuration = if (info.status == \"RUNNING\") UIUtils.formatDuration(duration)\n-        else metrics.map(m => UIUtils.formatDuration(m.executorRunTime)).getOrElse(\"\")\n-      val schedulerDelay = metrics.map(getSchedulerDelay(info, _, currentTime)).getOrElse(0L)\n-      val gcTime = metrics.map(_.jvmGCTime).getOrElse(0L)\n-      val taskDeserializationTime = metrics.map(_.executorDeserializeTime).getOrElse(0L)\n-      val serializationTime = metrics.map(_.resultSerializationTime).getOrElse(0L)\n-      val gettingResultTime = getGettingResultTime(info, currentTime)\n-\n-      val maybeAccumulators = info.accumulables\n-      val accumulatorsReadable = maybeAccumulators.map { acc =>\n-        StringEscapeUtils.escapeHtml4(s\"${acc.name}: ${acc.update.get}\")\n+}\n+\n+private[ui] object StagePage {\n+  private[ui] def getGettingResultTime(info: TaskInfo, currentTime: Long): Long = {\n+    if (info.gettingResult) {\n+      if (info.finished) {\n+        info.finishTime - info.gettingResultTime\n+      } else {\n+        // The task is still fetching the result.\n+        currentTime - info.gettingResultTime\n       }\n+    } else {\n+      0L\n+    }\n+  }\n \n-      val maybeInput = metrics.flatMap(_.inputMetrics)\n-      val inputSortable = maybeInput.map(_.bytesRead.toString).getOrElse(\"\")\n-      val inputReadable = maybeInput\n-        .map(m => s\"${Utils.bytesToString(m.bytesRead)} (${m.readMethod.toString.toLowerCase()})\")\n-        .getOrElse(\"\")\n-      val inputRecords = maybeInput.map(_.recordsRead.toString).getOrElse(\"\")\n-\n-      val maybeOutput = metrics.flatMap(_.outputMetrics)\n-      val outputSortable = maybeOutput.map(_.bytesWritten.toString).getOrElse(\"\")\n-      val outputReadable = maybeOutput\n-        .map(m => s\"${Utils.bytesToString(m.bytesWritten)}\")\n-        .getOrElse(\"\")\n-      val outputRecords = maybeOutput.map(_.recordsWritten.toString).getOrElse(\"\")\n-\n-      val maybeShuffleRead = metrics.flatMap(_.shuffleReadMetrics)\n-      val shuffleReadBlockedTimeSortable = maybeShuffleRead\n-        .map(_.fetchWaitTime.toString).getOrElse(\"\")\n-      val shuffleReadBlockedTimeReadable =\n-        maybeShuffleRead.map(ms => UIUtils.formatDuration(ms.fetchWaitTime)).getOrElse(\"\")\n-\n-      val totalShuffleBytes = maybeShuffleRead.map(_.totalBytesRead)\n-      val shuffleReadSortable = totalShuffleBytes.map(_.toString).getOrElse(\"\")\n-      val shuffleReadReadable = totalShuffleBytes.map(Utils.bytesToString).getOrElse(\"\")\n-      val shuffleReadRecords = maybeShuffleRead.map(_.recordsRead.toString).getOrElse(\"\")\n-\n-      val remoteShuffleBytes = maybeShuffleRead.map(_.remoteBytesRead)\n-      val shuffleReadRemoteSortable = remoteShuffleBytes.map(_.toString).getOrElse(\"\")\n-      val shuffleReadRemoteReadable = remoteShuffleBytes.map(Utils.bytesToString).getOrElse(\"\")\n-\n-      val maybeShuffleWrite = metrics.flatMap(_.shuffleWriteMetrics)\n-      val shuffleWriteSortable = maybeShuffleWrite.map(_.shuffleBytesWritten.toString).getOrElse(\"\")\n-      val shuffleWriteReadable = maybeShuffleWrite\n-        .map(m => s\"${Utils.bytesToString(m.shuffleBytesWritten)}\").getOrElse(\"\")\n-      val shuffleWriteRecords = maybeShuffleWrite\n-        .map(_.shuffleRecordsWritten.toString).getOrElse(\"\")\n-\n-      val maybeWriteTime = metrics.flatMap(_.shuffleWriteMetrics).map(_.shuffleWriteTime)\n-      val writeTimeSortable = maybeWriteTime.map(_.toString).getOrElse(\"\")\n-      val writeTimeReadable = maybeWriteTime.map(t => t / (1000 * 1000)).map { ms =>\n-        if (ms == 0) \"\" else UIUtils.formatDuration(ms)\n-      }.getOrElse(\"\")\n-\n-      val maybeMemoryBytesSpilled = metrics.map(_.memoryBytesSpilled)\n-      val memoryBytesSpilledSortable = maybeMemoryBytesSpilled.map(_.toString).getOrElse(\"\")\n-      val memoryBytesSpilledReadable =\n-        maybeMemoryBytesSpilled.map(Utils.bytesToString).getOrElse(\"\")\n-\n-      val maybeDiskBytesSpilled = metrics.map(_.diskBytesSpilled)\n-      val diskBytesSpilledSortable = maybeDiskBytesSpilled.map(_.toString).getOrElse(\"\")\n-      val diskBytesSpilledReadable = maybeDiskBytesSpilled.map(Utils.bytesToString).getOrElse(\"\")\n-\n-      <tr>\n-        <td>{info.index}</td>\n-        <td>{info.taskId}</td>\n-        <td sorttable_customkey={info.attempt.toString}>{\n-          if (info.speculative) s\"${info.attempt} (speculative)\" else info.attempt.toString\n-        }</td>\n-        <td>{info.status}</td>\n-        <td>{info.taskLocality}</td>\n-        <td>{info.executorId} / {info.host}</td>\n-        <td>{UIUtils.formatDate(new Date(info.launchTime))}</td>\n-        <td sorttable_customkey={duration.toString}>\n-          {formatDuration}\n-        </td>\n-        <td sorttable_customkey={schedulerDelay.toString}\n-            class={TaskDetailsClassNames.SCHEDULER_DELAY}>\n-          {UIUtils.formatDuration(schedulerDelay.toLong)}\n-        </td>\n-        <td sorttable_customkey={taskDeserializationTime.toString}\n-            class={TaskDetailsClassNames.TASK_DESERIALIZATION_TIME}>\n-          {UIUtils.formatDuration(taskDeserializationTime.toLong)}\n-        </td>\n-        <td sorttable_customkey={gcTime.toString}>\n-          {if (gcTime > 0) UIUtils.formatDuration(gcTime) else \"\"}\n-        </td>\n-        <td sorttable_customkey={serializationTime.toString}\n-            class={TaskDetailsClassNames.RESULT_SERIALIZATION_TIME}>\n-          {UIUtils.formatDuration(serializationTime)}\n-        </td>\n-        <td sorttable_customkey={gettingResultTime.toString}\n-            class={TaskDetailsClassNames.GETTING_RESULT_TIME}>\n-          {UIUtils.formatDuration(gettingResultTime)}\n-        </td>\n-        {if (hasAccumulators) {\n-          <td>\n-            {Unparsed(accumulatorsReadable.mkString(\"<br/>\"))}\n-          </td>\n-        }}\n-        {if (hasInput) {\n-          <td sorttable_customkey={inputSortable}>\n-            {s\"$inputReadable / $inputRecords\"}\n-          </td>\n-        }}\n-        {if (hasOutput) {\n-          <td sorttable_customkey={outputSortable}>\n-            {s\"$outputReadable / $outputRecords\"}\n-          </td>\n-        }}\n+  private[ui] def getSchedulerDelay(\n+      info: TaskInfo, metrics: TaskMetrics, currentTime: Long): Long = {\n+    if (info.finished) {\n+      val totalExecutionTime = info.finishTime - info.launchTime\n+      val executorOverhead = (metrics.executorDeserializeTime +\n+        metrics.resultSerializationTime)\n+      math.max(\n+        0,\n+        totalExecutionTime - metrics.executorRunTime - executorOverhead -\n+          getGettingResultTime(info, currentTime))\n+    } else {\n+      // The task is still running and the metrics like executorRunTime are not available.\n+      0L\n+    }\n+  }\n+}\n+\n+private[ui] case class TaskTableRowInputData(inputSortable: String, inputReadable: String)\n+\n+private[ui] case class TaskTableRowOutputData(outputSortable: String, outputReadable: String)\n+\n+private[ui] case class TaskTableRowShuffleReadData(\n+    shuffleReadBlockedTimeSortable: String,\n+    shuffleReadBlockedTimeReadable: String,\n+    shuffleReadSortable: String,\n+    shuffleReadReadable: String,\n+    shuffleReadRemoteSortable: String,\n+    shuffleReadRemoteReadable: String)\n+\n+private[ui] case class TaskTableRowShuffleWriteData(\n+    writeTimeSortable: String,\n+    writeTimeReadable: String,\n+    shuffleWriteSortable: String,\n+    shuffleWriteReadable: String)\n+\n+private[ui] case class TaskTableRowBytesSpilledData(\n+    memoryBytesSpilledSortable: String,\n+    memoryBytesSpilledReadable: String,\n+    diskBytesSpilledSortable: String,\n+    diskBytesSpilledReadable: String)\n+\n+/**\n+ * Contains all data that needs for sorting and generating HTML. Using this one rather than\n+ * TaskUIData to avoid creating duplicate contents during sorting the data.\n+ */\n+private[ui] case class TaskTableRowData(\n+    index: Int,\n+    taskId: Long,\n+    attempt: Int,\n+    speculative: Boolean,\n+    status: String,\n+    taskLocality: String,\n+    executorIdAndHost: String,\n+    launchTime: Long,\n+    duration: Long,\n+    formatDuration: String,\n+    schedulerDelay: Long,\n+    taskDeserializationTime: Long,\n+    gcTime: Long,\n+    serializationTime: Long,\n+    gettingResultTime: Long,\n+    accumulators: Option[String], // HTML\n+    input: Option[TaskTableRowInputData],\n+    output: Option[TaskTableRowOutputData],\n+    shuffleRead: Option[TaskTableRowShuffleReadData],\n+    shuffleWrite: Option[TaskTableRowShuffleWriteData],\n+    bytesSpilled: Option[TaskTableRowBytesSpilledData],\n+    error: String)\n+\n+private[ui] class TaskDataSource(\n+    tasks: Seq[TaskUIData],\n+    hasAccumulators: Boolean,\n+    hasInput: Boolean,\n+    hasOutput: Boolean,\n+    hasShuffleRead: Boolean,\n+    hasShuffleWrite: Boolean,\n+    hasBytesSpilled: Boolean,\n+    currentTime: Long,\n+    page: Int,\n+    pageSize: Int,\n+    sortColumn: String,\n+    desc: Boolean) extends PagedDataSource[TaskTableRowData](page: Int, pageSize: Int) {\n+  import StagePage._\n+\n+  // Convert TaskUIData to TaskTableRowData which contains the final contents to show in the table\n+  // so that we can avoid creating duplicate contents during sorting the data\n+  override val data = tasks.map(taskRow).sorted(ordering(sortColumn, desc))\n+\n+  private def taskRow(taskData: TaskUIData): TaskTableRowData = {\n+    val TaskUIData(info, metrics, errorMessage) = taskData\n+    val duration = if (info.status == \"RUNNING\") info.timeRunning(currentTime)\n+    else metrics.map(_.executorRunTime).getOrElse(1L)\n+    val formatDuration = if (info.status == \"RUNNING\") UIUtils.formatDuration(duration)\n+    else metrics.map(m => UIUtils.formatDuration(m.executorRunTime)).getOrElse(\"\")\n+    val schedulerDelay = metrics.map(getSchedulerDelay(info, _, currentTime)).getOrElse(0L)\n+    val gcTime = metrics.map(_.jvmGCTime).getOrElse(0L)\n+    val taskDeserializationTime = metrics.map(_.executorDeserializeTime).getOrElse(0L)\n+    val serializationTime = metrics.map(_.resultSerializationTime).getOrElse(0L)\n+    val gettingResultTime = getGettingResultTime(info, currentTime)\n+\n+    val maybeAccumulators = info.accumulables\n+    val accumulatorsReadable = maybeAccumulators.map { acc =>\n+      StringEscapeUtils.escapeHtml4(s\"${acc.name}: ${acc.update.get}\")\n+    }\n+\n+    val maybeInput = metrics.flatMap(_.inputMetrics)\n+    val inputSortable = maybeInput.map(_.bytesRead.toString).getOrElse(\"\")\n+    val inputReadable = maybeInput\n+      .map(m => s\"${Utils.bytesToString(m.bytesRead)} (${m.readMethod.toString.toLowerCase()})\")\n+      .getOrElse(\"\")\n+    val inputRecords = maybeInput.map(_.recordsRead.toString).getOrElse(\"\")\n+\n+    val maybeOutput = metrics.flatMap(_.outputMetrics)\n+    val outputSortable = maybeOutput.map(_.bytesWritten.toString).getOrElse(\"\")\n+    val outputReadable = maybeOutput\n+      .map(m => s\"${Utils.bytesToString(m.bytesWritten)}\")\n+      .getOrElse(\"\")\n+    val outputRecords = maybeOutput.map(_.recordsWritten.toString).getOrElse(\"\")\n+\n+    val maybeShuffleRead = metrics.flatMap(_.shuffleReadMetrics)\n+    val shuffleReadBlockedTimeSortable = maybeShuffleRead\n+      .map(_.fetchWaitTime.toString).getOrElse(\"\")\n+    val shuffleReadBlockedTimeReadable =\n+      maybeShuffleRead.map(ms => UIUtils.formatDuration(ms.fetchWaitTime)).getOrElse(\"\")\n+\n+    val totalShuffleBytes = maybeShuffleRead.map(_.totalBytesRead)\n+    val shuffleReadSortable = totalShuffleBytes.map(_.toString).getOrElse(\"\")\n+    val shuffleReadReadable = totalShuffleBytes.map(Utils.bytesToString).getOrElse(\"\")\n+    val shuffleReadRecords = maybeShuffleRead.map(_.recordsRead.toString).getOrElse(\"\")\n+\n+    val remoteShuffleBytes = maybeShuffleRead.map(_.remoteBytesRead)\n+    val shuffleReadRemoteSortable = remoteShuffleBytes.map(_.toString).getOrElse(\"\")\n+    val shuffleReadRemoteReadable = remoteShuffleBytes.map(Utils.bytesToString).getOrElse(\"\")\n+\n+    val maybeShuffleWrite = metrics.flatMap(_.shuffleWriteMetrics)\n+    val shuffleWriteSortable = maybeShuffleWrite.map(_.shuffleBytesWritten.toString).getOrElse(\"\")\n+    val shuffleWriteReadable = maybeShuffleWrite\n+      .map(m => s\"${Utils.bytesToString(m.shuffleBytesWritten)}\").getOrElse(\"\")\n+    val shuffleWriteRecords = maybeShuffleWrite\n+      .map(_.shuffleRecordsWritten.toString).getOrElse(\"\")\n+\n+    val maybeWriteTime = metrics.flatMap(_.shuffleWriteMetrics).map(_.shuffleWriteTime)\n+    val writeTimeSortable = maybeWriteTime.map(_.toString).getOrElse(\"\")\n+    val writeTimeReadable = maybeWriteTime.map(t => t / (1000 * 1000)).map { ms =>\n+      if (ms == 0) \"\" else UIUtils.formatDuration(ms)\n+    }.getOrElse(\"\")\n+\n+    val maybeMemoryBytesSpilled = metrics.map(_.memoryBytesSpilled)\n+    val memoryBytesSpilledSortable = maybeMemoryBytesSpilled.map(_.toString).getOrElse(\"\")\n+    val memoryBytesSpilledReadable =\n+      maybeMemoryBytesSpilled.map(Utils.bytesToString).getOrElse(\"\")\n+\n+    val maybeDiskBytesSpilled = metrics.map(_.diskBytesSpilled)\n+    val diskBytesSpilledSortable = maybeDiskBytesSpilled.map(_.toString).getOrElse(\"\")\n+    val diskBytesSpilledReadable = maybeDiskBytesSpilled.map(Utils.bytesToString).getOrElse(\"\")\n+\n+    val input =\n+      if (hasInput) {\n+        Some(TaskTableRowInputData(inputSortable, s\"$inputReadable / $inputRecords\"))\n+      } else {\n+        None\n+      }\n+\n+    val output =\n+      if (hasOutput) {\n+        Some(TaskTableRowOutputData(outputSortable, s\"$outputReadable / $outputRecords\"))\n+      } else {\n+        None\n+      }\n+\n+    val shuffleRead =\n+      if (hasShuffleRead) {\n+        Some(TaskTableRowShuffleReadData(\n+          shuffleReadBlockedTimeSortable,\n+          shuffleReadBlockedTimeReadable,\n+          shuffleReadSortable,\n+          s\"$shuffleReadReadable / $shuffleReadRecords\",\n+          shuffleReadRemoteSortable,\n+          shuffleReadRemoteReadable\n+        ))\n+      } else {\n+        None\n+      }\n+\n+    val shuffleWrite =\n+      if (hasShuffleWrite) {\n+        Some(TaskTableRowShuffleWriteData(\n+          writeTimeSortable,\n+          writeTimeReadable,\n+          shuffleWriteSortable,\n+          s\"$shuffleWriteReadable / $shuffleWriteRecords\"\n+        ))\n+      } else {\n+        None\n+      }\n+\n+    val bytesSpilled =\n+      if (hasBytesSpilled) {\n+        Some(TaskTableRowBytesSpilledData(\n+          memoryBytesSpilledSortable,\n+          memoryBytesSpilledReadable,\n+          diskBytesSpilledSortable,\n+          diskBytesSpilledReadable\n+        ))\n+      } else {\n+        None\n+      }\n+\n+    TaskTableRowData(\n+      info.index,\n+      info.taskId,\n+      info.attempt,\n+      info.speculative,\n+      info.status,\n+      info.taskLocality.toString,\n+      s\"${info.executorId} / ${info.host}\",\n+      info.launchTime,\n+      duration,\n+      formatDuration,\n+      schedulerDelay,\n+      taskDeserializationTime,\n+      gcTime,\n+      serializationTime,\n+      gettingResultTime,\n+      if (hasAccumulators) Some(accumulatorsReadable.mkString(\"<br/>\")) else None,\n+      input,\n+      output,\n+      shuffleRead,\n+      shuffleWrite,\n+      bytesSpilled,\n+      errorMessage.getOrElse(\"\")\n+    )\n+  }\n+\n+  /**\n+   * Return Ordering according to sortColumn and desc\n+   */\n+  private def ordering(sortColumn: String, desc: Boolean): Ordering[TaskTableRowData] = {\n+    val ordering = sortColumn match {\n+      case \"Index\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Int.compare(x.index, y.index)\n+      }\n+      case \"ID\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.taskId, y.taskId)\n+      }\n+      case \"Attempt\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Int.compare(x.attempt, y.attempt)\n+      }\n+      case \"Status\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.String.compare(x.status, y.status)\n+      }\n+      case \"Locality Level\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.String.compare(x.taskLocality, y.taskLocality)\n+      }\n+      case \"Executor ID / Host\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.String.compare(x.executorIdAndHost, y.executorIdAndHost)\n+      }\n+      case \"Launch Time\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.launchTime, y.launchTime)\n+      }\n+      case \"Duration\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.duration, y.duration)\n+      }\n+      case \"Scheduler Delay\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.schedulerDelay, y.schedulerDelay)\n+      }\n+      case \"Task Deserialization Time\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.taskDeserializationTime, y.taskDeserializationTime)\n+      }\n+      case \"GC Time\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.gcTime, y.gcTime)\n+      }\n+      case \"Result Serialization Time\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.serializationTime, y.serializationTime)\n+      }\n+      case \"Getting Result Time\" => new Ordering[TaskTableRowData] {\n+        override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+          Ordering.Long.compare(x.gettingResultTime, y.gettingResultTime)\n+      }\n+      case \"Accumulators\" =>\n+        if (hasAccumulators) {\n+          new Ordering[TaskTableRowData] {\n+            override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+              Ordering.String.compare(x.accumulators.get, y.accumulators.get)\n+          }\n+        } else {\n+          throw new IllegalArgumentException(\n+            \"Cannot sort by Accumulators because of no accumulators\")\n+        }\n+      case \"Input Size / Records\" =>\n+        if (hasInput) {\n+          new Ordering[TaskTableRowData] {\n+            override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+              Ordering.String.compare(x.input.get.inputSortable, y.input.get.inputSortable)\n+          }\n+        } else {\n+          throw new IllegalArgumentException(\n+            \"Cannot sort by Input Size / Records because of no inputs\")\n+        }\n+      case \"Output Size / Records\" =>\n+        if (hasOutput) {\n+          new Ordering[TaskTableRowData] {\n+            override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+              Ordering.String.compare(x.output.get.outputSortable, y.output.get.outputSortable)\n+          }\n+        } else {\n+          throw new IllegalArgumentException(\n+            \"Cannot sort by Input Size / Records because of no outputs\")\n+        }\n+      // ShuffleRead\n+      case \"Shuffle Read Blocked Time\" =>\n+        if (hasShuffleRead) {\n+          new Ordering[TaskTableRowData] {\n+            override def compare(x: TaskTableRowData, y: TaskTableRowData): Int =\n+              Ordering.String.compare(x.shuffleRead.get.shuffleReadBlockedTimeSortable,\n+                y.shuffleRead.get.shuffleReadBlockedTimeSortable)\n+          }\n+        } else {\n+          throw new IllegalArgumentException(\n+            \"Cannot sort by Input Size / Records because of no shuffle reads\")","path":"core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala","position":599,"original_position":599,"commit_id":"d9285f0a33b556dd1978c45e3e0c3963d65bceb3","original_commit_id":"d9285f0a33b556dd1978c45e3e0c3963d65bceb3","user":{"login":"carsonwang","id":9278199,"avatar_url":"https://avatars.githubusercontent.com/u/9278199?v=3","gravatar_id":"","url":"https://api.github.com/users/carsonwang","html_url":"https://github.com/carsonwang","followers_url":"https://api.github.com/users/carsonwang/followers","following_url":"https://api.github.com/users/carsonwang/following{/other_user}","gists_url":"https://api.github.com/users/carsonwang/gists{/gist_id}","starred_url":"https://api.github.com/users/carsonwang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/carsonwang/subscriptions","organizations_url":"https://api.github.com/users/carsonwang/orgs","repos_url":"https://api.github.com/users/carsonwang/repos","events_url":"https://api.github.com/users/carsonwang/events{/privacy}","received_events_url":"https://api.github.com/users/carsonwang/received_events","type":"User","site_admin":false},"body":"\"Cannot sort by Shuffle Read Blocked Time\"","created_at":"2015-07-17T07:04:58Z","updated_at":"2015-07-17T07:04:58Z","html_url":"https://github.com/apache/spark/pull/7399#discussion_r34866416","pull_request_url":"https://api.github.com/repos/apache/spark/pulls/7399","_links":{"self":{"href":"https://api.github.com/repos/apache/spark/pulls/comments/34866416"},"html":{"href":"https://github.com/apache/spark/pull/7399#discussion_r34866416"},"pull_request":{"href":"https://api.github.com/repos/apache/spark/pulls/7399"}}},"pull_request":{"url":"https://api.github.com/repos/apache/spark/pulls/7399","id":39939392,"html_url":"https://github.com/apache/spark/pull/7399","diff_url":"https://github.com/apache/spark/pull/7399.diff","patch_url":"https://github.com/apache/spark/pull/7399.patch","issue_url":"https://api.github.com/repos/apache/spark/issues/7399","number":7399,"state":"open","locked":false,"title":"[SPARK-4598][WebUI]Task table pagination for the Stage page","user":{"login":"zsxwing","id":1000778,"avatar_url":"https://avatars.githubusercontent.com/u/1000778?v=3","gravatar_id":"","url":"https://api.github.com/users/zsxwing","html_url":"https://github.com/zsxwing","followers_url":"https://api.github.com/users/zsxwing/followers","following_url":"https://api.github.com/users/zsxwing/following{/other_user}","gists_url":"https://api.github.com/users/zsxwing/gists{/gist_id}","starred_url":"https://api.github.com/users/zsxwing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zsxwing/subscriptions","organizations_url":"https://api.github.com/users/zsxwing/orgs","repos_url":"https://api.github.com/users/zsxwing/repos","events_url":"https://api.github.com/users/zsxwing/events{/privacy}","received_events_url":"https://api.github.com/users/zsxwing/received_events","type":"User","site_admin":false},"body":"This PR adds pagination for the task table to solve the scalability issue of the stage page. Here is the initial screenshot:\r\n<img width=\"1347\" alt=\"pagination\" src=\"https://cloud.githubusercontent.com/assets/1000778/8679669/9e63863c-2a8e-11e5-94e4-994febcd6717.png\">\r\nThe task table only shows 100 tasks. There is a page navigation above the table. Users can click the page navigation or type the page number to jump to another page. The table can be sorted by clicking the headers. However, unlike previous implementation, the sorting work is done in the server now. So clicking a table column to sort needs to refresh the web page.\r\n","created_at":"2015-07-14T17:16:35Z","updated_at":"2015-07-17T07:04:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"905c6409c932a3f2dcd5ac4dd0953b88cf6c0cfe","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/apache/spark/pulls/7399/commits","review_comments_url":"https://api.github.com/repos/apache/spark/pulls/7399/comments","review_comment_url":"https://api.github.com/repos/apache/spark/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/spark/issues/7399/comments","statuses_url":"https://api.github.com/repos/apache/spark/statuses/d9285f0a33b556dd1978c45e3e0c3963d65bceb3","head":{"label":"zsxwing:task-table-pagination","ref":"task-table-pagination","sha":"d9285f0a33b556dd1978c45e3e0c3963d65bceb3","user":{"login":"zsxwing","id":1000778,"avatar_url":"https://avatars.githubusercontent.com/u/1000778?v=3","gravatar_id":"","url":"https://api.github.com/users/zsxwing","html_url":"https://github.com/zsxwing","followers_url":"https://api.github.com/users/zsxwing/followers","following_url":"https://api.github.com/users/zsxwing/following{/other_user}","gists_url":"https://api.github.com/users/zsxwing/gists{/gist_id}","starred_url":"https://api.github.com/users/zsxwing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zsxwing/subscriptions","organizations_url":"https://api.github.com/users/zsxwing/orgs","repos_url":"https://api.github.com/users/zsxwing/repos","events_url":"https://api.github.com/users/zsxwing/events{/privacy}","received_events_url":"https://api.github.com/users/zsxwing/received_events","type":"User","site_admin":false},"repo":{"id":17894272,"name":"spark","full_name":"zsxwing/spark","owner":{"login":"zsxwing","id":1000778,"avatar_url":"https://avatars.githubusercontent.com/u/1000778?v=3","gravatar_id":"","url":"https://api.github.com/users/zsxwing","html_url":"https://github.com/zsxwing","followers_url":"https://api.github.com/users/zsxwing/followers","following_url":"https://api.github.com/users/zsxwing/following{/other_user}","gists_url":"https://api.github.com/users/zsxwing/gists{/gist_id}","starred_url":"https://api.github.com/users/zsxwing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zsxwing/subscriptions","organizations_url":"https://api.github.com/users/zsxwing/orgs","repos_url":"https://api.github.com/users/zsxwing/repos","events_url":"https://api.github.com/users/zsxwing/events{/privacy}","received_events_url":"https://api.github.com/users/zsxwing/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/zsxwing/spark","description":"Mirror of Apache Spark","fork":true,"url":"https://api.github.com/repos/zsxwing/spark","forks_url":"https://api.github.com/repos/zsxwing/spark/forks","keys_url":"https://api.github.com/repos/zsxwing/spark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zsxwing/spark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zsxwing/spark/teams","hooks_url":"https://api.github.com/repos/zsxwing/spark/hooks","issue_events_url":"https://api.github.com/repos/zsxwing/spark/issues/events{/number}","events_url":"https://api.github.com/repos/zsxwing/spark/events","assignees_url":"https://api.github.com/repos/zsxwing/spark/assignees{/user}","branches_url":"https://api.github.com/repos/zsxwing/spark/branches{/branch}","tags_url":"https://api.github.com/repos/zsxwing/spark/tags","blobs_url":"https://api.github.com/repos/zsxwing/spark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zsxwing/spark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zsxwing/spark/git/refs{/sha}","trees_url":"https://api.github.com/repos/zsxwing/spark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zsxwing/spark/statuses/{sha}","languages_url":"https://api.github.com/repos/zsxwing/spark/languages","stargazers_url":"https://api.github.com/repos/zsxwing/spark/stargazers","contributors_url":"https://api.github.com/repos/zsxwing/spark/contributors","subscribers_url":"https://api.github.com/repos/zsxwing/spark/subscribers","subscription_url":"https://api.github.com/repos/zsxwing/spark/subscription","commits_url":"https://api.github.com/repos/zsxwing/spark/commits{/sha}","git_commits_url":"https://api.github.com/repos/zsxwing/spark/git/commits{/sha}","comments_url":"https://api.github.com/repos/zsxwing/spark/comments{/number}","issue_comment_url":"https://api.github.com/repos/zsxwing/spark/issues/comments{/number}","contents_url":"https://api.github.com/repos/zsxwing/spark/contents/{+path}","compare_url":"https://api.github.com/repos/zsxwing/spark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zsxwing/spark/merges","archive_url":"https://api.github.com/repos/zsxwing/spark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zsxwing/spark/downloads","issues_url":"https://api.github.com/repos/zsxwing/spark/issues{/number}","pulls_url":"https://api.github.com/repos/zsxwing/spark/pulls{/number}","milestones_url":"https://api.github.com/repos/zsxwing/spark/milestones{/number}","notifications_url":"https://api.github.com/repos/zsxwing/spark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zsxwing/spark/labels{/name}","releases_url":"https://api.github.com/repos/zsxwing/spark/releases{/id}","created_at":"2014-03-19T06:21:04Z","updated_at":"2015-03-09T07:52:42Z","pushed_at":"2015-07-17T04:22:51Z","git_url":"git://github.com/zsxwing/spark.git","ssh_url":"git@github.com:zsxwing/spark.git","clone_url":"https://github.com/zsxwing/spark.git","svn_url":"https://github.com/zsxwing/spark","homepage":null,"size":107648,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"adb33d3665770daf2ccb8915d19e198be9dc3b47","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":17165658,"name":"spark","full_name":"apache/spark","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/spark","description":"Mirror of Apache Spark","fork":false,"url":"https://api.github.com/repos/apache/spark","forks_url":"https://api.github.com/repos/apache/spark/forks","keys_url":"https://api.github.com/repos/apache/spark/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/spark/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/spark/teams","hooks_url":"https://api.github.com/repos/apache/spark/hooks","issue_events_url":"https://api.github.com/repos/apache/spark/issues/events{/number}","events_url":"https://api.github.com/repos/apache/spark/events","assignees_url":"https://api.github.com/repos/apache/spark/assignees{/user}","branches_url":"https://api.github.com/repos/apache/spark/branches{/branch}","tags_url":"https://api.github.com/repos/apache/spark/tags","blobs_url":"https://api.github.com/repos/apache/spark/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/spark/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/spark/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/spark/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/spark/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/spark/languages","stargazers_url":"https://api.github.com/repos/apache/spark/stargazers","contributors_url":"https://api.github.com/repos/apache/spark/contributors","subscribers_url":"https://api.github.com/repos/apache/spark/subscribers","subscription_url":"https://api.github.com/repos/apache/spark/subscription","commits_url":"https://api.github.com/repos/apache/spark/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/spark/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/spark/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/spark/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/spark/contents/{+path}","compare_url":"https://api.github.com/repos/apache/spark/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/spark/merges","archive_url":"https://api.github.com/repos/apache/spark/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/spark/downloads","issues_url":"https://api.github.com/repos/apache/spark/issues{/number}","pulls_url":"https://api.github.com/repos/apache/spark/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/spark/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/spark/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/spark/labels{/name}","releases_url":"https://api.github.com/repos/apache/spark/releases{/id}","created_at":"2014-02-25T08:00:08Z","updated_at":"2015-07-17T06:57:33Z","pushed_at":"2015-07-17T06:57:56Z","git_url":"git://github.com/apache/spark.git","ssh_url":"git@github.com:apache/spark.git","clone_url":"https://github.com/apache/spark.git","svn_url":"https://github.com/apache/spark","homepage":null,"size":1546955,"stargazers_count":4774,"watchers_count":4774,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":4263,"mirror_url":"git://git.apache.org/spark.git","open_issues_count":332,"forks":4263,"open_issues":332,"watchers":4774,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/spark/pulls/7399"},"html":{"href":"https://github.com/apache/spark/pull/7399"},"issue":{"href":"https://api.github.com/repos/apache/spark/issues/7399"},"comments":{"href":"https://api.github.com/repos/apache/spark/issues/7399/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/spark/pulls/7399/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/spark/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/spark/pulls/7399/commits"},"statuses":{"href":"https://api.github.com/repos/apache/spark/statuses/d9285f0a33b556dd1978c45e3e0c3963d65bceb3"}}}}