diff options
author | jasplin <qt-info@nokia.com> | 2010-04-09 12:21:42 +0200 |
---|---|---|
committer | jasplin <qt-info@nokia.com> | 2010-04-09 12:21:42 +0200 |
commit | a62277ff1c05b222a42d0dd670443f92640df655 (patch) | |
tree | f5faca2ccdb3286cfde2f41ac167e5349c745bca | |
parent | e4adf9f130eeca2f43350ddc006f3659827dd72d (diff) |
Added save/load feature for index configs.
It is now possible to save, load, and delete
a performance index configuration (i.e. all the
settings in the 'Index' section in the BM web app).
-rw-r--r-- | doc/er-diagram.dia | bin | 4088 -> 5901 bytes | |||
-rw-r--r-- | src/bm/bmrequest.cpp | 596 | ||||
-rw-r--r-- | src/bm/bmrequest.h | 91 | ||||
-rw-r--r-- | src/bm/plotter.cpp | 1 | ||||
-rw-r--r-- | src/bmclient/main.cpp | 180 | ||||
-rw-r--r-- | src/bmserver/main.cpp | 75 | ||||
-rw-r--r-- | src/bmweb/global.js | 16 | ||||
-rw-r--r-- | src/bmweb/indexsection.js | 421 |
8 files changed, 1309 insertions, 71 deletions
diff --git a/doc/er-diagram.dia b/doc/er-diagram.dia Binary files differindex e187403..6db08a4 100644 --- a/doc/er-diagram.dia +++ b/doc/er-diagram.dia diff --git a/src/bm/bmrequest.cpp b/src/bm/bmrequest.cpp index dbbdf4d..fffcb34 100644 --- a/src/bm/bmrequest.cpp +++ b/src/bm/bmrequest.cpp @@ -92,6 +92,14 @@ BMRequest * BMRequest::create(const QByteArray &data, const QString &msgType) request = new BMRequest_GetResult(doc); else if (type == "IndexGetValues") request = new BMRequest_IndexGetValues(doc); + else if (type == "IndexGetConfigs") + request = new BMRequest_IndexGetConfigs(doc); + else if (type == "IndexGetConfig") + request = new BMRequest_IndexGetConfig(doc); + else if (type == "IndexPutConfig") + request = new BMRequest_IndexPutConfig(doc); + else if (type == "IndexDeleteConfig") + request = new BMRequest_IndexDeleteConfig(doc); #ifdef BMDEBUG else qDebug() << "invalid request type:" << type; @@ -278,7 +286,7 @@ bool BMRequest::getId( // ... and get its id ... if (!BMMisc::getLastInsertId(query, id)) { - Q_ASSERT(false); // ### Backup code (involving an separate query) to go here! + Q_ASSERT(false); // ### Backup code (involving a separate query) to go here! deleteQuery(query); *reply = errorReply(*query, name(), "failed to get last inserted ID (in getId())"); return false; @@ -433,7 +441,7 @@ bool BMRequest::getOrInsertContextId( // ... and get its ID ... if (!BMMisc::getLastInsertId(query, id)) { - Q_ASSERT(false); // ### Backup code (involving an separate query) to go here! + Q_ASSERT(false); // ### Backup code (involving a separate query) to go here! deleteQuery(query); *reply = errorReply(*query, name(), "failed to get ID of last context inserted"); return false; @@ -547,7 +555,7 @@ bool BMRequest::getOrInsertBMContextId( // ... and get its ID ... if (!BMMisc::getLastInsertId(query, id)) { - Q_ASSERT(false); // ### Backup code (involving an separate query) to go here! + Q_ASSERT(false); // ### Backup code (involving a separate query) to go here! deleteQuery(query); *reply = errorReply(*query, name(), "failed to get ID of last bmcontext inserted"); return false; @@ -596,8 +604,6 @@ bool BMRequest::insertOrReplaceResult( return false; } - deleteQuery(query); - } else { // ... it doesn't, so insert the value ... @@ -613,12 +619,10 @@ bool BMRequest::insertOrReplaceResult( deleteQuery(query, Rollback); return false; } - - database->commit(); - - deleteQuery(query); } + deleteQuery(query, Commit); + return true; } @@ -5785,3 +5789,577 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const BMMisc::printHTMLErrorPage(QString("failed to compute index values: %1").arg(error)); } } + + +// --- IndexGetConfigs --- +QByteArray BMRequest_IndexGetConfigs::toRequestBuffer(QString *) +{ + const QString request = QString("<request type=\"%1\" />").arg(name()); + return xmlConvert(request); +} + +QByteArray BMRequest_IndexGetConfigs::toReplyBuffer() +{ + QString reply = QString("<reply type=\"%1\">").arg(name()); + QSqlQuery *query; + + query = createQuery(); + if (!query->exec("SELECT name FROM indexConfig ORDER BY name ASC;")) { + reply = errorReply(*query, name(), QString("(exec() failed)")); + deleteQuery(query); + return xmlConvert(reply); + } + while (query->next()) + reply += QString("<indexConfig name=\"%1\" />").arg(query->value(0).toString()); + deleteQuery(query); + + reply += "</reply>"; + + return xmlConvert(reply); +} + +void BMRequest_IndexGetConfigs::handleReply_Raw(const QStringList &args) const +{ + printf("NOTE: raw output not supported yet; printing JSON output for now:\n\n"); + handleReply_JSON(args); +} + +void BMRequest_IndexGetConfigs::handleReply_JSON(const QStringList &args) const +{ + Q_UNUSED(args); + + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + QString reply = QString("{"); + + if (error.isEmpty()) { + + reply += "\n\"names\": ["; + + QDomNodeList indexConfigNodes = doc.elementsByTagName("indexConfig"); + for (int i = 0; i < indexConfigNodes.size(); ++i) { + QDomElement indexConfigElem = indexConfigNodes.at(i).toElement(); + reply += QString("%1\n\"%2\"") + .arg((i == 0) ? "" : ", ") + .arg(indexConfigElem.attributeNode("name").value()); + } + + reply += "\n]\n}"; + + } else { + reply = QString("{\"error\": \"error getting index configurations: %1\"}").arg(error); + } + + BMMisc::printJSONOutput(reply); +} + + +// --- IndexGetConfig --- +QByteArray BMRequest_IndexGetConfig::toRequestBuffer(QString *) +{ + const QString request = + QString("<request type=\"%1\"><args configName=\"%2\" /></request>") + .arg(name()).arg(configName); + return xmlConvert(request); +} + +QByteArray BMRequest_IndexGetConfig::toReplyBuffer() +{ + // Get config name ... + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + configName = argsElem.attributeNode("configName").value(); + + QString reply = QString("<reply type=\"%1\">").arg(name()); + QSqlQuery *query; + + // Get config ID and non-filter attributes ... + query = createQuery(); + if (!query->exec( + QString( + "SELECT id, baseTimestamp, loEvalTimestamp, hiEvalTimestamp, evalTimestep, " + "medianWinSize FROM indexConfig WHERE name='%1';").arg(configName))) { + reply = errorReply(*query, name(), QString("failed to get config (exec() failed (1))")); + deleteQuery(query); + return xmlConvert(reply); + } + int configId; + int baseTimestamp; + int evalLoTime; + int evalHiTime; + int evalStep; + int medianWinSize; + if (query->next()) { + configId = query->value(0).toInt(); + baseTimestamp = query->value(1).toInt(); + evalLoTime = query->value(2).toInt(); + evalHiTime = query->value(3).toInt(); + evalStep = query->value(4).toInt(); + medianWinSize = query->value(5).toInt(); + } else { + reply = errorReply(*query, name(), QString("index config '%1' not found").arg(configName)); + deleteQuery(query); + return xmlConvert(reply); + } + deleteQuery(query); + + + // Get filter attributes (### note the refactoring potential here) ... + + // ... test case ... + query = createQuery(); + if (!query->exec( + QString("SELECT name FROM icTestCase WHERE indexConfigId=%1 ORDER BY name ASC;") + .arg(configId))) { + reply = errorReply(*query, name(), QString("failed to get config (exec() failed (2))")); + deleteQuery(query); + return xmlConvert(reply); + } + QStringList testCaseFilter; + while (query->next()) + testCaseFilter.append(query->value(0).toString()); + deleteQuery(query); + + // ... metric ... + query = createQuery(); + if (!query->exec( + QString("SELECT name FROM icMetric WHERE indexConfigId=%1 ORDER BY name ASC;") + .arg(configId))) { + reply = errorReply(*query, name(), QString("failed to get config (exec() failed (2))")); + deleteQuery(query); + return xmlConvert(reply); + } + QStringList metricFilter; + while (query->next()) + metricFilter.append(query->value(0).toString()); + deleteQuery(query); + + // ... platform ... + query = createQuery(); + if (!query->exec( + QString("SELECT name FROM icPlatform WHERE indexConfigId=%1 ORDER BY name ASC;") + .arg(configId))) { + reply = errorReply(*query, name(), QString("failed to get config (exec() failed (2))")); + deleteQuery(query); + return xmlConvert(reply); + } + QStringList platformFilter; + while (query->next()) + platformFilter.append(query->value(0).toString()); + deleteQuery(query); + + // ... host ... + query = createQuery(); + if (!query->exec( + QString("SELECT name FROM icHost WHERE indexConfigId=%1 ORDER BY name ASC;") + .arg(configId))) { + reply = errorReply(*query, name(), QString("failed to get config (exec() failed (2))")); + deleteQuery(query); + return xmlConvert(reply); + } + QStringList hostFilter; + while (query->next()) + hostFilter.append(query->value(0).toString()); + deleteQuery(query); + + // ... branch ... + query = createQuery(); + if (!query->exec( + QString("SELECT name FROM icBranch WHERE indexConfigId=%1 ORDER BY name ASC;") + .arg(configId))) { + reply = errorReply(*query, name(), "failed to get config (exec() failed (2))"); + deleteQuery(query); + return xmlConvert(reply); + } + QStringList branchFilter; + while (query->next()) + branchFilter.append(query->value(0).toString()); + deleteQuery(query); + + + //-------------------------------------- + + reply += QString( + "<args baseTimestamp=\"%1\" evalLoTime=\"%2\" evalHiTime=\"%3\" evalStep=\"%4\" " + "medianWinSize=\"%5\" />") + .arg(baseTimestamp) + .arg(evalLoTime) + .arg(evalHiTime) + .arg(evalStep) + .arg(medianWinSize); + + for (int i = 0; i < testCaseFilter.size(); ++i) + reply += QString("<testCase name=\"%1\" />").arg(testCaseFilter.at(i)); + for (int i = 0; i < metricFilter.size(); ++i) + reply += QString("<metric name=\"%1\" />").arg(metricFilter.at(i)); + for (int i = 0; i < platformFilter.size(); ++i) + reply += QString("<platform name=\"%1\" />").arg(platformFilter.at(i)); + for (int i = 0; i < hostFilter.size(); ++i) + reply += QString("<host name=\"%1\" />").arg(hostFilter.at(i)); + for (int i = 0; i < branchFilter.size(); ++i) + reply += QString("<branch name=\"%1\" />").arg(branchFilter.at(i)); + + reply += "</reply>"; + + return xmlConvert(reply); +} + +void BMRequest_IndexGetConfig::handleReply_Raw(const QStringList &args) const +{ + printf("NOTE: raw output not supported yet; printing JSON output for now:\n\n"); + handleReply_JSON(args); +} + +void BMRequest_IndexGetConfig::handleReply_JSON(const QStringList &args) const +{ + Q_UNUSED(args); + + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + QString reply = QString("{"); + + if (error.isEmpty()) { + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + bool ok; + + // Non-filter values ... + + reply += QString("\n\"baseTimestamp\": %1") + .arg(argsElem.attributeNode("baseTimestamp").value().toInt(&ok)); + Q_ASSERT(ok); + + reply += QString(",\n\"evalLoTime\": %1") + .arg(argsElem.attributeNode("evalLoTime").value().toInt(&ok)); + Q_ASSERT(ok); + + reply += QString(",\n\"evalHiTime\": %1") + .arg(argsElem.attributeNode("evalHiTime").value().toInt(&ok)); + Q_ASSERT(ok); + + reply += QString(",\n\"evalStep\": %1") + .arg(argsElem.attributeNode("evalStep").value().toInt(&ok)); + Q_ASSERT(ok); + + reply += QString(",\n\"medianWinSize\": %1") + .arg(argsElem.attributeNode("medianWinSize").value().toInt(&ok)); + Q_ASSERT(ok); + + // Filter values ... + + const QStringList filterNames = + QStringList() << "testCase" << "metric" << "platform" << "host" << "branch"; + for (int i = 0; i < filterNames.size(); ++i) { + reply += QString(",\n\"%1Filter\": [").arg(filterNames.at(i)); + QDomNodeList filterNodes = doc.elementsByTagName(filterNames.at(i)); + for (int j = 0; j < filterNodes.size(); ++j) { + QDomElement filterElem = filterNodes.at(j).toElement(); + reply += QString("%1\"%2\"") + .arg((j == 0) ? "" : ", ") + .arg(filterElem.attributeNode("name").value()); + } + reply += "]"; + } + + reply += "\n}"; + + } else { + reply = QString("{\"error\": \"error getting index configuration: %1\"}").arg(error); + } + + BMMisc::printJSONOutput(reply); +} + + +// --- IndexPutConfig --- +QByteArray BMRequest_IndexPutConfig::toRequestBuffer(QString *) +{ + QString request = QString("<request type=\"%1\">").arg(name()); + + // Non-filter values ... + request += QString( + "<args configName=\"%1\" baseTimestamp=\"%2\" evalLoTime=\"%3\" " + "evalHiTime=\"%4\" evalStep=\"%5\" medianWinSize=\"%6\" />") + .arg(configName) + .arg(baseTimestamp) + .arg(evalLoTime) + .arg(evalHiTime) + .arg(evalStep) + .arg(medianWinSize); + + // Filter values ... + for (int i = 0; i < testCaseFilter.size(); ++i) + request += QString("<testCase name=\"%1\" />").arg(testCaseFilter.at(i)); + for (int i = 0; i < metricFilter.size(); ++i) + request += QString("<metric name=\"%1\" />").arg(metricFilter.at(i)); + for (int i = 0; i < platformFilter.size(); ++i) + request += QString("<platform name=\"%1\" />").arg(platformFilter.at(i)); + for (int i = 0; i < hostFilter.size(); ++i) + request += QString("<host name=\"%1\" />").arg(hostFilter.at(i)); + for (int i = 0; i < branchFilter.size(); ++i) + request += QString("<branch name=\"%1\" />").arg(branchFilter.at(i)); + + request += "</request>"; + + return xmlConvert(request); +} + +// ### 2 B DOCUMENTED! +static bool deleteIndexConfig( + QSqlDatabase *database, const QString &type, int configId, const QStringList &filterTables, + QString *reply) +{ + database->transaction(); + + QSqlQuery query(*database); + + // Delete from filter tables ... + for (int i = 0; i < filterTables.size(); ++i) { + if (!query.exec( + QString("DELETE FROM %1 WHERE indexConfigId=%2;") + .arg(filterTables.at(i)).arg(configId))) { + *reply = errorReply( + query, type, QString("failed to delete from %1").arg(filterTables.at(i))); + database->rollback(); + return false; + } + } + + // Delete from main table ... + if (!query.exec( + QString("DELETE FROM indexConfig WHERE id=%1;").arg(configId))) { + *reply = errorReply(query, type, "failed to delete from indexConfig"); + database->rollback(); + return false; + } + + database->commit(); + return true; +} + +QByteArray BMRequest_IndexPutConfig::toReplyBuffer() +{ + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + // Get config name ... + configName = argsElem.attributeNode("configName").value(); + + // Get non-filter values ... + bool ok; + + baseTimestamp = argsElem.attributeNode("baseTimestamp").value().toInt(&ok); + Q_ASSERT(ok); + + evalLoTime = argsElem.attributeNode("evalLoTime").value().toInt(&ok); + Q_ASSERT(ok); + + evalHiTime = argsElem.attributeNode("evalHiTime").value().toInt(&ok); + Q_ASSERT(ok); + + evalStep = argsElem.attributeNode("evalStep").value().toInt(&ok); + Q_ASSERT(ok); + + medianWinSize = argsElem.attributeNode("medianWinSize").value().toInt(&ok); + Q_ASSERT(ok); + + // Get filter values ... + + QDomNodeList testCaseNodes = doc.elementsByTagName("testCase"); + for (int i = 0; i < testCaseNodes.size(); ++i) + testCaseFilter.append(testCaseNodes.at(i).toElement().attributeNode("name").value()); + + QDomNodeList metricNodes = doc.elementsByTagName("metric"); + for (int i = 0; i < metricNodes.size(); ++i) + metricFilter.append(metricNodes.at(i).toElement().attributeNode("name").value()); + + QDomNodeList platformNodes = doc.elementsByTagName("platform"); + for (int i = 0; i < platformNodes.size(); ++i) + platformFilter.append(platformNodes.at(i).toElement().attributeNode("name").value()); + + QDomNodeList hostNodes = doc.elementsByTagName("host"); + for (int i = 0; i < hostNodes.size(); ++i) + hostFilter.append(hostNodes.at(i).toElement().attributeNode("name").value()); + + QDomNodeList branchNodes = doc.elementsByTagName("branch"); + for (int i = 0; i < branchNodes.size(); ++i) + branchFilter.append(branchNodes.at(i).toElement().attributeNode("name").value()); + + + //------------------------------------ + + // Insert config (overwriting any existing one) ... + + QString reply; + QSqlQuery *query = createQuery(); + const QStringList filterTables = + QStringList() << "icTestCase" << "icMetric" << "icPlatform" << "icHost" << "icBranch"; + QMap<QString, QStringList> filters; + filters.insert("icTestCase", testCaseFilter); + filters.insert("icMetric", metricFilter); + filters.insert("icPlatform", platformFilter); + filters.insert("icHost", hostFilter); + filters.insert("icBranch", branchFilter); + int configId; + + // Check if the config already exists ... + if (!query->exec(QString("SELECT id FROM indexConfig WHERE name='%1';").arg(configName))) { + reply = errorReply(*query, name(), "failed to save index config (exec() failed (1))"); + deleteQuery(query); + return xmlConvert(reply); + } + + if (query->next()) { + // ... it does, so delete it ... + + configId = query->value(0).toInt(); + deleteQuery(query); + if (!deleteIndexConfig(database, name(), configId, filterTables, &reply)) + return xmlConvert(reply); + } else { + deleteQuery(query); + } + + + // Insert the new config ... + + query = createQuery(Transaction); + + // Insert into main table ... + if (!query->exec( + QString( + "INSERT INTO indexConfig (name, baseTimestamp, loEvalTimestamp, hiEvalTimestamp, " + "evalTimestep, medianWinSize) " + "VALUES ('%1', %2, %3, %4, %5, %6);") + .arg(configName) + .arg(baseTimestamp) + .arg(evalLoTime) + .arg(evalHiTime) + .arg(evalStep) + .arg(medianWinSize))) { + reply = errorReply(*query, name(), "failed to insert into indexConfig"); + deleteQuery(query, Rollback); + return xmlConvert(reply); + } + + // Get last insert ID ... + if (!BMMisc::getLastInsertId(query, &configId)) { + Q_ASSERT(false); // ### Backup code (involving a separate query) to go here! + reply = errorReply(*query, name(), "failed to get last ID inserted in indexConfig"); + deleteQuery(query, Rollback); + return xmlConvert(reply); + } + + // Insert into filter tables ... + for (int i = 0; i < filterTables.size(); ++i) { + QStringList filter = filters.value(filterTables.at(i)); + for (int j = 0; j < filter.size(); ++j) { + if (!query->exec( + QString("INSERT INTO %1 (indexConfigId, name) VALUES (%2, '%3');") + .arg(filterTables.at(i)).arg(configId).arg(filter.at(j)))) { + reply = errorReply( + *query, name(), + QString("failed to insert value '%1' into filter table %1") + .arg(filter.at(j)).arg(filterTables.at(i))); + deleteQuery(query, Rollback); + return xmlConvert(reply); + } + } + } + + deleteQuery(query, Commit); + + + reply = QString("<reply type=\"%1\" />").arg(name()); + return xmlConvert(reply); +} + +void BMRequest_IndexPutConfig::handleReply_Raw(const QStringList &args) const +{ + printf("NOTE: raw output not supported yet; printing JSON output for now:\n\n"); + handleReply_JSON(args); +} + +void BMRequest_IndexPutConfig::handleReply_JSON(const QStringList &args) const +{ + Q_UNUSED(args); + + QString reply; + + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + if (error.isEmpty()) + reply = "{}"; + else + reply = QString("{\"error\": \"error saving index configuration: %1\"}").arg(error); + + BMMisc::printJSONOutput(reply); +} + + +// --- IndexDeleteConfig --- +QByteArray BMRequest_IndexDeleteConfig::toRequestBuffer(QString *) +{ + const QString request = + QString("<request type=\"%1\"><args configName=\"%2\" /></request>") + .arg(name()).arg(configName); + return xmlConvert(request); +} + +QByteArray BMRequest_IndexDeleteConfig::toReplyBuffer() +{ + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + // Get config name ... + configName = argsElem.attributeNode("configName").value(); + + QString reply; + QSqlQuery *query = createQuery(); + const QStringList filterTables = + QStringList() << "icTestCase" << "icMetric" << "icPlatform" << "icHost" << "icBranch"; + int configId; + + // Check if the config already exists ... + if (!query->exec(QString("SELECT id FROM indexConfig WHERE name='%1';").arg(configName))) { + reply = errorReply(*query, name(), "failed to save index config (exec() failed (1))"); + deleteQuery(query); + return xmlConvert(reply); + } + + if (query->next()) { + // ... it does, so delete it ... + configId = query->value(0).toInt(); + deleteQuery(query); + if (!deleteIndexConfig(database, name(), configId, filterTables, &reply)) + return xmlConvert(reply); + } else { + deleteQuery(query); + } + + reply = QString("<reply type=\"%1\" />").arg(name()); + return xmlConvert(reply); +} + +void BMRequest_IndexDeleteConfig::handleReply_Raw(const QStringList &args) const +{ + printf("NOTE: raw output not supported yet; printing JSON output for now:\n\n"); + handleReply_JSON(args); +} + +void BMRequest_IndexDeleteConfig::handleReply_JSON(const QStringList &args) const +{ + Q_UNUSED(args); + + QString reply; + + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + if (error.isEmpty()) + reply = "{}"; + else + reply = QString("{\"error\": \"error deleting index configuration: %1\"}").arg(error); + + BMMisc::printJSONOutput(reply); +} diff --git a/src/bm/bmrequest.h b/src/bm/bmrequest.h index 83986d0..1d2c40f 100644 --- a/src/bm/bmrequest.h +++ b/src/bm/bmrequest.h @@ -566,9 +566,9 @@ public: BMRequest_IndexGetValues( const int evalLoTime, const int evalHiTime, const int evalStep, const QList<int> &evalTimestamps, const int baseTimestamp, const int medianWinSize, - const QString &cacheKey, const QStringList &metricFilter, const QStringList &platformFilter, - const QStringList &hostFilter, const QStringList &branchFilter, - const QStringList &testCaseFilter) + const QString &cacheKey, const QStringList &testCaseFilter, + const QStringList &metricFilter, const QStringList &platformFilter, + const QStringList &hostFilter, const QStringList &branchFilter) : evalLoTime(evalLoTime), evalHiTime(evalHiTime), evalStep(evalStep) , evalTimestamps(evalTimestamps), baseTimestamp(baseTimestamp) , medianWinSize(medianWinSize), cacheKey(cacheKey), testCaseFilter(testCaseFilter) @@ -607,4 +607,89 @@ private: const QStringList &filter, const QString &filterName, QString *reply); }; +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_IndexGetConfigs : public BMRequest +{ +public: + BMRequest_IndexGetConfigs() {} + BMRequest_IndexGetConfigs(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString name() const { return "IndexGetConfigs"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const; + void handleReply_JSON(const QStringList &args) const; +}; + +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_IndexGetConfig : public BMRequest +{ +public: + BMRequest_IndexGetConfig(const QString &configName) + : configName(configName) {} + BMRequest_IndexGetConfig(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString configName; + + QString name() const { return "IndexGetConfig"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const; + void handleReply_JSON(const QStringList &args) const; +}; + +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_IndexPutConfig : public BMRequest +{ +public: + BMRequest_IndexPutConfig( + const QString &configName, const int baseTimestamp, const int evalLoTime, + const int evalHiTime, const int evalStep, const int medianWinSize, + const QStringList &testCaseFilter, const QStringList &metricFilter, + const QStringList &platformFilter, const QStringList &hostFilter, + const QStringList &branchFilter) + : configName(configName), baseTimestamp(baseTimestamp), evalLoTime(evalLoTime) + , evalHiTime(evalHiTime), evalStep(evalStep), medianWinSize(medianWinSize) + , testCaseFilter(testCaseFilter), metricFilter(metricFilter) + , platformFilter(platformFilter), hostFilter(hostFilter), branchFilter(branchFilter) + {} + + BMRequest_IndexPutConfig(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString configName; + int baseTimestamp; + int evalLoTime; + int evalHiTime; + int evalStep; + int medianWinSize; + QStringList testCaseFilter; + QStringList metricFilter; + QStringList platformFilter; + QStringList hostFilter; + QStringList branchFilter; + + QString name() const { return "IndexPutConfig"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const; + void handleReply_JSON(const QStringList &args) const; +}; + +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_IndexDeleteConfig : public BMRequest +{ +public: + BMRequest_IndexDeleteConfig(const QString &configName) + : configName(configName) {} + BMRequest_IndexDeleteConfig(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString configName; + + QString name() const { return "IndexDeleteConfig"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const; + void handleReply_JSON(const QStringList &args) const; +}; + #endif // BMREQUEST_H diff --git a/src/bm/plotter.cpp b/src/bm/plotter.cpp index e709fb5..180827a 100644 --- a/src/bm/plotter.cpp +++ b/src/bm/plotter.cpp @@ -26,6 +26,7 @@ #include <QGraphicsScene> #include <QGraphicsSimpleTextItem> #include <QPainter> +#include <QDebug> // ### Note: There is a potential for refactoring in order to avoid code duplication // in this file. diff --git a/src/bmclient/main.cpp b/src/bmclient/main.cpp index ecbb6e9..63e18bc 100644 --- a/src/bmclient/main.cpp +++ b/src/bmclient/main.cpp @@ -113,6 +113,7 @@ private: BMRequest * createIndexGetValuesRequest( const QStringList &args, QString *error, const QString &command = "index get values") const; + BMRequest * createIndexPutConfigRequest(const QStringList &args, QString *error) const; mutable BMRequest::OutputFormat explicitOutputFormat; mutable bool useExplicitOutputFormat; BMRequest::OutputFormat outputFormat() const; @@ -674,6 +675,62 @@ BMRequest * Executor::createRequest(const QStringList &args, QString *error) con setOutputFormat(BMRequest::HTML); return request; + } else if( + (args.size() >= 3) && (args.at(0) == "index") && (args.at(1) == "get") + && (args.at(2) == "configs")) { + // --- 'index get configs' command --- + + return new BMRequest_IndexGetConfigs(); + + } else if( + (args.size() >= 3) && (args.at(0) == "index") && (args.at(1) == "get") + && (args.at(2) == "config")) { + // --- 'index get config' command --- + + QStringList values; + + // Get name ... + if (!BMMisc::getOption(args, "-name", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-name option not found"; + return 0; + } + const QString configName = values.first().trimmed(); + if (configName.isEmpty()) { + *error = "empty config name"; + return 0; + } + + return new BMRequest_IndexGetConfig(configName); + + } else if( + (args.size() >= 3) && (args.at(0) == "index") && (args.at(1) == "put") + && (args.at(2) == "config")) { + // --- 'index put config' command --- + + return createIndexPutConfigRequest(args, error); + + } else if( + (args.size() >= 3) && (args.at(0) == "index") && (args.at(1) == "delete") + && (args.at(2) == "config")) { + // --- 'index delete config' command --- + + QStringList values; + + // Get name ... + if (!BMMisc::getOption(args, "-name", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-name option not found"; + return 0; + } + const QString configName = values.first().trimmed(); + if (configName.isEmpty()) { + *error = "empty config name"; + return 0; + } + + return new BMRequest_IndexDeleteConfig(configName); + } else if ((args.size() >= 2) && (args.at(0) == "get") && (args.at(1) == "detailspage")) { // --- 'get detailspage' command --- @@ -864,7 +921,7 @@ BMRequest * Executor::createIndexGetValuesRequest( QStringList values; - // Get explicit eval timestamps (default is current time) or time range ... + // Get explicit eval timestamps or time range ... int evalLoTime = -1; int evalHiTime = -1; int evalStep = -1; @@ -915,7 +972,7 @@ BMRequest * Executor::createIndexGetValuesRequest( evalStep = values.at(2).toInt(&ok); if ((!ok) || (evalStep < 1)) { *error = - QString("eval time range step not a positve integer: %1").arg(values.at(2)); + QString("eval time range step not a positive integer: %1").arg(values.at(2)); return 0; } @@ -975,6 +1032,9 @@ BMRequest * Executor::createIndexGetValuesRequest( } // Get filters ... + QStringList testCaseFilter; + if (!BMMisc::getMultiOption(args, "-testcase", &testCaseFilter, error)) + return 0; QStringList metricFilter; if (!BMMisc::getMultiOption(args, "-metric", &metricFilter, error)) return 0; @@ -987,13 +1047,100 @@ BMRequest * Executor::createIndexGetValuesRequest( QStringList branchFilter; if (!BMMisc::getMultiOption(args, "-branch", &branchFilter, error)) return 0; + + return new BMRequest_IndexGetValues( + evalLoTime, evalHiTime, evalStep, evalTimestamps, baseTimestamp, medianWinSize, cacheKey, + testCaseFilter, metricFilter, platformFilter, hostFilter, branchFilter); +} + +BMRequest * Executor::createIndexPutConfigRequest(const QStringList &args, QString *error) const +{ + QStringList values; + + // Get name ... + if (!BMMisc::getOption(args, "-name", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-name option not found"; + return 0; + } + const QString configName = values.first().trimmed(); + if (configName.isEmpty()) { + *error = "empty config name"; + return 0; + } + + // Get base timestamp ... + if (!BMMisc::getOption(args, "-basetimestamp", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-basetimestamp option not found"; + return 0; + } + bool ok; + int baseTimestamp = values.at(0).toInt(&ok); + if (!ok) { + *error = "failed to extract base timestamp as an integer"; + return 0; + } + baseTimestamp = qMax(baseTimestamp, -1); + + // Get evaluation time range ... + if (!BMMisc::getOption(args, "-evaltimerange", &values, 3, 0, error)) { + if (error->isEmpty()) + *error = "-evaltimerange option not found"; + return 0; + } + const int evalLoTime = values.at(0).toInt(&ok); + if (!ok) { + *error = QString("eval time range start not an integer: %1").arg(values.at(0)); + return 0; + } + const int evalHiTime = values.at(1).toInt(&ok); + if (!ok) { + *error = QString("eval time range end not an integer: %1").arg(values.at(1)); + return 0; + } + const int evalStep = values.at(2).toInt(&ok); + if ((!ok) || (evalStep < 1)) { + *error = QString("eval time range step not a positive integer: %1").arg(values.at(2)); + return 0; + } + if ((evalLoTime < 0) || ((evalLoTime > evalHiTime) && (evalHiTime != -1))) { + *error = QString("invalid time range: %1..%2").arg(evalLoTime).arg(evalHiTime); + return 0; + } + + // Get median window size ... + if (!BMMisc::getOption(args, "-medianwinsize", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-medianwinsize option not found"; + return 0; + } + const int medianWinSize = values.at(0).toInt(&ok); + if ((!ok) || (medianWinSize < 1)) { + *error = "failed to extract median window size as a positive integer"; + return 0; + } + + // Get filters ... QStringList testCaseFilter; if (!BMMisc::getMultiOption(args, "-testcase", &testCaseFilter, error)) return 0; + QStringList metricFilter; + if (!BMMisc::getMultiOption(args, "-metric", &metricFilter, error)) + return 0; + QStringList platformFilter; + if (!BMMisc::getMultiOption(args, "-platform", &platformFilter, error)) + return 0; + QStringList hostFilter; + if (!BMMisc::getMultiOption(args, "-host", &hostFilter, error)) + return 0; + QStringList branchFilter; + if (!BMMisc::getMultiOption(args, "-branch", &branchFilter, error)) + return 0; - return new BMRequest_IndexGetValues( - evalLoTime, evalHiTime, evalStep, evalTimestamps, baseTimestamp, medianWinSize, cacheKey, - metricFilter, platformFilter, hostFilter, branchFilter, testCaseFilter); + return new BMRequest_IndexPutConfig( + configName, baseTimestamp, evalLoTime, evalHiTime, evalStep, medianWinSize, testCaseFilter, + metricFilter, platformFilter, hostFilter, branchFilter); } // ### 2 B DOCUMENTED! @@ -1360,9 +1507,9 @@ class DirectExecutor : public Executor "index get values \\\n" " {-evaltimestamp <...> ...} | {-evaltimerange <first> <last> <step>} \\\n" " -basetimestamp <...> [-medianwinsize <...> (default = 8)] \\\n" - " [-metric <...> -metric <...> ...] [-platform <...> -platform <...> ...] \\\n" - " [-host <...> -host <...> ...] [-branch <...> -branch <...> ...] \\\n" - " [-testcase <...> -testcase <...> ...]\n" + " [-testcase <...> -testcase <...> ...] [-metric <...> -metric <...> ...] \\\n" + " [-platform <...> -platform <...> ...] [-host <...> -host <...> ...] \\\n" + " [-branch <...> -branch <...> ...]\n" << "index get plot <SAME AS 'index get values' except that the optional \\\n" @@ -1372,6 +1519,23 @@ class DirectExecutor : public Executor "index get detailspage <SAME AS 'index get plot'> except that the mandatory \\\n" " -stylesheet option is recognized\n" + << + "index get configs\n" + + << + "index get config -name <...>\n" + + << + "index put config -name <...> \\\n" + " -basetimestamp <...> -evaltimerange <first> <last> <step> \\\n" + " -medianwinsize <...> \\\n" + " [-testcase <...> -testcase <...> ...] [-metric <...> -metric <...> ...] \\\n" + " [-platform <...> -platform <...> ...] [-host <...> -host <...> ...] \\\n" + " [-branch <...> -branch <...> ...]\n" + + << + "index delete config -name <...>\n" + ; qDebug() << "\nNote: the -server option may be replaced by a BMSERVER=<host>:<port> " diff --git a/src/bmserver/main.cpp b/src/bmserver/main.cpp index dbc4d59..9ee80f9 100644 --- a/src/bmserver/main.cpp +++ b/src/bmserver/main.cpp @@ -117,6 +117,58 @@ static bool initDatabase(const QString &dbfile, QString *error) ", UNIQUE(bmcontextId, snapshotId));"); Q_ASSERT(ok); + // indexConfig + ok = query.exec( + "CREATE TABLE indexConfig(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", name TEXT NOT NULL" + ", baseTimestamp INTEGER NOT NULL" + ", loEvalTimestamp INTEGER NOT NULL" + ", hiEvalTimestamp INTEGER NOT NULL" + ", evalTimestep INTEGER NOT NULL" + ", medianWinSize INTEGER NOT NULL" + ", UNIQUE(name));"); + Q_ASSERT(ok); + + // icTestCase + ok = query.exec( + "CREATE TABLE icTestCase(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", indexConfigId INTEGER REFERENCES indexConfig(id)" + ", name TEXT NOT NULL" + ", UNIQUE(indexConfigId, name));"); + Q_ASSERT(ok); + + // icMetric + ok = query.exec( + "CREATE TABLE icMetric(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", indexConfigId INTEGER REFERENCES indexConfig(id)" + ", name TEXT NOT NULL" + ", UNIQUE(indexConfigId, name));"); + Q_ASSERT(ok); + + // icPlatform + ok = query.exec( + "CREATE TABLE icPlatform(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", indexConfigId INTEGER REFERENCES indexConfig(id)" + ", name TEXT NOT NULL" + ", UNIQUE(indexConfigId, name));"); + Q_ASSERT(ok); + + // icHost + ok = query.exec( + "CREATE TABLE icHost(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", indexConfigId INTEGER REFERENCES indexConfig(id)" + ", name TEXT NOT NULL" + ", UNIQUE(indexConfigId, name));"); + Q_ASSERT(ok); + + // icBranch + ok = query.exec( + "CREATE TABLE icBranch(id INTEGER PRIMARY KEY AUTOINCREMENT" + ", indexConfigId INTEGER REFERENCES indexConfig(id)" + ", name TEXT NOT NULL" + ", UNIQUE(indexConfigId, name));"); + Q_ASSERT(ok); + // *** Create indexes *** ok = query.exec("CREATE INDEX index_metric_name ON metric(name);"); @@ -165,6 +217,29 @@ static bool initDatabase(const QString &dbfile, QString *error) "CREATE INDEX index_result_bmcontextId_snapshotId ON result(bmcontextId, snapshotId);"); Q_ASSERT(ok); + ok = query.exec("CREATE INDEX index_indexConfig_name ON indexConfig(name);"); + Q_ASSERT(ok); + + ok = query.exec( + "CREATE INDEX index_icTestCase_indexConfigId_name ON icTestCase(indexConfigId, name);"); + Q_ASSERT(ok); + + ok = query.exec( + "CREATE INDEX index_icMetric_indexConfigId_name ON icMetric(indexConfigId, name);"); + Q_ASSERT(ok); + + ok = query.exec( + "CREATE INDEX index_icPlatform_indexConfigId_name ON icPlatform(indexConfigId, name);"); + Q_ASSERT(ok); + + ok = query.exec( + "CREATE INDEX index_icHost_indexConfigId_name ON icHost(indexConfigId, name);"); + Q_ASSERT(ok); + + ok = query.exec( + "CREATE INDEX index_icBranch_indexConfigId_name ON icBranch(indexConfigId, name);"); + Q_ASSERT(ok); + return true; } diff --git a/src/bmweb/global.js b/src/bmweb/global.js index e435e9d..e6b80d6 100644 --- a/src/bmweb/global.js +++ b/src/bmweb/global.js @@ -396,6 +396,22 @@ function selectedOptions(id) return result; } +function sortOptions(id) +{ + var select = document.getElementById(id); + if (!select) + return; + var optionValues = []; + for (i = 0; i < select.options.length; ++i) + optionValues[optionValues.length] = select.options[i].value; + optionValues.sort(function(val1, val2) { + return val1 > val2; + }); + select.options.length = 0; + for (i = 0; i < optionValues.length; ++i) + select.options[select.options.length] = new Option(optionValues[i]); +} + function setDiffToleranceOptions(select) { select.options[0] = new Option("0.00", "0.00"); diff --git a/src/bmweb/indexsection.js b/src/bmweb/indexsection.js index bddccd5..93f3630 100644 --- a/src/bmweb/indexsection.js +++ b/src/bmweb/indexsection.js @@ -41,18 +41,18 @@ function updateEvalLink( // Add evaluation timestamps ... - evalTimeLo = getIntFromTextInput("ixEvalTimeLo", defaultEvalTimeLo); - document.getElementById("ixEvalTimeLo").value = evalTimeLo; + evalTimeLo = getIntFromTextInput("ixEvalLoTime", defaultEvalTimeLo); + document.getElementById("ixEvalLoTime").value = evalTimeLo; - evalTimeHi = getIntFromTextInput("ixEvalTimeHi", defaultEvalTimeHi); - document.getElementById("ixEvalTimeHi").value = evalTimeHi; + evalTimeHi = getIntFromTextInput("ixEvalHiTime", defaultEvalTimeHi); + document.getElementById("ixEvalHiTime").value = evalTimeHi; - evalTimeStep = getIntFromTextInput("ixEvalTimeStep", defaultEvalTimeStep); + evalTimeStep = getIntFromTextInput("ixEvalStep", defaultEvalTimeStep); // Make sure the time step is at least 24 hours to prevent unnecessary // server load (results are currently not uploaded to the server more // than approximately once a day anyway) ... evalTimeStep = Math.max(evalTimeStep, 86400); - document.getElementById("ixEvalTimeStep").value = evalTimeStep; + document.getElementById("ixEvalStep").value = evalTimeStep; url += " -evaltimerange " + evalTimeLo + " " + evalTimeHi + " " + evalTimeStep; @@ -114,16 +114,37 @@ function IndexSection() this.name = function() { return "ixSection"; } - this.resetFilters = function(handleDone) + this.reset = function(handleDone) { - this.finalize_all = function() + this.finalize_reset = function() { setMessage(""); - document.getElementById("resetIX").disabled = false; + document.getElementById("ixReset").disabled = false; if (handleDone) handleDone(); } + this.handleReply_resetConfigs = function(reply) + { + if (reply.error) { + alert("reset() failed when fetching configs (2): " + reply.error); + } else { + // Populate <select> tag ... + select = document.getElementById("ixConfigs"); + select.options.length = 0; + for (i = 0; i < reply.names.length; ++i) + select.options[select.options.length] = new Option(reply.names[i]); + } + + this.finalize_reset(); + } + + this.handleError_resetConfigs = function(error) + { + alert("reset() failed when fetching configs (1): " + error); + this.finalize_resetConfigs(); + } + this.populateFilterTable = function(tableId, values, skipValue) { // alert("populating " + tableId + " filter table; values (" + values.length @@ -151,6 +172,7 @@ function IndexSection() td = tr.insertCell(1); td.setAttribute("style", "border:0px; padding:0px"); + // ### Just display the concatenation of multi-component values for now: value = ""; for (j = 0; j < values[i].length; ++j) value += values[i][j]; @@ -160,11 +182,13 @@ function IndexSection() } } - this.handleReply_tccontexts = function(reply) + this.handleReply_resetFilters = function(reply) { if (reply.error) { - alert( - "resetFilters() failed when fetching test cases and contexts: " + reply.error); + alert("reset() failed when fetching test cases and contexts (2): " + reply.error); + + this.finalize_reset(); + } else { this.populateFilterTable("ixTestCaseFilter", reply.values[0]); @@ -203,19 +227,25 @@ function IndexSection() this.updateMatches(); } - this.finalize_all(); + // Fetch index configurations ... + setMessage("fetching configurations ..."); + var args = ["index", "get", "configs"]; + sendRequest( + args, + function(reply) { thisSection.handleReply_resetConfigs(reply); }, + function(error) { thisSection.handleError_resetConfigs(error); }); } - this.handleError_all = function(error) + this.handleError_resetFilters = function(error) { - alert("resetFilters() failed: " + error); - this.finalize_all(); + alert("reset() failed when fetching test cases and contexts (1): " + error); + this.finalize_reset(); } var thisSection = this; // ### hack? - document.getElementById("resetIX").disabled = true; + document.getElementById("ixReset").disabled = true; // Clear tables ... removeElementChildren(document.getElementById("ixTestCaseFilter")); @@ -229,23 +259,19 @@ function IndexSection() var args = ["get", "tccontexts"]; sendRequest( args, - function(reply) { thisSection.handleReply_tccontexts(reply); }, - function(error) { thisSection.handleError_all(error); }); - } - - this.reset = function(handleDone) - { - this.resetFilters(handleDone); + function(reply) { thisSection.handleReply_resetFilters(reply); }, + function(error) { thisSection.handleError_resetFilters(error); }); } - this.clearFilter = function(tableId) { + this.clearFilter = function(tableId, skipUpdate) { table = document.getElementById(tableId); for (i = 0; i < table.rows.length; ++i) { input = document.getElementById(tableId + ":" + i); input.checked = false; } - this.updateMatches(); + if (!skipUpdate) + this.updateMatches(); } this.updateMatches = function() { @@ -309,6 +335,211 @@ function IndexSection() } } + this.loadConfig = function() + { + this.finalize_loadConfig = function() + { + setMessage(""); + } + + this.handleReply_loadConfig = function(reply) + { + if (reply.error) { + alert("loadConfig() failed (2): " + reply.error); + + } else { + + // Load non-filter values ... + document.getElementById("ixBaseTime").value = reply.baseTimestamp; + document.getElementById("ixEvalLoTime").value = reply.evalLoTime; + document.getElementById("ixEvalHiTime").value = reply.evalHiTime; + document.getElementById("ixEvalStep").value = reply.evalStep; + document.getElementById("ixMedianWinSize").value = reply.medianWinSize; + + // Load filter values ... + function loadFilterValues(tableId, filter) { + thisSection.clearFilter(tableId, true); + table = document.getElementById(tableId); + for (i = 0; i < table.rows.length; ++i) { + name = table.rows[i].cells[1].innerHTML; + if (filter.indexOf(name) != -1) + table.rows[i].cells[0].childNodes[0].checked = true; + } + } + loadFilterValues("ixTestCaseFilter", reply.testCaseFilter); + loadFilterValues("ixMetricFilter", reply.metricFilter); + loadFilterValues("ixPlatformFilter", reply.platformFilter); + loadFilterValues("ixHostFilter", reply.hostFilter); + loadFilterValues("ixBranchFilter", reply.branchFilter); + + this.updateMatches(); + } + + + this.finalize_loadConfig(); + } + + this.handleError_loadConfig = function(error) + { + alert("loadConfig() failed (1): " + error); + this.finalize_loadConfig(); + } + + + var thisSection = this; // ### hack? + + // Get current config name ... + configName = selectedOptions("ixConfigs")[0] + + // Fetch config values ... + setMessage("fetching config values ..."); + var args = ["index", "get", "config", "-name", "'" + configName + "'"]; + sendRequest( + args, + function(reply) { thisSection.handleReply_loadConfig(reply); }, + function(error) { thisSection.handleError_loadConfig(error); }); + } + + this.deleteConfig = function() + { + this.finalize_deleteConfig = function() + { + setMessage(""); + } + + this.handleReply_deleteConfig = function(reply) + { + if (reply.error) { + alert("deleteConfig() failed (2): " + reply.error); + } else { + select = document.getElementById("ixConfigs"); + pos = -1; + for (i = 0; i < select.options.length; ++i) + if (select.options[i].value == configName) { + pos = i; + break; + } + // assert(pos >= 0); + select.remove(pos); + +// alert("configuration successfully deleted"); + } + + this.finalize_deleteConfig(); + } + + this.handleError_deleteConfig = function(error) + { + alert("deleteConfig() failed (1): " + error); + this.finalize_deleteConfig(); + } + + var thisSection = this; // ### hack? + + // Get current config name ... + configName = selectedOptions("ixConfigs")[0] + + if (!confirm("Really delete configuration '" + configName + "' ?")) + return; + + setMessage("deleting config values ..."); + var args = ["index", "delete", "config", "-name", "'" + configName + "'"]; + sendRequest( + args, + function(reply) { thisSection.handleReply_deleteConfig(reply); }, + function(error) { thisSection.handleError_deleteConfig(error); }); + } + + this.saveConfig = function() + { + this.finalize_saveConfig = function() + { + setMessage(""); + } + + this.handleReply_saveConfig = function(reply) + { + if (reply.error) { + alert("saveConfig() failed (2): " + reply.error); + } else { + + select = document.getElementById("ixConfigs"); + + pos = -1; + for (i = 0; i < select.options.length; ++i) + if (select.options[i].value == configName) { + pos = i; + break; + } + if (pos == -1) { + select.appendChild(new Option(configName)); + sortOptions("ixConfigs"); + } + +// alert("configuration successfully saved"); + } + + this.finalize_saveConfig(); + } + + this.handleError_saveConfig = function(error) + { + alert("saveConfig() failed (1): " + error); + this.finalize_saveConfig(); + } + + var thisSection = this; // ### hack? + + // Get new config name ... + configName = trim(document.getElementById("ixNewConfig").value); + if (configName.length == 0) { + alert("please enter a non-empty name!"); + return; + } + + if (!confirm("Really save configuration '" + configName + "' ?")) + return; + + // Save config values ... + setMessage("saving config values ..."); + var args = [ + "index", "put", "config", "-name", "'" + configName + "'", + "-basetimestamp", document.getElementById("ixBaseTime").value, + "-evaltimerange", + document.getElementById("ixEvalLoTime").value, + document.getElementById("ixEvalHiTime").value, + document.getElementById("ixEvalStep").value, + "-medianwinsize", document.getElementById("ixMedianWinSize").value + ]; + + function filterSelection(tableId, option) + { + var sel = []; + + table = document.getElementById(tableId); + for (i = 0; i < table.rows.length; ++i) { + if (table.rows[i].cells[0].childNodes[0].checked) { + sel[sel.length] = option; + sel[sel.length] = table.rows[i].cells[1].innerHTML; + + } + } + + return sel; + } + + args = args.concat(filterSelection("ixTestCaseFilter", "-testcase")); + args = args.concat(filterSelection("ixMetricFilter", "-metric")); + args = args.concat(filterSelection("ixPlatformFilter", "-platform")); + args = args.concat(filterSelection("ixHostFilter", "-host")); + args = args.concat(filterSelection("ixBranchFilter", "-branch")); + + sendRequest( + args, + function(reply) { thisSection.handleReply_saveConfig(reply); }, + function(error) { thisSection.handleError_saveConfig(error); }); + } + this.appendAll = function(section) { var thisSection = this; // ### hack? @@ -334,7 +565,7 @@ function IndexSection() ); - // Create settings section ... + // Create 'Configuration' section ... section.appendChild(document.createElement("br")); section.appendChild(document.createElement("br")); table = section.appendChild(document.createElement("table")); @@ -342,12 +573,36 @@ function IndexSection() tr = table.insertRow(0); td = tr.insertCell(0); td.setAttribute("style", "border:0px; padding:0px"); - fieldset = td.appendChild(document.createElement("fieldset")); + configFieldset = td.appendChild(document.createElement("fieldset")); - legend = fieldset.appendChild(document.createElement("legend")); - legend.appendChild(document.createTextNode("Settings")); + legend = configFieldset.appendChild(document.createElement("legend")); + legend.appendChild(document.createTextNode("Configuration")); - table = fieldset.appendChild(document.createElement("table")); + // ... 'Reset' button ... + input = configFieldset.appendChild(document.createElement("input")); + input.setAttribute("type", "button"); + input.setAttribute("id", "ixReset"); + input.setAttribute("value", "Reset"); + input.onclick = function() { + try { thisSection.reset(); } + catch (ex) { alert("thisSection.reset() failed: " + ex); } + } + + configFieldset.appendChild(document.createElement("br")); + configFieldset.appendChild(document.createElement("br")); + + table = configFieldset.appendChild(document.createElement("table")); + table.setAttribute("style", "border:0px; padding:0px"); + tr = table.insertRow(0); + topLeftCell = tr.insertCell(0); + topLeftCell.setAttribute("style", "border:0px; padding:0px"); + topMidCell = tr.insertCell(1); + topMidCell.setAttribute("style", "border:0px; padding:10px"); + topRightCell = tr.insertCell(2); + topRightCell.setAttribute("style", "border:0px; padding:0px"); + + // Create "miscellaneous settings" section ... + table = topLeftCell.appendChild(document.createElement("table")); table.setAttribute("style", "border:0px; padding:0px"); // ... base timestamp ... @@ -367,7 +622,7 @@ function IndexSection() td = tr.insertCell(1); input = td.appendChild(document.createElement("input")); input.setAttribute("type", "text"); - input.setAttribute("id", "ixEvalTimeLo"); + input.setAttribute("id", "ixEvalLoTime"); input.setAttribute("value", defaultEvalTimeLo); // ... high evaluation timestamp ... @@ -377,7 +632,7 @@ function IndexSection() td = tr.insertCell(1); input = td.appendChild(document.createElement("input")); input.setAttribute("type", "text"); - input.setAttribute("id", "ixEvalTimeHi"); + input.setAttribute("id", "ixEvalHiTime"); input.setAttribute("value", defaultEvalTimeHi); // ... evaluation time step ... @@ -387,7 +642,7 @@ function IndexSection() td = tr.insertCell(1); input = td.appendChild(document.createElement("input")); input.setAttribute("type", "text"); - input.setAttribute("id", "ixEvalTimeStep"); + input.setAttribute("id", "ixEvalStep"); input.setAttribute("value", defaultEvalTimeStep); // ... median window size ... @@ -401,32 +656,96 @@ function IndexSection() input.setAttribute("value", defaultMedianWinSize); - // Create filter section ... - section.appendChild(document.createElement("br")); - table = section.appendChild(document.createElement("table")); + // Create "load/save" section ... + loadSaveTable = topRightCell.appendChild(document.createElement("table")); + + // ... available configurations + operations 'load' and 'delete' ... + tr = loadSaveTable.insertRow(loadSaveTable.rows.length); + td = tr.insertCell(0); + select = td.appendChild(document.createElement("select")); + select.setAttribute("id", "ixConfigs"); + select.setAttribute("size", "4"); + select.onchange = function() { + select = document.getElementById("ixConfigs"); + document.getElementById("ixNewConfig").value = + select.options[select.selectedIndex].value; + } + + td = tr.insertCell(1); + loadDeleteTable = td.appendChild(document.createElement("table")); + loadDeleteTable.setAttribute("style", "border:0px; padding:0px"); + + tr = loadDeleteTable.insertRow(loadDeleteTable.rows.length); + td = tr.insertCell(0); + td.setAttribute("style", "border:0px; padding:2px"); + input = td.appendChild(document.createElement("input")); + input.setAttribute("type", "button"); + input.setAttribute("id", "ixLoadConfig"); + input.setAttribute("value", "Load"); + input.onclick = function() { + try { thisSection.loadConfig(); } + catch (ex) { alert("thisSection.loadConfig() failed: " + ex); } + } + + tr = loadDeleteTable.insertRow(loadDeleteTable.rows.length); + td = tr.insertCell(0); + td.setAttribute("style", "border:0px; padding:2px"); + input = td.appendChild(document.createElement("input")); + input.setAttribute("type", "button"); + input.setAttribute("id", "ixDeleteConfig"); + input.setAttribute("value", "Delete"); + input.onclick = function() { + try { thisSection.deleteConfig(); } + catch (ex) { alert("thisSection.deleteConfig() failed: " + ex); } + } + + + // ... new configuration + operation 'save' ... + tr = loadSaveTable.insertRow(loadSaveTable.rows.length); + td = tr.insertCell(0); + input = td.appendChild(document.createElement("input")); + input.setAttribute("type", "text"); + input.setAttribute("id", "ixNewConfig"); +// input.setAttribute("value", "<enter new name here>"); + td = tr.insertCell(1); + td.setAttribute("colspan", "2"); + input = td.appendChild(document.createElement("input")); + input.setAttribute("type", "button"); + input.setAttribute("id", "ixSaveConfig"); + input.setAttribute("value", "Save"); + input.onclick = function() { + try { thisSection.saveConfig(); } + catch (ex) { alert("thisSection.saveConfig() failed: " + ex); } + } + + + // Create 'Filters' section ... + configFieldset.appendChild(document.createElement("br")); + table = configFieldset.appendChild(document.createElement("table")); table.setAttribute("style", "border:0px; padding:0px"); tr = table.insertRow(0); td = tr.insertCell(0); td.setAttribute("style", "border:0px; padding:0px"); - fieldset = td.appendChild(document.createElement("fieldset")); + filtersFieldset = td.appendChild(document.createElement("fieldset")); - legend = fieldset.appendChild(document.createElement("legend")); + legend = filtersFieldset.appendChild(document.createElement("legend")); legend.appendChild(document.createTextNode("Filters")); - // ... 'Reset' button ... - input = fieldset.appendChild(document.createElement("input")); - input.setAttribute("type", "button"); - input.setAttribute("id", "resetIX"); - input.setAttribute("value", "Reset"); - input.onclick = function() { - try { thisSection.resetFilters(); } - catch (ex) { alert("thisSection.resetFilters() failed: " + ex); } - } + // ... a bit of documentation ... + table = filtersFieldset.appendChild(document.createElement("table")); + table.setAttribute("style", "border:0px; padding:0px"); + tr = table.insertRow(0); + td = tr.insertCell(0); + td.setAttribute("style", "background-color:#eeeeee"); + td.innerHTML = + "<b>Note</b>: The green backgrounds in the table below indicate matching combinations. " + + "<br /><b>Note</b>: A filter with no explicitly selected values is considered " + + "disabled/open, i.e. it behaves as if all values were selected."; + + filtersFieldset.appendChild(document.createElement("br")); // ... main table ... - fieldset.appendChild(document.createElement("br")); - fieldset.appendChild(document.createElement("br")); - table = fieldset.appendChild(document.createElement("table")); + table = filtersFieldset.appendChild(document.createElement("table")); table.setAttribute("id", "ixFilters"); table.insertRow(0); // Header row |