Source code
Revision control
Copy as Markdown
Other Tools
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Overview - Web Platform Test</title>
    <link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" />
    <link rel="stylesheet" href="css/fontawesome-5.7.2.min.css" />
    <script src="lib/utils.js"></script>
    <script src="lib/wave-service.js"></script>
    <script src="lib/ui.js"></script>
    <style>
      .site-logo {
        max-width: 300px;
        margin: 0 0 30px -15px;
      }
      .disabled-row {
        color: gray;
        background: lightgray;
      }
    </style>
  </head>
  <body>
    <script>
      window.onload = () => {
        const query = utils.parseQuery(location.search);
        if (query.token) {
          location.href = WEB_ROOT + "results.html" + location.search;
        }
        resultsUi.render();
        resultsUi.loadData();
      };
      var sortDetail = {};
      const defaultSortDetail = { sortColumn: "dateStarted", ascending: true };
      sortDetail["recentSessions"] = defaultSortDetail;
      sortDetail["pinnedSessions"] = defaultSortDetail;
      sortDetail["publicSessions"] = defaultSortDetail;
      const resultsUi = {
        state: {
          comparison: [],
          recentSessions: null,
          importResultsEnabled: false,
          filterLabels: []
        },
        loadData() {
          const pinnedSessions = WaveService.getPinnedSessions().filter(
            token => !!token
          );
          const recentSessions = WaveService.getRecentSessions().filter(
            token => !!token
          );
          pinnedSessions.forEach(token => {
            const index = recentSessions.indexOf(token);
            if (index !== -1) recentSessions.splice(index, 1);
          });
          WaveService.setRecentSessions(recentSessions);
          let allSessions = [];
          allSessions = allSessions.concat(pinnedSessions);
          allSessions = allSessions.concat(recentSessions);
          WaveService.readPublicSessions(publicSessions => {
            publicSessions.forEach(token => {
              const index = recentSessions.indexOf(token);
              if (index !== -1) recentSessions.splice(index, 1);
            });
            WaveService.setRecentSessions(recentSessions);
            allSessions = allSessions.concat(publicSessions);
            WaveService.readMultipleSessions(allSessions, configurations =>
              WaveService.readMultipleSessionStatuses(allSessions, statuses => {
                configurations.forEach(configuration => {
                  const status = statuses.find(
                    status => status.token === configuration.token
                  );
                  configuration.dateStarted = status.dateStarted;
                  configuration.dateFinished = status.dateFinished;
                  configuration.status = status.status;
                });
                configurations = configurations.filter(
                  configuration => !!configuration
                );
                allSessions
                  .filter(
                    token =>
                      !configurations.some(
                        configuration => configuration.token === token
                      )
                  )
                  .forEach(token => {
                    WaveService.removePinnedSession(token);
                    WaveService.removeRecentSession(token);
                  });
                resultsUi.state.publicSessions = publicSessions;
                resultsUi.state.pinnedSessions = WaveService.getPinnedSessions();
                resultsUi.state.recentSessions = WaveService.getRecentSessions();
                const sessions = {};
                configurations.forEach(
                  configuration =>
                    (sessions[configuration.token] = configuration)
                );
                resultsUi.state.sessions = sessions;
                const referenceTokens = [];
                const loadedSessionsTokens = Object.keys(sessions);
                configurations.forEach(configuration =>
                  configuration.referenceTokens
                    .filter(token => loadedSessionsTokens.indexOf(token) === -1)
                    .forEach(token => referenceTokens.push(token))
                );
                WaveService.readMultipleSessions(
                  referenceTokens,
                  configurations => {
                    const { sessions } = resultsUi.state;
                    configurations.forEach(
                      configuration =>
                        (sessions[configuration.token] = configuration)
                    );
                    resultsUi.renderPublicSessions();
                    resultsUi.renderPinnedSessions();
                    resultsUi.renderRecentSessions();
                  }
                );
              })
            );
          });
          WaveService.readStatus(function(config) {
            resultsUi.state.importResultsEnabled = config.importResultsEnabled;
            resultsUi.state.reportsEnabled = config.reportsEnabled;
            resultsUi.renderManageSessions();
          });
        },
        findSession(fragment, callback) {
          if (!fragment || fragment.length < 8) return;
          WaveService.findToken(
            fragment,
            token => {
              WaveService.readSession(token, session => {
                WaveService.readSessionStatus(token, status => {
                  session.status = status.status;
                  session.dateStarted = status.dateStarted;
                  session.dateFinished = status.dateFinished;
                  callback(session);
                });
              });
            },
            () => callback(null)
          );
        },
        addSession(session) {
          const token = session.token;
          if (resultsUi.state.sessions[token]) return;
          resultsUi.state.sessions[token] = session;
          resultsUi.pinSession(token);
        },
        removeSession(token) {
          delete resultsUi.state.sessions[token];
          WaveService.removeRecentSession(token);
          WaveService.removePinnedSession(token);
          resultsUi.updateSessionState();
        },
        showAddSessionError() {
          const errorBox = UI.getElement("find-error");
          errorBox.setAttribute("style", "display: block");
        },
        hideAddSessionError() {
          const errorBox = UI.getElement("find-error");
          errorBox.setAttribute("style", "display: none");
        },
        pinSession(token) {
          WaveService.addPinnedSession(token);
          WaveService.removeRecentSession(token);
          resultsUi.updateSessionState();
        },
        unpinSession(token) {
          WaveService.removePinnedSession(token);
          WaveService.addRecentSession(token);
          resultsUi.updateSessionState();
        },
        updateSessionState() {
          resultsUi.state.pinnedSessions = WaveService.getPinnedSessions();
          resultsUi.state.recentSessions = WaveService.getRecentSessions();
          resultsUi.renderPinnedSessions();
          resultsUi.renderRecentSessions();
        },
        openSessionResult(token) {
					location.href = `${WEB_ROOT}results.html?token=${token}`;
        },
        sortSessions(tableType, column) {
          if (tableType in sortDetail) {
            if (sortDetail[tableType].sortColumn == column) {
              sortDetail[tableType].ascending = !sortDetail[tableType]
                .ascending;
            } else {
              sortDetail[tableType].sortColumn = column;
              sortDetail[tableType].ascending = true;
            }
            switch (tableType) {
              case "recentSessions":
                resultsUi.renderRecentSessions();
                break;
              case "pinnedSessions":
                resultsUi.renderPinnedSessions();
                break;
              case "publicSessions":
                resultsUi.renderPublicSessions();
                break;
            }
          }
        },
        sortSessionsByColumn(sessions, recentSessions, column, ascending) {
          var resultArray = recentSessions
            .map(token => sessions[token])
            .sort(function(sessionA, sessionB) {
              let columnA = sessionA[column];
              if (column === "browser")
                columnA = sessionA[column].name + sessionA[column].version;
              if (column === "dateStarted" && !columnA) {
                columnA = Date.now();
              }
              let columnB = sessionB[column];
              if (column === "browser")
                columnB = sessionB[column].name + sessionA[column].version;
              if (column === "dateStarted" && !columnB) {
                columnB = Date.now();
              }
              if (columnA < columnB) {
                return -1;
              }
              if (columnA > columnB) {
                return 1;
              }
              return 0;
            });
          if (ascending) {
            resultArray.reverse();
          }
          return resultArray;
        },
        compareSessions(reftokens) {
          if (!resultsUi.isComparisonValid()) return;
          const tokens = resultsUi.state.comparison;
          if (!tokens || tokens.length === 0) return;
          const refQuery = reftokens ? `&reftokens=${reftokens}` : "";
					location.href = `${WEB_ROOT}comparison.html?tokens=${tokens.join(
            ","
          )}${refQuery}`;
        },
        isComparisonValid() {
          const { comparison, sessions } = resultsUi.state;
          if (!comparison) return false;
          if (comparison.length <= 1) return false;
          const comparingSessions = comparison.map(token => sessions[token]);
          const referenceTokens = comparingSessions[0].referenceTokens;
          for (let comparingSession of comparingSessions) {
            const comparingReferenceTokens = comparingSession.referenceTokens;
            if (referenceTokens.length !== comparingReferenceTokens.length)
              return false;
            for (let token of comparingReferenceTokens) {
              if (referenceTokens.indexOf(token) === -1) return false;
            }
          }
          return true;
        },
        isSessionValidForComparison(session) {
          if (!session) return false;
          if (session.status !== "completed" && session.status !== "aborted")
            return false;
          const sessionRefTokens = session.reference_tokens;
          const comparisonSession =
            resultsUi.state.sessions[resultsUi.state.comparison[0]];
          if (!comparisonSession) return true;
          const comparisonRefTokens = comparisonSession.reference_tokens;
          if (!comparisonRefTokens) return true;
          if (sessionRefTokens.length !== comparisonRefTokens.length)
            return false;
          if (
            sessionRefTokens.some(
              token => comparisonRefTokens.indexOf(token) === -1
            )
          )
            return false;
          return true;
        },
        isSessionSelectedForComparison(session) {
          return resultsUi.state.comparison.indexOf(session.token) !== -1;
        },
        isSessionDisabled(session) {
          return (
            resultsUi.state.comparison.length > 0 &&
            !resultsUi.isSessionValidForComparison(session)
          );
        },
        addSessionToComparison(token) {
          if (resultsUi.state.comparison.indexOf(token) !== -1) return;
          resultsUi.state.comparison.push(token);
          resultsUi.updateCompareButton();
          resultsUi.renderSessions();
        },
        removeSessionFromComparison(token) {
          const index = resultsUi.state.comparison.indexOf(token);
          if (index === -1) return;
          resultsUi.state.comparison.splice(index, 1);
          resultsUi.updateCompareButton();
          resultsUi.renderSessions();
        },
        handleAddSession() {
          const tokenFragmentInput = UI.getElement("token-fragment");
          const fragment = tokenFragmentInput.value;
          resultsUi.findSession(fragment, session => {
            if (!session) {
              resultsUi.showAddSessionError();
              return;
            }
            tokenFragmentInput.value = "";
            resultsUi.hideAddSessionError();
            resultsUi.addSession(session);
          });
        },
        handleImportSession() {
          resultsUi.state.importError = null;
          resultsUi.state.importInProgress = true;
          resultsUi.renderManageSessions();
          const { importSessionFile: file } = resultsUi.state;
          const reader = new FileReader();
          reader.readAsArrayBuffer(file);
          reader.onload = () => {
            const data = reader.result;
            WaveService.importResults(
              data,
              function(token) {
                location.href = WEB_ROOT + "results.html?token=" + token;
              },
              function(error) {
                resultsUi.state.importError = error;
                resultsUi.state.importInProgress = false;
                resultsUi.renderManageSessions();
              }
            );
          };
        },
        handleImportSessionSelection() {
          const file = UI.getElement("import-session-file").files[0];
          resultsUi.state.importSessionFile = file;
          resultsUi.renderManageSessions();
        },
        addFilterLabel() {
          const label = UI.getElement("filter-label-input").value;
          if (!label) return;
          const { filterLabels } = resultsUi.state;
          if (filterLabels.indexOf(label) !== -1) return;
          filterLabels.push(label);
          resultsUi.renderSessions();
          UI.getElement("filter-label-input").focus();
        },
        removeFilterLabel(index) {
          resultsUi.state.filterLabels.splice(index, 1);
          resultsUi.renderSessions();
        },
        showAddFilterLabel() {
          resultsUi.state.addFilterLabelVisible = true;
          resultsUi.renderSessions();
          UI.getElement("filter-label-input").focus();
        },
        hideAddFilterLabel() {
          resultsUi.state.addFilterLabelVisible = false;
          resultsUi.renderSessions();
        },
        render() {
          const { getRoot, createElement, getElement } = UI;
          const resultsView = UI.createElement({
            className: "section",
            children: [
              {
                className: "container",
                style: "margin-bottom: 2em",
                children: [
                  {
                    element: "img",
                    src: "res/wavelogo_2016.jpg",
                    className: "site-logo"
                  },
                  { text: "Results Overview", className: "title" }
                ]
              },
              {
                id: "manage-sessions",
                className: "container",
                style: "margin-bottom: 2em"
              },
              { id: "sessions", className: "container" }
            ]
          });
          const root = UI.getRoot();
          root.innerHTML = "";
          root.appendChild(resultsView);
          resultsUi.renderManageSessions();
          resultsUi.renderSessions();
        },
        renderManageSessions() {
          const manageSessionsView = UI.getElement("manage-sessions");
          manageSessionsView.innerHTML = "";
          const heading = { text: "Manage Sessions", className: "title is-4" };
          const addCompareSessions = {
            className: "columns",
            children: [
              {
                className: "column",
                children: [
                  { text: "Add Sessions", className: "title is-5" },
                  {
                    element: "article",
                    className: "message is-danger",
                    id: "find-error",
                    children: [
                      {
                        text:
                          "Could not find any sessions! Try adding more characters of the token.",
                        className: "message-body"
                      }
                    ],
                    style: "display: none"
                  },
                  {
                    className: "field",
                    children: [
                      {
                        className: "label has-text-weight-normal",
                        text: "Session token:"
                      },
                      {
                        className: "field-body",
                        children: {
                          className: "field",
                          children: {
                            className: "control",
                            children: {
                              style: "display: flex; margin-bottom: 10px;",
                              children: [
                                {
                                  element: "input",
                                  inputType: "text",
                                  className: "input is-family-monospace",
                                  id: "token-fragment",
                                  placeholder:
                                    "First 8 characters or more of session token",
                                  onKeyDown: event =>
                                    event.key === "Enter"
                                      ? resultsUi.handleAddSession()
                                      : null
                                }
                              ]
                            }
                          }
                        }
                      },
                      {
                        className: "field is-grouped is-grouped-right",
                        children: {
                          className: "control",
                          children: {
                            className: "button is-dark is-outlined",
                            children: [
                              {
                                element: "span",
                                className: "icon",
                                children: [
                                  {
                                    element: "i",
                                    className: "fas fa-plus"
                                  }
                                ]
                              },
                              { text: "Add Session", element: "span" }
                            ],
                            onclick: resultsUi.handleAddSession
                          }
                        }
                      }
                    ]
                  }
                ]
              },
              {
                className: "column",
                children: [
                  { text: "Compare Sessions", className: "title is-5" },
                  {
                    element: "label",
                    text:
                      "Compare sessions by selecting them in the list below. " +
                      "Only sessions with the same set of reference sessions can be compared. " +
                      "Sessions have to be finished."
                  },
                  {
                    style: "text-align: right",
                    children: [
                      {
                        className: "button is-dark is-outlined",
                        disabled: true,
                        id: "compare-button",
                        children: [
                          {
                            element: "span",
                            className: "icon",
                            children: [
                              {
                                element: "i",
                                className: "fas fa-balance-scale"
                              }
                            ]
                          },
                          { text: "Compare Selected", element: "span" }
                        ],
                        onClick: () => resultsUi.compareSessions()
                      }
                    ]
                  }
                ]
              }
            ]
          };
          const {
            importSessionFile,
            importError,
            importInProgress
          } = resultsUi.state;
          const importSessions = {
            className: "columns",
            style: "margin-bottom: 2em",
            children: [
              {
                className: "column is-half",
                children: [
                  { text: "Import Sessions", className: "title is-5" },
                  {
                    element: "article",
                    className: "message is-danger",
                    children: [
                      {
                        className: "message-body",
                        text: "Could not import session: " + importError
                      }
                    ],
                    style: importError ? "" : "display: none"
                  },
                  {
                    className: "field file has-name",
                    children: [
                      {
                        element: "label",
                        className: "file-label",
                        style: "width: 100%",
                        children: [
                          {
                            element: "input",
                            className: "file-input",
                            type: "file",
                            accept: ".zip",
                            id: "import-session-file",
                            onChange: resultsUi.handleImportSessionSelection
                          },
                          {
                            element: "span",
                            className: "file-cta",
                            children: [
                              {
                                element: "span",
                                className: "file-icon",
                                children: [
                                  {
                                    element: "i",
                                    className: "fas fa-upload"
                                  }
                                ]
                              },
                              {
                                element: "span",
                                className: "file-label",
                                text: "Choose ZIP file"
                              }
                            ]
                          },
                          {
                            element: "span",
                            className: "file-name",
                            style: "width: 100%; max-width: unset",
                            text: importSessionFile
                              ? importSessionFile.name
                              : ""
                          }
                        ]
                      }
                    ]
                  },
                  {
                    className: "field is-grouped is-grouped-right",
                    children: {
                      className: "control",
                      children: {
                        className: "button is-dark is-outlined",
                        disabled: !importSessionFile,
                        children: [
                          {
                            element: "span",
                            className: "icon",
                            children: [
                              {
                                element: "i",
                                className: importInProgress
                                  ? "fas fa-spinner fa-pulse"
                                  : "fas fa-plus"
                              }
                            ]
                          },
                          { text: "Import Session", element: "span" }
                        ],
                        onclick: resultsUi.handleImportSession
                      }
                    }
                  }
                ]
              },
              {
                className: "column",
                children: []
              }
            ]
          };
          const { importResultsEnabled } = resultsUi.state;
          manageSessionsView.appendChild(UI.createElement(heading));
          manageSessionsView.appendChild(UI.createElement(addCompareSessions));
          if (!importResultsEnabled) return;
          manageSessionsView.appendChild(UI.createElement(importSessions));
        },
        renderSessions() {
          const sessionsView = UI.getElement("sessions");
          sessionsView.innerHTML = "";
          sessionsView.appendChild(
            UI.createElement({ text: "Sessions", className: "title is-4" })
          );
          const sessionFilters = resultsUi.createSessionFilters();
          sessionsView.appendChild(sessionFilters);
          sessionsView.appendChild(UI.createElement({ id: "public-sessions" }));
          sessionsView.appendChild(UI.createElement({ id: "pinned-sessions" }));
          sessionsView.appendChild(UI.createElement({ id: "recent-sessions" }));
          sessionsView.appendChild(UI.createElement({ id: "session-status" }));
          resultsUi.renderPublicSessions();
          resultsUi.renderPinnedSessions();
          resultsUi.renderRecentSessions();
        },
        renderPublicSessions() {
          resultsUi.renderSessionStatus();
          const { sessions, publicSessions, filterLabels } = resultsUi.state;
          UI.saveScrollPosition("public-sessions-overflow");
          const publicSessionsView = UI.getElement("public-sessions");
          publicSessionsView.innerHTML = "";
          if (!publicSessions || publicSessions.length === 0) return;
          const sortedPublicSessions = resultsUi.sortSessionsByColumn(
            sessions,
            publicSessions,
            sortDetail["publicSessions"].sortColumn,
            sortDetail["publicSessions"].ascending
          );
          const filteredPublicSessions = sortedPublicSessions.filter(
            session =>
              filterLabels.length === 0 ||
              filterLabels.reduce(
                (match, label) =>
                  match &&
                  session.labels
                    .map(label => label.toLowerCase())
                    .indexOf(label.toLowerCase()) !== -1,
                true
              )
          );
          if (filteredPublicSessions.length === 0) return;
          publicSessionsView.appendChild(
            UI.createElement({
              text: "Reference Browsers",
              className: "title is-5"
            })
          );
          const sessionsTable = UI.createElement({
            style: "overflow-x: auto",
            id: "public-sessions-overflow",
            children: resultsUi.createSessionsTable(
              "publicSessions",
              filteredPublicSessions,
              { static: true }
            )
          });
          publicSessionsView.appendChild(sessionsTable);
          publicSessionsView.appendChild(
            UI.createElement({ style: "content: ''; margin-bottom: 40px" })
          );
          UI.loadScrollPosition("public-sessions-overflow")
        },
        renderPinnedSessions() {
          resultsUi.renderSessionStatus();
          const { sessions, pinnedSessions, filterLabels } = resultsUi.state;
          UI.saveScrollPosition("pinned-sessions-overflow");
          const pinnedSessionsView = UI.getElement("pinned-sessions");
          pinnedSessionsView.innerHTML = "";
          if (!pinnedSessions || pinnedSessions.length === 0) return;
          const sortedPinnedSessions = resultsUi.sortSessionsByColumn(
            sessions,
            pinnedSessions,
            sortDetail["pinnedSessions"].sortColumn,
            sortDetail["pinnedSessions"].ascending
          );
          const filteredPinnedSessions = sortedPinnedSessions.filter(
            session =>
              filterLabels.length === 0 ||
              filterLabels.reduce(
                (match, label) =>
                  match &&
                  session.labels
                    .map(label => label.toLowerCase())
                    .indexOf(label.toLowerCase()) !== -1,
                true
              )
          );
          if (filteredPinnedSessions.length === 0) return;
          pinnedSessionsView.appendChild(
            UI.createElement({ text: "Pinned", className: "title is-5" })
          );
          const sessionsTable = UI.createElement({
            style: "overflow-x: auto",
            id: "pinned-sessions-overflow",
            children: resultsUi.createSessionsTable(
              "pinnedSessions",
              filteredPinnedSessions,
              { pinned: true }
            )
          });
          pinnedSessionsView.appendChild(sessionsTable);
          pinnedSessionsView.appendChild(
            UI.createElement({ style: "content: ''; margin-bottom: 40px" })
          );
          UI.loadScrollPosition("pinned-sessions-overflow");
        },
        renderRecentSessions() {
          resultsUi.renderSessionStatus();
          const {
            sessions,
            recentSessions,
            pinnedSessions,
            filterLabels
          } = resultsUi.state;
          UI.saveScrollPosition("recent-sessions-overflow");
          const recentSessionsView = UI.getElement("recent-sessions");
          recentSessionsView.innerHTML = "";
          if (!recentSessions || recentSessions.length === 0) return;
          const sortedRecentSessions = resultsUi.sortSessionsByColumn(
            sessions,
            recentSessions,
            sortDetail["recentSessions"].sortColumn,
            sortDetail["recentSessions"].ascending
          );
          const filteredRecentSessions = sortedRecentSessions.filter(
            session =>
              filterLabels.length === 0 ||
              filterLabels.reduce(
                (match, label) =>
                  match &&
                  session.labels
                    .map(label => label.toLowerCase())
                    .indexOf(label.toLowerCase()) !== -1,
                true
              )
          );
          if (filteredRecentSessions.length === 0) return;
          recentSessionsView.appendChild(
            UI.createElement({ text: "Recent", className: "title is-5" })
          );
          const sessionsTable = UI.createElement({
            style: "overflow-x: auto",
            id: "recent-sessions-overflow",
            children: resultsUi.createSessionsTable(
              "recentSessions",
              filteredRecentSessions,
              { pinned: false }
            )
          });
          recentSessionsView.appendChild(sessionsTable);
          recentSessionsView.appendChild(
            UI.createElement({ style: "content: ''; margin-bottom: 40px" })
          );
          UI.loadScrollPosition("recent-sessions-overflow");
        },
        renderSessionStatus() {
          const {
            recentSessions,
            pinnedSessions,
            publicSessions
          } = resultsUi.state;
          const sessionStatusView = UI.getElement("session-status");
          sessionStatusView.innerHTML = "";
          if (!recentSessions && !pinnedSessions && !publicSessions) {
            sessionStatusView.appendChild(
              UI.createElement({
                className: "level",
                children: {
                  element: "span",
                  className: "level-item",
                  children: [
                    {
                      element: "i",
                      className: "fas fa-spinner fa-pulse"
                    },
                    {
                      style: "margin-left: 0.4em;",
                      text: "Loading sessions ..."
                    }
                  ]
                }
              })
            );
            return;
          } else if (
            (!recentSessions || recentSessions.length === 0) &&
            (!pinnedSessions || pinnedSessions.length === 0) &&
            (!publicSessions || publicSessions.length === 0)
          ) {
            sessionStatusView.appendChild(
              UI.createElement({
                className: "level",
                children: {
                  element: "span",
                  className: "level-item",
                  text: "No sessions available."
                }
              })
            );
            return;
          }
        },
        createSessionFilters() {
          const { filterLabels, addFilterLabelVisible } = resultsUi.state;
          const filters = UI.createElement({
            className: "field is-horizontal",
            style: "margin-bottom: 2em",
            children: [
              {
                className: "field-label",
                style: "flex: unset",
                children: {
                  className: "label has-text-weight-normal",
                  text: "Filter by labels:"
                }
              },
              {
                className: "field-body",
                children: {
                  className: "control",
                  children: {
                    className: "field is-grouped is-grouped-multiline",
                    children: filterLabels
                      .map((label, index) => ({
                        className: "control",
                        children: {
                          className: "tags has-addons",
                          children: [
                            {
                              element: "span",
                              className: "tag is-info",
                              text: label
                            },
                            {
                              element: "a",
                              className: "tag is-delete",
                              onClick: () => resultsUi.removeFilterLabel(index)
                            }
                          ]
                        }
                      }))
                      .concat(
                        addFilterLabelVisible
                          ? [
                              {
                                className: "control field is-grouped",
                                children: [
                                  {
                                    element: "input",
                                    className: "input is-small control",
                                    style: "width: 10rem",
                                    id: "filter-label-input",
                                    type: "text",
                                    onKeyUp: event =>
                                      event.keyCode === 13
                                        ? resultsUi.addFilterLabel()
                                        : null
                                  },
                                  {
                                    className:
                                      "button is-dark is-outlined is-small is-rounded control",
                                    text: "save",
                                    onClick: resultsUi.addFilterLabel
                                  },
                                  {
                                    className:
                                      "button is-dark is-outlined is-small is-rounded control",
                                    text: "cancel",
                                    onClick: resultsUi.hideAddFilterLabel
                                  }
                                ]
                              }
                            ]
                          : [
                              {
                                className: "button is-rounded is-small",
                                text: "Add",
                                onClick: resultsUi.showAddFilterLabel
                              }
                            ]
                      )
                  }
                }
              }
            ]
          });
          return filters;
        },
        createSessionsTable(
          tableType,
          sessions,
          { pinned = false, static = false } = {}
        ) {
          const getTagStyle = status => {
            switch (status) {
              case "completed":
                return "is-success";
              case "running":
                return "is-info";
              case "aborted":
                return "is-danger";
              case "paused":
                return "is-warning";
              case "pending":
                return "is-primary";
            }
          };
          var sortIcon = null;
          if (tableType in sortDetail) {
            sortIcon = sortDetail[tableType].ascending
              ? "fas fa-sort-down"
              : "fas fa-sort-up";
          }
          return UI.createElement({
            element: "table",
            className: "table is-bordered is-hoverable is-fullwidth",
            children: [
              {
                element: "thead",
                children: {
                  element: "tr",
                  children: [
                    {
                      element: "td",
                      style: "text-decoration: underline dotted;",
                      text: "Cp",
                      className: "is-narrow",
                      title: "Select for comparison"
                    },
                    {
                      element: "td",
                      text: "Token",
                      className: "is-narrow",
                      onclick: () => resultsUi.sortSessions(tableType, "token"),
                      style: "cursor: pointer;",
                      children: [
                        {
                          element: "i",
                          className: sortIcon,
                          style:
                            "padding-left: 20px; visibility:" +
                            (sortIcon &&
                            sortDetail[tableType].sortColumn == "token"
                              ? "visible;"
                              : "hidden;")
                        }
                      ]
                    },
                    {
                      element: "td",
                      text: "Browser",
                      onclick: () =>
                        resultsUi.sortSessions(tableType, "browser"),
                      style: "cursor: pointer;",
                      className: "is-narrow",
                      children: [
                        {
                          element: "i",
                          className: sortIcon,
                          style:
                            "padding-left: 20px; visibility:" +
                            (sortIcon &&
                            sortDetail[tableType].sortColumn == "browser"
                              ? "visible;"
                              : "hidden;")
                        }
                      ]
                    },
                    {
                      element: "td",
                      text: "Status",
                      onclick: () =>
                        resultsUi.sortSessions(tableType, "status"),
                      style: "cursor: pointer",
                      className: "is-narrow",
                      children: [
                        {
                          element: "i",
                          className: sortIcon,
                          style:
                            "padding-left: 20px; visibility:" +
                            (sortIcon &&
                            sortDetail[tableType].sortColumn == "status"
                              ? "visible;"
                              : "hidden;")
                        }
                      ]
                    },
                    {
                      element: "td",
                      text: "Date Started",
                      onclick: () =>
                        resultsUi.sortSessions(tableType, "dateStarted"),
                      style: "cursor: pointer;",
                      className: "is-narrow",
                      children: [
                        {
                          element: "i",
                          className: sortIcon,
                          style:
                            "padding-left: 20px; visibility:" +
                            (sortIcon &&
                            sortDetail[tableType].sortColumn == "dateStarted"
                              ? "visible;"
                              : "hidden;")
                        }
                      ]
                    },
                    {
                      element: "td",
                      text: "Labels",
                      style: "cursor: pointer; width: 18rem"
                    },
                    static
                      ? null
                      : {
                          element: "td",
                          text: "RefS",
                          title: "Reference Sessions",
                          style: "text-decoration: underline dotted;",
                          className: "is-narrow"
                        },
                    static
                      ? null
                      : {
                          element: "td",
                          colspan: 2,
                          text: "Options",
                          className: "is-narrow"
                        }
                  ]
                }
              },
              {
                element: "tbody",
                children: sessions.map(session => ({
                  element: "tr",
                  className: resultsUi.isSessionDisabled(session)
                    ? "disabled-row"
                    : "",
                  style: "cursor: pointer",
                  onclick: () => resultsUi.openSessionResult(session.token),
                  children: [
                    {
                      element: "td",
                      onclick: event => event.stopPropagation(),
                      style: "vertical-align: middle;",
                      children: [
                        {
                          element: "input",
                          className: "checkbox",
                          style:
                            "width: 18px; height: 18px; margin-top: 0.55em",
                          type: "checkbox",
                          disabled: !resultsUi.isSessionValidForComparison(
                            session
                          ),
                          checked: resultsUi.isSessionSelectedForComparison(
                            session
                          ),
                          onchange: event =>
                            event.target.checked
                              ? resultsUi.addSessionToComparison(session.token)
                              : resultsUi.removeSessionFromComparison(
                                  session.token
                                )
                        }
                      ]
                    },
                    {
                      element: "td",
                      className: "is-family-monospace",
                      style: "vertical-align: middle;",
                      text: session.token.split("-").shift()
                    },
                    {
                      element: "td",
                      style: "vertical-align: middle; white-space: nowrap",
                      text: session.browser.name + " " + session.browser.version
                    },
                    {
                      element: "td",
                      style: "vertical-align: middle; text-align: center",
                      children: [
                        {
                          className: `tag ${getTagStyle(session.status)}`,
                          text: session.status
                        }
                      ]
                    },
                    {
                      element: "td",
                      style: "vertical-align: middle; white-space: nowrap",
                      text: session.dateStarted
                        ? new Date(session.dateStarted).toLocaleString()
                        : "not started"
                    },
                    {
                      element: "td",
                      children: {
                        className: "tags field is-grouped isgrouped-multiline",
                        style: "min-width: 10em",
                        children: session.labels.map(label => ({
                          className: "control",
                          children: {
                            element: "span",
                            className: "tag is-info",
                            text: label
                          }
                        }))
                      }
                    },
                    static
                      ? null
                      : {
                          element: "td",
                          title: session.referenceTokens
                            .map(token => token.split("-").shift())
                            .sort((tokenA, tokenB) => tokenA - tokenB)
                            .join("\n"),
                          style: "white-space:nowrap",
                          children: (() => {
                            const tokens = session.referenceTokens.slice();
                            let overflow = 0;
                            if (tokens.length > 3) {
                              overflow = tokens.length - 2;
                            }
                            if (overflow > 0) tokens.splice(2, overflow + 2);
                            const children = tokens.map(token => {
                              let icon = "";
                              const session = resultsUi.state.sessions[token];
                              switch (session.browser.name.toLowerCase()) {
                                case "firefox":
                                  icon = "fab fa-firefox";
                                  break;
                                case "edge":
                                  icon = "fab fa-edge";
                                  break;
                                case "chrome":
                                case "chromium":
                                  icon = "fab fa-chrome";
                                  break;
                                case "safari":
                                case "webkit":
                                  icon = "fab fa-safari";
                                  break;
                              }
                              return {
                                element: "span",
                                style:
                                  "margin-right: 5px; vertical-align: middle;",
                                children: { element: "i", className: icon }
                              };
                            });
                            if (overflow > 0)
                              children.push({
                                element: "span",
                                style: "vertical-align: middle",
                                className: "is-size-7",
                                text: `+${overflow}`
                              });
                            return children;
                          })()
                        },
                    static
                      ? null
                      : {
                          element: "td",
                          style: "vertical-align: middle; text-align: center",
                          className: "is-paddingless",
                          children: [
                            {
                              className: "button is-dark is-outlined is-small",
                              title: pinned ? "Unpin session" : "Pin session",
                              style: "margin: 5px",
                              children: [
                                {
                                  element: "span",
                                  className: "icon",
                                  children: [
                                    {
                                      element: "i",
                                      className: "fas fa-thumbtack",
                                      style: pinned
                                        ? ""
                                        : "transform: rotate(45deg)"
                                    }
                                  ]
                                }
                              ],
                              onclick: event => {
                                event.stopPropagation();
                                if (pinned) {
                                  resultsUi.unpinSession(session.token);
                                } else {
                                  resultsUi.pinSession(session.token);
                                }
                              }
                            }
                          ]
                        },
                    static
                      ? null
                      : {
                          element: "td",
                          style: "vertical-align: middle; text-align: center",
                          className: "is-paddingless",
                          children: [
                            {
                              className: "button is-dark is-outlined is-small",
                              title: "Remove session from list",
                              style: "margin: 5px",
                              children: [
                                {
                                  element: "span",
                                  className: "icon",
                                  children: [
                                    {
                                      element: "i",
                                      className: "fas fa-trash-alt"
                                    }
                                  ]
                                }
                              ],
                              onclick: event => {
                                event.stopPropagation();
                                resultsUi.removeSession(session.token);
                              }
                            }
                          ]
                        }
                  ]
                }))
              }
            ]
          });
        },
        updateCompareButton: () => {
          const compareButton = UI.getElement("compare-button");
          if (resultsUi.isComparisonValid()) {
            compareButton.removeAttribute("disabled");
          } else {
            compareButton.setAttribute("disabled", true);
          }
        }
      };
    </script>
  </body>
</html>