diff --git a/package-lock.json b/package-lock.json index 1fee486624..e86fd09de6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12655,6 +12655,31 @@ "is-regex": "^1.0.3" } }, + "node_modules/chart.js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.0.1.tgz", + "integrity": "sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==", + "engines": { + "pnpm": "^7.0.0" + } + }, + "node_modules/chartjs-adapter-moment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", + "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", + "peerDependencies": { + "chart.js": ">=3.0.0", + "moment": "^2.10.2" + } + }, + "node_modules/chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "peerDependencies": { + "chart.js": ">=3.0.0" + } + }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -29031,6 +29056,15 @@ "react-dom": ">=16.3.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.0.1.tgz", + "integrity": "sha512-u38C9OxynlNCBp+79grgXRs7DSJ9w8FuQ5/HO5FbYBbri8HSZW+9SWgjVshLkbXBfXnMGWakbHEtvN0nL2UG7Q==", + "peerDependencies": { + "chart.js": "^4.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dnd": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", @@ -38973,6 +39007,9 @@ "bunyan": "^1.8.15", "cache-flow": "^1.7.4", "celebrate": "^10.0.1", + "chart.js": "^4.0.1", + "chartjs-adapter-moment": "^1.0.1", + "chartjs-plugin-datalabels": "^2.2.0", "classnames": "^2.2.6", "codemirror": "~5.33.0", "connect-redis": "^3.1.0", @@ -39048,6 +39085,7 @@ "qrcode": "^1.4.4", "react": "^17.0.2", "react-bootstrap": "^0.33.1", + "react-chartjs-2": "^5.0.1", "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dom": "^17.0.2", @@ -49552,6 +49590,9 @@ "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "chai-exclude": "^2.0.3", + "chart.js": "^4.0.1", + "chartjs-adapter-moment": "^1.0.1", + "chartjs-plugin-datalabels": "^2.2.0", "cheerio": "^1.0.0-rc.3", "classnames": "^2.2.6", "codemirror": "~5.33.0", @@ -49664,6 +49705,7 @@ "qrcode": "^1.4.4", "react": "^17.0.2", "react-bootstrap": "^0.33.1", + "react-chartjs-2": "^5.0.1", "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dom": "^17.0.2", @@ -55239,6 +55281,23 @@ "is-regex": "^1.0.3" } }, + "chart.js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.0.1.tgz", + "integrity": "sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==" + }, + "chartjs-adapter-moment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", + "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", + "requires": {} + }, + "chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "requires": {} + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -68640,6 +68699,12 @@ "warning": "^3.0.0" } }, + "react-chartjs-2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.0.1.tgz", + "integrity": "sha512-u38C9OxynlNCBp+79grgXRs7DSJ9w8FuQ5/HO5FbYBbri8HSZW+9SWgjVshLkbXBfXnMGWakbHEtvN0nL2UG7Q==", + "requires": {} + }, "react-dnd": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", diff --git a/services/web/app/src/Features/SplitTests/SplitTestManager.js b/services/web/app/src/Features/SplitTests/SplitTestManager.js index e963dcf673..0e4966ed26 100644 --- a/services/web/app/src/Features/SplitTests/SplitTestManager.js +++ b/services/web/app/src/Features/SplitTests/SplitTestManager.js @@ -7,7 +7,7 @@ const ALPHA_PHASE = 'alpha' const BETA_PHASE = 'beta' const RELEASE_PHASE = 'release' -async function getSplitTests({ name, activeOnly, archivedOnly }) { +async function getSplitTests({ name, phase, type, activeOnly, archivedOnly }) { const filters = {} if (name && name !== '') { filters.name = { $regex: _.escapeRegExp(name) } @@ -15,6 +15,32 @@ async function getSplitTests({ name, activeOnly, archivedOnly }) { if (activeOnly) { filters.$where = 'this.versions[this.versions.length - 1].active === true' } + if (type === 'split-test') { + const query = + 'this.versions[this.versions.length - 1].analyticsEnabled === true' + if (filters.$where) { + filters.$where += `&& ${query}` + } else { + filters.$where = query + } + } + if (type === 'gradual-rollout') { + const query = + 'this.versions[this.versions.length - 1].analyticsEnabled === false' + if (filters.$where) { + filters.$where += `&& ${query}` + } else { + filters.$where = query + } + } + if (['alpha', 'beta', 'release'].includes(phase)) { + const query = `this.versions[this.versions.length - 1].phase === "${phase}"` + if (filters.$where) { + filters.$where += `&& ${query}` + } else { + filters.$where = query + } + } if (archivedOnly) { filters.archived = true } else { @@ -205,6 +231,7 @@ async function revertToPreviousVersion(name, versionNumber) { } const previousVersionCopy = previousVersion.toObject() previousVersionCopy.versionNumber = lastVersion.versionNumber + 1 + previousVersionCopy.createdAt = new Date() splitTest.versions.push(previousVersionCopy) return _saveSplitTest(splitTest) } @@ -218,11 +245,7 @@ async function archive(name) { throw new OError(`Split test with ID '${name}' is already archived`) } splitTest.archived = true - const previousVersionCopy = - SplitTestUtils.getCurrentVersion(splitTest).toObject() - previousVersionCopy.versionNumber += 1 - previousVersionCopy.active = false - splitTest.versions.push(previousVersionCopy) + splitTest.archivedAt = new Date() return _saveSplitTest(splitTest) } diff --git a/services/web/app/src/models/SplitTest.js b/services/web/app/src/models/SplitTest.js index 03c82d2e86..426aa8392b 100644 --- a/services/web/app/src/models/SplitTest.js +++ b/services/web/app/src/models/SplitTest.js @@ -140,6 +140,10 @@ const SplitTestSchema = new Schema({ type: Boolean, required: false, }, + archivedAt: { + type: Date, + required: false, + }, badgeInfo: { type: BadgeInfoSchema, required: false, diff --git a/services/web/app/views/layout/navbar-marketing.pug b/services/web/app/views/layout/navbar-marketing.pug index 8f0a440448..76c5878bf8 100644 --- a/services/web/app/views/layout/navbar-marketing.pug +++ b/services/web/app/views/layout/navbar-marketing.pug @@ -58,7 +58,7 @@ nav.navbar.navbar-default.navbar-main a(href=settings.adminUrl) Switch to Admin if canDisplaySplitTestMenu li - a(href="/admin/split-test") Manage Split Tests + a(href="/admin/split-test") Manage Feature Flags if canDisplaySurveyMenu li a(href="/admin/survey") Manage Surveys diff --git a/services/web/app/views/layout/navbar.pug b/services/web/app/views/layout/navbar.pug index 577b9ccda3..27631e4268 100644 --- a/services/web/app/views/layout/navbar.pug +++ b/services/web/app/views/layout/navbar.pug @@ -40,7 +40,7 @@ nav.navbar.navbar-default.navbar-main a(href=settings.adminUrl) Switch to Admin if canDisplaySplitTestMenu li - a(href="/admin/split-test") Manage Split Tests + a(href="/admin/split-test") Manage Feature Flags if canDisplaySurveyMenu li a(href="/admin/survey") Manage Surveys diff --git a/services/web/frontend/stylesheets/modules/admin-panel.less b/services/web/frontend/stylesheets/modules/admin-panel.less index 1c4f4924c0..a6bb721d79 100644 --- a/services/web/frontend/stylesheets/modules/admin-panel.less +++ b/services/web/frontend/stylesheets/modules/admin-panel.less @@ -1,4 +1,18 @@ +@import '../../../frontend/stylesheets/core/variables.less'; + .admin-panel-pagination { display: flex; justify-content: center; } + +.phase-badge { + display: inline-block; + border-radius: @badge-border-radius; + font-size: @font-size-small; + font-weight: @badge-font-weight; + line-height: @badge-line-height; + color: @badge-color; + white-space: nowrap; + text-align: center; + padding: 3px 7px; +} diff --git a/services/web/local-dev.env b/services/web/local-dev.env index 373c72674d..1c7505b05d 100644 --- a/services/web/local-dev.env +++ b/services/web/local-dev.env @@ -3,7 +3,6 @@ QUEUES_REDIS_HOST=localhost PUBSUB_REDIS_HOST=localhost RATELIMITER_REDIS_HOST=localhost GCLOUD_2_REDIS_HOST=localhost -QUEUES_REDIS_HOST=localhost POSTGRES_HOST=localhost MONGO_URL=mongodb://localhost/sharelatex SHARELATEX_LDAP_URL=ldap://localhost:22389 diff --git a/services/web/package.json b/services/web/package.json index eb6790055a..46e4a4e393 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -136,6 +136,9 @@ "bunyan": "^1.8.15", "cache-flow": "^1.7.4", "celebrate": "^10.0.1", + "chart.js": "^4.0.1", + "chartjs-adapter-moment": "^1.0.1", + "chartjs-plugin-datalabels": "^2.2.0", "classnames": "^2.2.6", "codemirror": "~5.33.0", "connect-redis": "^3.1.0", @@ -211,6 +214,7 @@ "qrcode": "^1.4.4", "react": "^17.0.2", "react-bootstrap": "^0.33.1", + "react-chartjs-2": "^5.0.1", "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dom": "^17.0.2",