You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by ge...@apache.org on 2019/05/30 14:56:23 UTC
[netbeans-tools] branch master updated: adding Synergy sources,
from 3rd donation
This is an automated email from the ASF dual-hosted git repository.
geertjan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans-tools.git
The following commit(s) were added to refs/heads/master by this push:
new c34a844 adding Synergy sources, from 3rd donation
c34a844 is described below
commit c34a844fb9b3c8f2ffaece85c1ffc5e78033bd7d
Author: Geertjan Wielenga <ge...@apache.org>
AuthorDate: Thu May 30 16:56:10 2019 +0200
adding Synergy sources, from 3rd donation
---
.DS_Store | Bin 0 -> 8196 bytes
synergy/Gruntfile.js | 156 +
synergy/README.md | 61 +
synergy/build.xml | 87 +
synergy/client/app/admin.html | 134 +
synergy/client/app/css/custom.css | 477 ++
synergy/client/app/css/docs.css | 1001 +++
synergy/client/app/css/min/custom.css | 1 +
synergy/client/app/css/min/docs.css | 1 +
synergy/client/app/favicon.ico | Bin 0 -> 1150 bytes
synergy/client/app/img/ajax-loader.gif | Bin 0 -> 3169 bytes
synergy/client/app/img/blue.png | Bin 0 -> 1731 bytes
.../client/app/img/bs-docs-bootstrap-features.png | Bin 0 -> 5872 bytes
.../client/app/img/bs-docs-masthead-pattern.png | Bin 0 -> 14189 bytes
synergy/client/app/img/clock.png | Bin 0 -> 1558 bytes
.../client/app/img/glyphicons-halflings-white.png | Bin 0 -> 12371 bytes
synergy/client/app/img/glyphicons-halflings.png | Bin 0 -> 17141 bytes
synergy/client/app/img/grey.png | Bin 0 -> 1693 bytes
synergy/client/app/img/grid-baseline-20px.png | Bin 0 -> 102 bytes
synergy/client/app/img/nb.gif | Bin 0 -> 2207 bytes
synergy/client/app/img/red.png | Bin 0 -> 1665 bytes
synergy/client/app/img/user.png | Bin 0 -> 691 bytes
synergy/client/app/img/yellow.png | Bin 0 -> 1730 bytes
synergy/client/app/index.html | 149 +
synergy/client/app/index2.html | 147 +
synergy/client/app/index_dev.html | 179 +
synergy/client/app/js/app.js | 260 +
synergy/client/app/js/configuration.js | 393 ++
synergy/client/app/js/controllers.js | 7283 ++++++++++++++++++++
synergy/client/app/js/excl/inspect.js | 169 +
synergy/client/app/js/excl/inspectx.js | 169 +
synergy/client/app/js/excl/wgxpath.install.js | 49 +
synergy/client/app/js/exts.js | 7 +
synergy/client/app/js/factories.js | 3332 +++++++++
synergy/client/app/js/filters.js | 37 +
synergy/client/app/js/handlers.js | 749 ++
synergy/client/app/js/legacy/polyfills.js | 77 +
synergy/client/app/js/login/app.js | 29 +
synergy/client/app/js/min/synergy.js | 3 +
synergy/client/app/js/models.js | 296 +
synergy/client/app/js/utils.js | 322 +
synergy/client/app/login.html | 84 +
synergy/client/app/opensearch.xml | 11 +
.../app/partials/admin/create/assignment.html | 58 +
.../partials/admin/create/matrix_assignment.html | 72 +
synergy/client/app/partials/admin/create/run.html | 65 +
.../client/app/partials/admin/create/tribe.html | 58 +
synergy/client/app/partials/admin/create/user.html | 132 +
.../client/app/partials/admin/edit/project.html | 74 +
synergy/client/app/partials/admin/edit/run.html | 111 +
synergy/client/app/partials/admin/edit/user.html | 130 +
.../client/app/partials/admin/view/database.html | 39 +
synergy/client/app/partials/admin/view/home.html | 7 +
synergy/client/app/partials/admin/view/log.html | 14 +
.../client/app/partials/admin/view/platforms.html | 70 +
.../client/app/partials/admin/view/projects.html | 49 +
.../client/app/partials/admin/view/reviews.html | 61 +
synergy/client/app/partials/admin/view/runs.html | 64 +
.../client/app/partials/admin/view/settings.html | 39 +
synergy/client/app/partials/admin/view/tribes.html | 42 +
synergy/client/app/partials/admin/view/users.html | 60 +
.../client/app/partials/admin/view/versions.html | 69 +
.../client/app/partials/directives/loginBt.html | 17 +
.../app/partials/public/create/assignment.html | 71 +
.../partials/public/create/assignment_tribe.html | 73 +
.../client/app/partials/public/create/case.html | 63 +
.../app/partials/public/create/specification.html | 46 +
.../client/app/partials/public/create/suite.html | 61 +
synergy/client/app/partials/public/edit/case.html | 149 +
.../client/app/partials/public/edit/review.html | 66 +
.../app/partials/public/edit/specification.html | 150 +
synergy/client/app/partials/public/edit/suite.html | 69 +
synergy/client/app/partials/public/edit/tribe.html | 70 +
synergy/client/app/partials/public/login/home.html | 11 +
synergy/client/app/partials/public/view/about.html | 9 +
.../app/partials/public/view/assignment.html | 115 +
.../partials/public/view/assignment_comments.html | 64 +
.../client/app/partials/public/view/calendar.html | 14 +
synergy/client/app/partials/public/view/case.html | 116 +
.../client/app/partials/public/view/favorites.html | 20 +
synergy/client/app/partials/public/view/home.html | 57 +
synergy/client/app/partials/public/view/label.html | 17 +
synergy/client/app/partials/public/view/login.html | 28 +
.../client/app/partials/public/view/profile.html | 175 +
.../client/app/partials/public/view/recover.html | 17 +
.../client/app/partials/public/view/register.html | 52 +
.../client/app/partials/public/view/review.html | 26 +
.../client/app/partials/public/view/revisions.html | 46 +
.../app/partials/public/view/run_coverage.html | 57 +
.../app/partials/public/view/run_view_1.html | 305 +
.../app/partials/public/view/run_view_2.html | 292 +
.../app/partials/public/view/run_view_3.html | 91 +
synergy/client/app/partials/public/view/runs.html | 55 +
.../client/app/partials/public/view/search.html | 17 +
.../partials/public/view/specification_view_1.html | 142 +
.../partials/public/view/specification_view_2.html | 228 +
.../client/app/partials/public/view/specpool.html | 32 +
.../app/partials/public/view/statistics.html | 215 +
synergy/client/app/partials/public/view/suite.html | 96 +
synergy/client/app/partials/public/view/tribe.html | 89 +
.../client/app/partials/public/view/tribes.html | 11 +
synergy/client/config/testacular-e2e.conf.js | 18 +
synergy/client/config/testacular.conf.js | 15 +
synergy/client/scripts/e2e-test.bat | 10 +
synergy/client/scripts/e2e-test.sh | 9 +
synergy/client/scripts/test-server.bat | 19 +
synergy/client/scripts/test-server.sh | 14 +
synergy/client/scripts/test.bat | 14 +
synergy/client/scripts/test.sh | 9 +
synergy/client/scripts/watchr.rb | 19 +
synergy/client/scripts/web-server.js | 243 +
synergy/client/test/app/synergy.js | 9 +
synergy/client/test/app/test.html | 143 +
synergy/client/test/e2e/config.js | 41 +
synergy/client/test/e2e/homeSpec.js | 56 +
synergy/client/test/e2e/runsSpec.js | 91 +
synergy/client/test/e2e/specificationsSpec.js | 60 +
synergy/manual/.htaccess | 5 +
synergy/misc/database_schema/schema_inserts.sql | 565 ++
synergy/misc/migration | 50 +
synergy/nbproject/customs.json | 27 +
synergy/nbproject/project.properties | 34 +
synergy/nbproject/project.xml | 9 +
synergy/package.json | 16 +
synergy/server/api/.htaccess | 10 +
synergy/server/api/_dummy2.php | 13 +
synergy/server/api/about.php | 9 +
synergy/server/api/assignment.php | 245 +
synergy/server/api/assignment_bugs.php | 66 +
synergy/server/api/assignment_comments.php | 38 +
synergy/server/api/assignment_exists.php | 22 +
synergy/server/api/assignments.php | 65 +
synergy/server/api/attachment.php | 103 +
synergy/server/api/attachments.php | 33 +
synergy/server/api/case.php | 138 +
synergy/server/api/cases.php | 23 +
synergy/server/api/comments.php | 11 +
synergy/server/api/configuration.php | 36 +
synergy/server/api/db.php | 39 +
synergy/server/api/events.php | 19 +
synergy/server/api/favorite.php | 34 +
synergy/server/api/favorites.php | 33 +
synergy/server/api/image.php | 77 +
synergy/server/api/images.php | 32 +
synergy/server/api/import.php | 29 +
synergy/server/api/issue.php | 70 +
synergy/server/api/job.php | 50 +
synergy/server/api/label.php | 88 +
synergy/server/api/labels.php | 87 +
synergy/server/api/log.php | 30 +
synergy/server/api/login.php | 71 +
synergy/server/api/platform.php | 80 +
synergy/server/api/platforms.php | 49 +
synergy/server/api/products.php | 16 +
synergy/server/api/profile_img.php | 73 +
synergy/server/api/project.php | 84 +
synergy/server/api/projects.php | 22 +
synergy/server/api/proxy.php | 36 +
synergy/server/api/refresh.php | 41 +
synergy/server/api/register.php | 42 +
synergy/server/api/review.php | 39 +
synergy/server/api/review_assignment.php | 199 +
synergy/server/api/reviews.php | 49 +
synergy/server/api/revisions.php | 41 +
synergy/server/api/revison.php | 13 +
synergy/server/api/run.php | 186 +
synergy/server/api/run_attachment.php | 99 +
synergy/server/api/run_notifications.php | 29 +
synergy/server/api/run_notifications_auto.php | 30 +
synergy/server/api/run_notifications_auto_test.php | 30 +
synergy/server/api/run_specifications.php | 37 +
synergy/server/api/run_tribes.php | 46 +
synergy/server/api/runs.php | 71 +
synergy/server/api/sanitizer.php | 20 +
synergy/server/api/search.php | 25 +
synergy/server/api/specification.php | 251 +
synergy/server/api/specification_length.php | 27 +
synergy/server/api/specification_request.php | 50 +
synergy/server/api/specifications.php | 113 +
synergy/server/api/statistics.php | 38 +
synergy/server/api/statistics_filter.php | 39 +
synergy/server/api/suite.php | 196 +
synergy/server/api/test.php | 11 +
synergy/server/api/tribe.php | 169 +
synergy/server/api/tribe_assignments.php | 44 +
synergy/server/api/tribe_specification.php | 53 +
synergy/server/api/tribes.php | 69 +
synergy/server/api/user.php | 150 +
synergy/server/api/users.php | 69 +
synergy/server/api/version.php | 69 +
synergy/server/api/versions.json | 230 +
synergy/server/api/versions.php | 36 +
synergy/server/api/versions_dev.php | 30 +
synergy/server/api/versions_dev_1.php | 42 +
synergy/server/app/Synergy.php | 173 +
synergy/server/cache/ical.ics | 15 +
.../server/controller/AssignmentCommentsCtrl.php | 145 +
synergy/server/controller/AssignmentCtrl.php | 171 +
synergy/server/controller/AttachmentCtrl.php | 142 +
synergy/server/controller/CalendarCtrl.php | 126 +
synergy/server/controller/CaseCtrl.php | 396 ++
synergy/server/controller/CommentsCtrl.php | 27 +
synergy/server/controller/ConfigurationCtrl.php | 47 +
synergy/server/controller/DatabaseCtrl.php | 55 +
synergy/server/controller/ExtensionCtrl.php | 185 +
synergy/server/controller/LabelCtrl.php | 40 +
synergy/server/controller/Mediator.php | 81 +
synergy/server/controller/NotificationCtrl.php | 267 +
synergy/server/controller/PlatformCtrl.php | 78 +
synergy/server/controller/ProjectCtrl.php | 74 +
synergy/server/controller/RegistrationCtrl.php | 57 +
synergy/server/controller/ReviewCtrl.php | 197 +
synergy/server/controller/ReviewsCtrl.php | 61 +
synergy/server/controller/RevisionCtrl.php | 112 +
synergy/server/controller/RunCtrl.php | 1078 +++
synergy/server/controller/RunNotificationCtrl.php | 235 +
synergy/server/controller/SearchCtrl.php | 70 +
synergy/server/controller/SessionRefreshCtrl.php | 32 +
synergy/server/controller/SpecRelationCtrl.php | 104 +
synergy/server/controller/SpecificationCtrl.php | 572 ++
.../server/controller/SpecificationLockCtrl.php | 100 +
synergy/server/controller/StatisticsCtrl.php | 115 +
synergy/server/controller/SuiteCtrl.php | 182 +
synergy/server/controller/TribeCtrl.php | 449 ++
synergy/server/controller/UserCtrl.php | 409 ++
synergy/server/controller/VersionCtrl.php | 102 +
synergy/server/data/sample.json | 1 +
synergy/server/db/AssignmentCommentsDAO.php | 107 +
synergy/server/db/AssignmentDAO.php | 130 +
synergy/server/db/AttachmentDAO.php | 215 +
synergy/server/db/Bugzilla_DAO.php | 55 +
synergy/server/db/CaseDAO.php | 661 ++
synergy/server/db/CiDAO.php | 99 +
synergy/server/db/CommentsDAO.php | 28 +
synergy/server/db/ConfigurationDAO.php | 80 +
synergy/server/db/DB_DAO.php | 122 +
synergy/server/db/IssueDAO.php | 96 +
synergy/server/db/LabelDAO.php | 59 +
synergy/server/db/LockDAO.php | 105 +
synergy/server/db/PlatformDAO.php | 142 +
synergy/server/db/ProductDAO.php | 42 +
synergy/server/db/ProjectDAO.php | 239 +
synergy/server/db/RegistrationDAO.php | 35 +
synergy/server/db/RemovalDAO.php | 71 +
synergy/server/db/ReviewDAO.php | 388 ++
synergy/server/db/ReviewsDAO.php | 93 +
synergy/server/db/RevisionDAO.php | 72 +
synergy/server/db/RunDAO.php | 1062 +++
synergy/server/db/RunNotificationDAO.php | 77 +
synergy/server/db/SessionDAO.php | 114 +
synergy/server/db/SessionRefreshDAO.php | 59 +
synergy/server/db/SpecificationDAO.php | 816 +++
synergy/server/db/SpecificationRelationDAO.php | 30 +
synergy/server/db/StructureDAO.php | 54 +
synergy/server/db/SuiteDAO.php | 383 +
synergy/server/db/TribeDAO.php | 398 ++
synergy/server/db/TribeExtensionDAO.php | 98 +
synergy/server/db/UserDAO.php | 515 ++
synergy/server/db/VersionDAO.php | 179 +
synergy/server/db/structure.sql | 470 ++
synergy/server/errors/.htaccess | 9 +
synergy/server/errors/errors.log | 0
.../ContinuousIntegrationExtension.php | 68 +
.../extensions/specification/ProjectExtension.php | 75 +
.../specification/RemovalRequestExtension.php | 62 +
.../extensions/specification/TestExtension.php | 42 +
.../server/extensions/suite/ProjectExtension.php | 57 +
.../extensions/testcase/ProjectExtension.php | 57 +
.../tribe/TribeSpecificationExtension.php | 78 +
synergy/server/interfaces/EmailProvider.php | 28 +
synergy/server/interfaces/ExtensionInterface.php | 48 +
synergy/server/interfaces/IssueProvider.php | 21 +
synergy/server/interfaces/LoggerProvider.php | 29 +
synergy/server/interfaces/Observer.php | 22 +
synergy/server/interfaces/ReviewImporter.php | 17 +
synergy/server/interfaces/SessionProvider.php | 76 +
synergy/server/interfaces/TutorialProvider.php | 19 +
synergy/server/misc/HTTP.php | 129 +
synergy/server/misc/Util.php | 202 +
synergy/server/model/Action.php | 32 +
synergy/server/model/AssignmentComment.php | 64 +
synergy/server/model/AssignmentComments.php | 23 +
synergy/server/model/AssignmentDuration.php | 20 +
synergy/server/model/AssignmentProgress.php | 32 +
synergy/server/model/BlobSpecification.php | 47 +
synergy/server/model/BlobTestCase.php | 33 +
synergy/server/model/BlobTestSuite.php | 45 +
synergy/server/model/Bug.php | 52 +
synergy/server/model/CachedSession.php | 21 +
synergy/server/model/CommentType.php | 20 +
synergy/server/model/CurlRequestResult.php | 18 +
synergy/server/model/Email.php | 28 +
synergy/server/model/Job.php | 34 +
synergy/server/model/Label.php | 26 +
synergy/server/model/LabelResult.php | 36 +
synergy/server/model/Membership.php | 27 +
synergy/server/model/Platform.php | 71 +
synergy/server/model/Product.php | 36 +
synergy/server/model/Revision.php | 28 +
synergy/server/model/RunAttachment.php | 55 +
synergy/server/model/SearchResult.php | 33 +
synergy/server/model/Session.php | 67 +
synergy/server/model/Setting.php | 48 +
synergy/server/model/Specification.php | 243 +
synergy/server/model/SpecificationAttachment.php | 69 +
synergy/server/model/SpecificationListItem.php | 54 +
synergy/server/model/SpecificationSkeleton.php | 26 +
.../server/model/SpecificationsSimpleNameList.php | 80 +
synergy/server/model/StatRecord.php | 25 +
synergy/server/model/Suite.php | 176 +
synergy/server/model/SuiteSkeleton.php | 27 +
synergy/server/model/TestAssignment.php | 197 +
synergy/server/model/TestCase.php | 155 +
synergy/server/model/TestCaseImage.php | 88 +
synergy/server/model/TestCaseSkeleton.php | 31 +
synergy/server/model/TestRun.php | 117 +
synergy/server/model/TestRunList.php | 34 +
synergy/server/model/TestRunStatistics.php | 30 +
synergy/server/model/Tribe.php | 98 +
synergy/server/model/User.php | 94 +
synergy/server/model/UserStatistics.php | 86 +
synergy/server/model/UsersResult.php | 41 +
synergy/server/model/Version.php | 110 +
.../assignment/rest/AssignmentLineResource.php | 36 +
.../assignment/rest/AssignmentListItemResource.php | 73 +
.../rest/AssignmentStatisticsResource.php | 40 +
.../rest/RichAssignmentListItemResource.php | 43 +
synergy/server/model/bug/rest/BugResource.php | 44 +
.../server/model/comment/rest/CommentResource.php | 56 +
.../model/comment/rest/CommentTypeResource.php | 30 +
.../model/comment/rest/CommentsListResource.php | 24 +
.../model/exception/AssignmentCommentException.php | 25 +
.../exception/AssignmentConflictException.php | 25 +
.../server/model/exception/AssignmentException.php | 26 +
.../exception/AssignmentSecurityException.php | 26 +
.../exception/CorruptedAssignmentException.php | 25 +
.../model/exception/CurlRequestException.php | 26 +
.../server/model/exception/GeneralException.php | 31 +
.../exception/SpecificationDuplicateException.php | 26 +
synergy/server/model/exception/UserException.php | 32 +
synergy/server/model/image/rest/ImageResource.php | 38 +
synergy/server/model/label/rest/LabelResource.php | 30 +
.../model/label/rest/LabelSearchResource.php | 31 +
.../model/platform/rest/PlatformResource.php | 34 +
.../server/model/product/rest/ProductResource.php | 30 +
synergy/server/model/project/Project.php | 80 +
synergy/server/model/project/ProjectListItem.php | 20 +
.../server/model/project/rest/ProjectResource.php | 39 +
synergy/server/model/registration/Registration.php | 25 +
synergy/server/model/review/ReviewAssignment.php | 140 +
synergy/server/model/review/ReviewComment.php | 50 +
synergy/server/model/review/ReviewPage.php | 45 +
.../model/review/rest/ReviewStatisticsResource.php | 77 +
.../revision/rest/RevisionListItemResource.php | 32 +
.../model/revision/rest/RevisionResource.php | 34 +
synergy/server/model/run/RunNotification.php | 93 +
.../model/run/rest/RunAttachmentResource.php | 36 +
synergy/server/model/run/rest/RunBlobsResource.php | 41 +
.../server/model/run/rest/RunListItemResource.php | 53 +
synergy/server/model/run/rest/RunResource.php | 42 +
.../run/rest/RunSpecificationsListResource.php | 23 +
.../model/run/rest/RunStatisticsResource.php | 37 +
.../model/search/rest/SearchResultResource.php | 17 +
synergy/server/model/session/RefreshSession.php | 20 +
.../server/model/setting/rest/SettingResource.php | 32 +
.../model/specification/ext/RemovalRequest.php | 22 +
.../rest/SpecificationAttachmentResource.php | 34 +
.../rest/SpecificationListItemResource.php | 44 +
.../specification/rest/SpecificationResource.php | 74 +
.../statistics/rest/StatisticsLineResource.php | 34 +
synergy/server/model/suite/rest/SuiteResource.php | 41 +
.../model/suite/rest/SuiteSnippetResource.php | 57 +
.../model/testcase/rest/CaseListItemResource.php | 34 +
.../server/model/testcase/rest/CaseResource.php | 44 +
.../model/testcase/rest/CaseSnippetResource.php | 56 +
.../model/tribe/rest/TribeListItemResource.php | 40 +
synergy/server/model/tribe/rest/TribeResource.php | 48 +
.../server/model/user/rest/MembershipResource.php | 32 +
.../model/user/rest/UserListItemResource.php | 38 +
synergy/server/model/user/rest/UserResource.php | 63 +
.../server/model/version/rest/VersionResource.php | 34 +
synergy/server/observer/SpecificationObserver.php | 53 +
synergy/server/providers/EmailCtrl.php | 39 +
synergy/server/providers/IssueCtrl.php | 70 +
synergy/server/providers/IssueOtherCtrl.php | 61 +
synergy/server/providers/LoggerCtrl.php | 38 +
synergy/server/providers/ProductCtrl.php | 36 +
synergy/server/providers/ReviewImporterCtrl.php | 77 +
synergy/server/providers/SessionCtrl.php | 134 +
.../server/providers/SessionCtrl_Production.php | 224 +
synergy/server/providers/SessionCtrl_SSO.php | 153 +
synergy/server/providers/TutorialFormatter.php | 44 +
synergy/server/setup/conf.php | 134 +
synergy/server_tests/bootstrap.php | 81 +
synergy/server_tests/configuration.xml | 8 +
synergy/server_tests/server/DatabaseSetup.php | 88 +
.../server/controller/AttachmentCtrlTest.php | 47 +
.../server/controller/CaseCtrlTest.php | 237 +
.../server/controller/CaseExtensionCtrlTest.php | 51 +
.../server/controller/LabelCtrlTest.php | 43 +
.../server/controller/PlatformCtrlTest.php | 66 +
.../server_tests/server/controller/RunCtrlTest.php | 288 +
.../server/controller/SearchCtrlTest.php | 46 +
.../server/controller/SpecificationCtrlTest.php | 187 +
.../controller/SpecificationExtensionCtrlTest.php | 37 +
.../server/controller/StatisticsCtrlTest.php | 34 +
.../server/controller/SuiteCtrlTest.php | 137 +
.../server/controller/SuiteExtensionCtrlTest.php | 52 +
.../server/controller/TribeCtrlTest.php | 106 +
.../server/controller/TribeExtensionCtrlTest.php | 48 +
.../server/controller/UserCtrlTest.php | 187 +
.../server/controller/VersionCtrlTest.php | 83 +
synergy/server_tests/server/db/FixtureTestCase.php | 115 +
.../server/db/SpecificationDAOTest.php | 35 +
synergy/server_tests/server/db/fixtures/dump.xml | 1315 ++++
.../server/db/fixtures/specification.xml | 120 +
synergy/synergy.wiki/.htaccess | 5 +
417 files changed, 49668 insertions(+)
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..0d9c46e
Binary files /dev/null and b/.DS_Store differ
diff --git a/synergy/Gruntfile.js b/synergy/Gruntfile.js
new file mode 100755
index 0000000..ee3c46d
--- /dev/null
+++ b/synergy/Gruntfile.js
@@ -0,0 +1,156 @@
+var execSync = require("child_process").execSync;
+var svnRevision = execSync("svn info -r 'HEAD' | grep Revision: | awk -F' ' '{print $2}'").toString();
+svnRevision = svnRevision.replace(/\s/g, "");
+module.exports = function (grunt) {
+ var timestamp = Date.now();
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ replace: {
+ index: {
+ src: ['client/app/index.html'],
+ dest: ['client/app/index.html'],
+ replacements: [{
+ from: /synergy\.js\?v=[0-9]+"/,
+ to: 'synergy.js?v=' + timestamp + "\""
+ }, {
+ from: /polyfills\.js\?v=[0-9]+"/,
+ to: 'polyfills.js?v=' + timestamp + "\""
+ }]
+ },
+ version: {
+ src: ['client/app/js/configuration.js'],
+ dest: ['client/app/js/configuration.js'],
+ replacements: [{
+ from: /this\.version = "1\.0\.[0-9]+";/,
+ to: 'this.version = "1.0.' + svnRevision + '";'
+ }]
+ },
+ index2: {
+ src: ['client/app/index2.html'],
+ dest: ['client/app/index2.html'],
+ replacements: [{
+ from: /synergy\.js\?v=[0-9]+"/,
+ to: 'synergy.js?v=' + timestamp + "\""
+ }, {
+ from: /polyfills\.js\?v=[0-9]+"/,
+ to: 'polyfills.js?v=' + timestamp + "\""
+ }]
+ },
+ appjs: {
+ src: ['client/app/js/app.js'],
+ dest: ['client/app/js/app.js'],
+ replacements: [{
+ from: /\.html\?v=[0-9]+/g,
+ to: '.html?v=' + timestamp
+ }]
+ },
+ css: {
+ src: ['client/app/index.html'],
+ dest: ['client/app/index.html'],
+ replacements: [{//custom.css?v=7
+ from: /custom\.css\?v=[0-9]+"/,
+ to: 'custom.css?v=' + timestamp + "\""
+ }]
+ },
+ css2: {
+ src: ['client/app/index2.html'],
+ dest: ['client/app/index2.html'],
+ replacements: [{//custom.css?v=7
+ from: /custom\.css\?v=[0-9]+"/,
+ to: 'custom.css?v=' + timestamp + "\""
+ }]
+ },
+ testSynergyPartials: {
+ src: ['client/test/app/synergy.js'],
+ dest: ['client/test/app/synergy.js'],
+ replacements: [{//custom.css?v=7
+ from: /partials\//g,
+ to: '../../app/partials/'
+ }]
+ },
+ testSynergyResources: {
+ src: ['client/test/app/synergy.js'],
+ dest: ['client/test/app/synergy.js'],
+ replacements: [{//custom.css?v=7
+ from: /\.\.\/\.\.\/server\/api/g,
+ to: '../../../server/api'
+ }]
+ },
+ testReplaceDatabaseName: {
+ src: ['server/setup/conf.php'],
+ dest: ['server/setup/conf.php'],
+ replacements: [{//custom.css?v=7
+ from: /define\('DHOST', 'mysql:host=localhost;dbname=synergy;charset=UTF8'\);/g,
+ to: 'define(\'DHOST\', \'mysql:host=localhost;dbname=synergy_test;charset=UTF8\');'
+ }]
+ },
+ replaceDatabaseName: {
+ src: ['server/setup/conf.php'],
+ dest: ['server/setup/conf.php'],
+ replacements: [{//custom.css?v=7
+ from: /define\('DHOST', 'mysql:host=localhost;dbname=synergy_test;charset=UTF8'\);/g,
+ to: 'define(\'DHOST\', \'mysql:host=localhost;dbname=synergy;charset=UTF8\');'
+ }]
+ }
+ },
+ cssmin: {
+ combine: {
+ files: {
+ 'client/app/css/min/custom.css': ['client/app/css/custom.css'],
+ 'client/app/css/min/docs.css': ['client/app/css/docs.css'],
+ 'client/app/css/min/bootstrap.css': ['client/app/css/bootstrap.css'],
+ 'client/app/css/min/bootstrap-responsive.css': ['client/app/css/bootstrap-responsive.css']
+ }
+ }
+ },
+ uglify: {
+ options: {
+ mangle: false,
+ banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
+ },
+ buildSynergy: {
+ files: {
+ 'client/app/js/min/synergy.js': ['client/app/js/*.js']
+ }
+ },
+ buildTestSynergy: {
+ files: {
+ 'client/test/app/synergy.js': ['client/app/js/min/synergy.js']
+ }
+ }
+ },
+ jshint: {
+ "client": {
+ "src": ["client/app/js/*.js"],
+ options: {
+ "reporterOutput": "",
+ "force": true,
+ "strict": true,
+ "curly": true,
+ "eqnull": true,
+// "unused": true,
+ "eqeqeq": true,
+ "undef": true,
+// "camelcase": true,
+ "forin": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "expr": true,
+ "quotmark": "double",
+ "trailing": true,
+// "globalstrict": true,//
+ globals: {difflib: true, diffview: true, "$": true, angular: true, window: true, google: true},
+ reporter: require('jshint-stylish'),
+ '-W097': true // use strict in function form warning
+ }
+ }
+ }
+ });
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
+ grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-text-replace');
+ grunt.registerTask('default', ['jshint', 'replace:index', 'replace:version', 'replace:index2', 'replace:appjs', 'replace:css', 'replace:css2', 'uglify:buildSynergy', 'cssmin', 'replace:replaceDatabaseName']);
+ grunt.registerTask('testBuild', ['default', 'uglify:buildTestSynergy', 'replace:testSynergyPartials', 'replace:testReplaceDatabaseName', 'replace:testSynergyResources']);
+};
diff --git a/synergy/README.md b/synergy/README.md
new file mode 100755
index 0000000..1700908
--- /dev/null
+++ b/synergy/README.md
@@ -0,0 +1,61 @@
+
+# How to install Synergy
+#### 1. Install Apache HTTP server, PHP and MYSQL
+Install Apache with PHP and MySQL:
+https://www.vultr.com/docs/how-to-install-apache-mysql-and-php-on-ubuntu-16-04
+
+Next enable .htaccess using steps from
+https://linode.com/docs/web-servers/apache/how-to-set-up-htaccess-on-apache/
+
+#### 2. Get Synergy sources
+assuming apache's document root is in `/var/www/html`, in terminal:
+
+```
+cd /var/www/html
+svn checkout https://svn.netbeans.org/svn/opensynergy~source-code-repository
+mv opensynergy~source-code-repository synergy
+cd synergy
+```
+Now you are in the synergy main directory.
+
+#### 3. Create database
+this will also create default project, default version and a new user with username `import` and password `import` - this user is administrator, in terminal:
+
+```
+mysql -u root -p < server/db/structure.sql
+```
+
+#### 4. If needed change DB connection details
+update file `server/setup/conf.php` from line 18 which contains database connection details to match your database credentials
+
+```
+define('DHOST', 'mysql:host=localhost;dbname=synergy;charset=UTF8');
+define('DUSER', 'user');
+define('DPASS', 'password');
+define('DB', 'synergy');
+define('DBHOST', 'localhost');
+```
+
+#### 5. Now Synergy should be almost up and running
+you can try it in your browser `http://localhost/synergy/client/app/`
+
+#### 6. Setup directories for attachments and images
+By default, images will be stored at `/var/www/media/` and attachments at `/var/www/att/`. Either make sure these 2 paths exist and
+that apache server can write there, or you'll need to change the setting to point to paths which meet the criteria.
+
+The change can be done via Synergy UI in `Administration -> Server` setting where you can find 2 setting fields: `ATTACHMENT_PATH`
+and `IMAGE_PATH`. Note also the `IMAGE_BASE` which must correlate with `IMAGE_BASE`, for instance if `IMAGE_PATH` is `/var/www/html/images`,
+then `IMAGE_BASE` would be `http://localhost/images/` . Please note that these 2 directories must have proper permissions - for local setup 777 is fine
+
+#### 7. Setting up error log file
+In terminal, navigate to the Synergy directory and open file `.htaccess`, line 33 starts with `php_value error_log` and
+it ends with path to error log file. Synergy will print error logs there in case something is wrong.
+
+Either create the default path and file (`/var/www/synergy/server/errors/errors.log`) or change the path in `.htaccess` file to your path.
+Again make sure it is possible to write to file - setting 777 permissions should be on local deployment.
+
+#### 8. Allow Synergy to store static data from test runs
+In terminal in synergy directory, execute following:
+```
+chmod 777 -R server/data
+```
diff --git a/synergy/build.xml b/synergy/build.xml
new file mode 100755
index 0000000..d774c13
--- /dev/null
+++ b/synergy/build.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--to run:
+
+cd synergy
+ant -Dyui_compressor=/pathTo/yuicompressor-2.4.7.jar
+-->
+<project name="Synergy build tool" default="build" basedir=".">
+ <property name="yui_compressor" value="/home/vriha/javalib/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar"/>
+ <property name="client_root" value="client/app"/>
+ <property name="min_css_folder" value="${basedir}/${client_root}/css/min"/>
+ <property name="css_folder" value="${basedir}/${client_root}/css"/>
+ <property name="min_js_folder" value="${basedir}/${client_root}/js/min"/>
+ <property name="js_folder" value="${basedir}/${client_root}/js"/>
+ <property name="index" value="${basedir}/${client_root}/index.html"/>
+ <property name="index_dev" value="${basedir}/${client_root}/index_dev.html"/>
+ <tstamp>
+ <format property="timestamp" pattern="mmss" />
+ </tstamp>
+
+ <target name="browser_cache">
+ <echo message="increasing cache parameters"/>
+ <exec executable="sed">
+ <arg line="-i s/synergy\.js?v=[0-9]*/synergy\.js?v=${timestamp}/ client/app/index.html"/>
+ </exec>
+ <exec executable="sed">
+ <arg line="-i s/synergy\.js?v=[0-9]*/synergy\.js?v=${timestamp}/ client/app/index2.html"/>
+ </exec>
+ <exec executable="sed">
+ <arg line="-i s/.html'/.html?v=${timestamp}'/ client/app/js/app.js"/>
+ </exec>
+ </target>
+ <target name="revert_browser_cache">
+ <echo message="removing partials cache parameters"/>
+ <exec executable="sed">
+ <arg line="-i s/.html?v=[0-9]*'/.html'/ client/app/js/app.js"/>
+ </exec>
+ </target>
+
+ <target name="clean_css">
+ <delete>
+ <fileset dir="${min_css_folder}" includes="**/*.css"/>
+ </delete>
+ <echo message="Removing CSS files"/>
+ </target>
+ <target name="min_css" depends="clean_css">
+ <echo message="minifying CSS files"/>
+ <apply executable="java">
+ <arg value="-jar"/>
+ <arg value="${yui_compressor}"/>
+ <arg line="--charset utf-8"/>
+ <arg line="--nomunge"/>
+ <srcfile/>
+ <arg line="-o"/>
+ <targetfile/>
+ <fileset dir="${css_folder}" includes="*.css"/>
+ <mapper type="regexp" from="(.*)" to="${min_css_folder}/\0" />
+ </apply>
+ </target>
+ <target name="clean_js">
+ <delete>
+ <fileset dir="${min_js_folder}" includes="**/*.js"/>
+ </delete>
+ <echo message="Removing JS files"/>
+ </target>
+ <target name="min_js" depends="clean_js">
+ <echo message="minifying JS files"/>
+ <apply executable="java">
+ <arg value="-jar"/>
+ <arg value="${yui_compressor}"/>
+ <arg line="--charset utf-8"/>
+ <arg line="--nomunge"/>
+ <srcfile/>
+ <arg line="-o"/>
+ <targetfile/>
+ <fileset dir="${js_folder}" includes="*.js"/>
+ <mapper type="regexp" from="(.*)" to="${min_js_folder}/\0" />
+ </apply>
+ </target>
+ <target name="combine_js" depends="min_js">
+ <echo message="combining js files"/>
+ <concat destfile="${min_js_folder}/synergy.js">
+ <fileset dir="${min_js_folder}" includes="**/*.js"/>
+ </concat>
+ </target>
+ <target name="build" depends="min_css, browser_cache, min_js, combine_js, revert_browser_cache">
+ </target>
+</project>
\ No newline at end of file
diff --git a/synergy/client/app/admin.html b/synergy/client/app/admin.html
new file mode 100755
index 0000000..9f5d6ea
--- /dev/null
+++ b/synergy/client/app/admin.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html lang="en" >
+ <head>
+ <meta charset="utf-8">
+ <title>Synergy - Administration</title>
+ <script src="js/bootstrap/jquery.js"></script>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+ <link href="css/bootstrap.css" rel="stylesheet">
+ <link href="css/custom.css" rel="stylesheet">
+ <style type="text/css">
+ body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+ }
+ </style>
+ <link href="css/bootstrap-responsive.css" rel="stylesheet">
+ <script src="js/libs/json/json2-min.js"></script>
+ <script src="js/configuration.js"></script>
+ <script src="js/utils.js"></script>
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+
+ </head>
+
+ <body ng-app="synergy_admin">
+
+ <div class="navbar navbar-inverse navbar-fixed-top" >
+
+
+
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Synergy - administration</a>
+ <div class="nav-collapse collapse" id="synergy_mainbar">
+ <ul class="nav">
+ <li class="active"><a href="index.html">Home</a></li>
+ <li><a href="#/runs">Test Runs</a></li>
+ <li><a href="#/versions">Versions</a></li>
+ <li><a href="#/platforms">Platforms</a></li>
+ <li><a href="#/users">Users</a></li>
+ <li><a href="#/tribes">Tribes</a></li>
+
+ </ul>
+ <div ng-controller="SessionCtrl" id="synergy_session">
+ <form class="navbar-form pull-right" id="synergy_login_form" >
+ <input class="span2" type="text" placeholder="Email" ng-model="username" >
+ <input class="span2" type="password" placeholder="Password"ng-model="password" >
+ <button type="submit" class="btn" ng-click="login();">Sign in</button>
+ </form>
+ <div id="synergy_usermenu" style="display:none">
+ <ul class="nav pull-right"><li class="dropdown"><a href="#" class="dropdown-toggle btn-primary" data-toggle="dropdown" id="usermenu_user" style="color: white">USER <b class="caret"></b></a>
+ <ul class="dropdown-menu"><li><a href="index.html#/user">Me</a></li><li><a href="#logout" ng-click="logout();">Logout</a></li>
+ </ul></li></ul>
+ </div>
+ </div>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+
+ <div class="container-fluid">
+
+
+ <div class="row-fluid">
+
+ </div>
+ <div class="row-fluid" >
+
+
+
+ <div class="span12">
+
+
+
+ <div ng-view></div>
+ </div>
+ <div class="span2"></div>
+ </div>
+ <!-- Main hero unit for a primary marketing message or call to action -->
+
+ <!-- Example row of columns -->
+
+
+ <hr>
+
+ <footer>
+ <p>© Company 2012</p>
+ </footer>
+
+ </div> <!-- /container -->
+ <div class="modal fade hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3 id="myModalLabel">Modal header</h3>
+ </div>
+ <div class="modal-body" id="modal-body">
+ <p>One fine body 2…</p>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" data-dismiss="modal">OK</button>
+ </div>
+ </div>
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+
+ <script src="js/bootstrap/bootstrap-transition.js"></script>
+ <script src="js/bootstrap/bootstrap-alert.js"></script>
+ <script src="js/bootstrap/bootstrap-modal.js"></script>
+ <script src="js/bootstrap/bootstrap-dropdown.js"></script>
+ <script src="js/bootstrap/bootstrap-scrollspy.js"></script>
+ <script src="js/bootstrap/bootstrap-tab.js"></script>
+ <script src="js/bootstrap/bootstrap-tooltip.js"></script>
+ <script src="js/bootstrap/bootstrap-popover.js"></script>
+ <script src="js/bootstrap/bootstrap-button.js"></script>
+ <script src="js/bootstrap/bootstrap-collapse.js"></script>
+ <script src="js/bootstrap/bootstrap-carousel.js"></script>
+ <script src="js/bootstrap/bootstrap-typeahead.js"></script>
+ <script src="js/admin_controllers.js"></script>
+ <script src="lib/angular/angular.js"></script>
+ <script src="js/admin_app.js"></script>
+
+ </body>
+</html>
diff --git a/synergy/client/app/css/custom.css b/synergy/client/app/css/custom.css
new file mode 100755
index 0000000..0076569
--- /dev/null
+++ b/synergy/client/app/css/custom.css
@@ -0,0 +1,477 @@
+/* Custom keywords/labels for test cases */
+.label-fails, .badge-fails {
+ background-color: #333;
+}
+
+.label-sanity, .badge-sanity {
+ background-color: #B94A48;
+}
+
+.label-obsolete, .badge-obsolete {
+ background-color: #999;
+}
+
+
+/*custom labels end*/
+
+.dropbox{
+ border: 4px dashed rgba(0, 0, 0, 0.2);
+ text-align: center;
+ color: #999999;
+}
+.dropbox.hover{
+ border: 4px dashed #08C;
+}
+
+.table tbody tr.finished td, .table tbody tr td.finished, .finished, .row-passed td {
+ background-color: #dff0d8 !important;
+}
+
+.table tbody tr.unfinished td, .table tbody tr td.unfinished, .unfinished {
+ background-color: #fcf8e3 !important;
+}
+
+.table tbody tr.warning td, .table tbody tr td.warning, .warning, .row-failed td {
+ background-color: #fbdcbc !important;
+}
+
+.table tbody tr.pending td, .table tbody tr td.pending, .row-passed_with_issues td {
+ background-color: #d9edf7 !important;
+}
+
+.table-hover tbody tr.finished:hover td, .table tbody tr td.finished:hover, .row-passed:hover td{
+ background-color: #d0e9c6 !important;
+}
+
+.table-hover tbody tr.unfinished:hover td, .table tbody tr td.unfinished:hover {
+ background-color: #faf2cc !important;
+}
+
+.table-hover tbody tr.warning:hover td, .table tbody tr td.warning:hover, .row-failed:hover td {
+ background-color: #f8c592 !important;
+}
+
+.table-hover tbody tr.pending:hover td, .table tbody tr td.pending:hover, .row-passed_with_issues:hover td {
+ background-color: #c4e3f3 !important;
+}
+
+.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+.ui-timepicker-div dl { text-align: left; }
+.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
+.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
+.ui-timepicker-div td { font-size: 90%; }
+.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
+
+.ui-timepicker-rtl{ direction: rtl; }
+.ui-timepicker-rtl dl { text-align: right; }
+.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; }
+
+div.thumbnail > img:hover {
+ cursor: pointer;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ border: 1px solid #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+[ng\:cloak], [ng-cloak], .ng-cloak {
+ display: none !important;
+}
+
+.well, .well-large, .well-small, .table-bordered, .breadcrumb{
+ box-shadow: 2px 2px 2px #ccc;
+}
+#usermenu_user{
+ background-color: #006dcc !important;
+ background-image: none;
+ text-shadow: none;
+}
+
+.likelink{
+ color: #08c;
+ text-decoration: none;
+ cursor: pointer;
+}
+.likelink:hover{
+ color: #005580;
+ text-decoration: underline;
+ cursor: pointer;
+}
+/*.result pre{
+ background-color:white;
+ border:none;
+}*/
+
+.brand_busy{
+ color: #999;
+ -webkit-animation-name: glow;
+ -webkit-animation-duration: 1s;
+ -webkit-animation-iteration-count: infinite;
+ -ms-animation-name: glow;
+ -ms-animation-duration: 1s;
+ -ms-animation-iteration-count: infinite;
+ -moz-animation-name: glow;
+ -moz-animation-duration: 1s;
+ -moz-animation-iteration-count: infinite;
+ -o-animation-name: glow;
+ -o-animation-duration: 1s;
+ -o-animation-iteration-count: infinite;
+ animation-name: glow;
+ animation-duration: 1s;
+ animation-iteration-count: infinite;
+}
+@-webkit-keyframes glow{
+ 0%{
+ color: #999;
+ }
+ 10%{
+ color: #9197a4;
+ }
+ 20%{
+ color: #7182aa;
+ }
+ 30%{
+ color: #516db0;
+ }
+ 40%{
+ color: #3057b6;
+ }
+ 50%{
+ color: #1042bc;
+ }
+ 60%{
+ color: #3057b6;
+ }
+ 70%{
+ color: #516db0;
+ }
+ 80%{
+ color: #7182aa;
+ }
+ 90%{
+ color: #9197a4;
+ }
+ 100%{
+ color: #999;
+ }
+}
+@-moz-keyframes glow{
+ 0%{
+ color: #999;
+ }
+ 10%{
+ color: #9197a4;
+ }
+ 20%{
+ color: #7182aa;
+ }
+ 30%{
+ color: #516db0;
+ }
+ 40%{
+ color: #3057b6;
+ }
+ 50%{
+ color: #1042bc;
+ }
+ 60%{
+ color: #3057b6;
+ }
+ 70%{
+ color: #516db0;
+ }
+ 80%{
+ color: #7182aa;
+ }
+ 90%{
+ color: #9197a4;
+ }
+ 100%{
+ color: #999;
+ }
+}
+@keyframes glow{
+ 0%{
+ color: #999;
+ }
+ 10%{
+ color: #9197a4;
+ }
+ 20%{
+ color: #7182aa;
+ }
+ 30%{
+ color: #516db0;
+ }
+ 40%{
+ color: #3057b6;
+ }
+ 50%{
+ color: #1042bc;
+ }
+ 60%{
+ color: #3057b6;
+ }
+ 70%{
+ color: #516db0;
+ }
+ 80%{
+ color: #7182aa;
+ }
+ 90%{
+ color: #9197a4;
+ }
+ 100%{
+ color: #999;
+ }
+}
+
+@-ms-keyframes glow{
+ 0%{
+ color: #999;
+ }
+ 10%{
+ color: #9197a4;
+ }
+ 20%{
+ color: #7182aa;
+ }
+ 30%{
+ color: #516db0;
+ }
+ 40%{
+ color: #3057b6;
+ }
+ 50%{
+ color: #1042bc;
+ }
+ 60%{
+ color: #3057b6;
+ }
+ 70%{
+ color: #516db0;
+ }
+ 80%{
+ color: #7182aa;
+ }
+ 90%{
+ color: #9197a4;
+ }
+ 100%{
+ color: #999;
+ }
+}
+
+@-o-keyframes glow{
+ 0%{
+ color: #999;
+ }
+ 10%{
+ color: #9197a4;
+ }
+ 20%{
+ color: #7182aa;
+ }
+ 30%{
+ color: #516db0;
+ }
+ 40%{
+ color: #3057b6;
+ }
+ 50%{
+ color: #1042bc;
+ }
+ 60%{
+ color: #3057b6;
+ }
+ 70%{
+ color: #516db0;
+ }
+ 80%{
+ color: #7182aa;
+ }
+ 90%{
+ color: #9197a4;
+ }
+ 100%{
+ color: #999;
+ }
+}
+
+.obsolete1{
+ color: gray;
+ font-style: italic;
+}
+.active0{
+ color: gray;
+ font-style: italic;
+}
+
+
+/* Styling for the ngProgress itself */
+#ngProgress {
+ margin: 0;
+ padding: 0;
+ z-index: 99998;
+ background-color: green;
+ color: green;
+ box-shadow: 0 0 10px 0;
+ /* Inherits the font color */
+
+ height: 2px;
+ opacity: 0;
+ /* Add CSS3 styles for transition smoothing */
+ -webkit-transition: all 0.5s ease-in-out;
+ -moz-transition: all 0.5s ease-in-out;
+ -o-transition: all 0.5s ease-in-out;
+ transition: all 0.5s ease-in-out;
+}
+
+/* Styling for the ngProgress-container */
+#ngProgress-container {
+ position: fixed;
+ margin: 0;
+ padding: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 99999;
+}
+
+#userCaret{
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+}
+@media (max-width: 979px) {
+ body {
+ padding-top: 0px;
+ }
+}
+
+.bugs-error{
+ background-color: #f2dede;
+}
+.bugs-warning{
+ background-color: #fcf8e3;
+}
+.bugs-success{
+ background-color: #dff0d8;
+}
+.bugs-orange{
+ background-color: #ffcc66;
+}
+
+.userfalse, .specificationfalse, .platformfalse, .tribefalse, .passedfalse, .testedfalse, .timefalse, .testersfalse, .ratiofalse, .ttimefalse, .complfalse, .userfalse, .productivityfalse,
+.reviewerfalse,.reviewerRfalse,.complRfalse,.startedRfalse,.commentsRfalse, .weightRfalse,.timeRfalse {
+ opacity: 0.5;
+}
+.usertrue, .specificationtrue, .platformtrue, .tribetrue, .passedtrue, .testedtrue, .timetrue, .testerstrue, .ratiotrue, .ttimetrue, .compltrue, .usertrue, .productivitytrue,
+.reviewertrue,.reviewerRtrue,.complRtrue,.startedRtrue,.commentsRtrue, .weightRtrue,.timeRtrue{
+ opacity: 1;
+}
+.label-frozen{
+ background-color: #FF7878;
+}
+.small_cells td{
+ padding: 5px;
+}
+.casefailed{
+ background-color: #fbdcbc;
+}
+.casepassed{
+ background-color: #dff0d8;
+}
+.caseskipped{
+ background-color: #f5f5f5;
+}
+#cal .fc-header-title h2 {
+ font-size: .9em;
+ white-space: normal !important;
+}
+#cal .fc-view-month .fc-event, .fc-view-agendaWeek .fc-event {
+ font-size: 0;
+ overflow: hidden;
+ height: 2px;
+}
+#cal .fc-view-agendaWeek .fc-event-vert {
+ font-size: 0;
+ overflow: hidden;
+ width: 2px !important;
+}
+#cal .fc-agenda-axis {
+ width: 20px !important;
+ font-size: .7em;
+}
+
+#cal .fc-button-content {
+ padding: 0;
+}
+#typeahead_search{
+ display: block;
+ visibility: visible
+}
+.owner_formerUser{
+ color: #888;
+}
+
+.clockIcon{
+ width: 1.5em;
+}
+.label-aborted, .badge-aborted,.label-ABORTED, .badge-ABORTED, .label-disabled, .badge-disabled,.label-DISABLED, .badge-DISABLED {
+ background-color: #999;
+}
+.label-failed, .badge-failed,.label-FAILED, .badge-FAILED, .label-failure, .badge-failure,.label-FAILURE, .badge-FAILURE {
+ background-color: #B94A48;
+}
+.label-unstable, .badge-unstable,.label-UNSTABLE, .badge-UNSTABLE {
+ background-color: #f89406;
+}
+.label-SUCCESS, .badge-SUCCESS{
+ background-color: #468847;
+}
+.jobContainer{
+ margin: 0.5em 0;
+}
+.label-big{
+ font-size: 1.3em;
+ margin: 0.5em;
+}
+.grey{
+ color: #777;
+}
+.specVisiblefalse, .platformVisiblefalse{
+ color: #aaa;
+}
+.bold{
+ font-weight: bold;
+}
+.x-controls{
+ font-size: 14px;
+ margin: 1em 0 2em 0;
+}
+.x-controls label, .x-controls input{
+ display: inline !important;
+ margin: 0 !important;
+}
+.x-controls .pull-left{
+ margin: 0 1em 0 0;
+}
+.x-controls .pull-left:nth-child(2){
+ margin: 0 1em 0 2em;
+}
+.x-controls .select2-search-choice-close:after {
+ content: 'x';
+ width: 14px;
+ height: 14px;
+ color: #000;
+ font-size: 14px;
+}
+.x-labels{
+ width: 30vw;
+}
\ No newline at end of file
diff --git a/synergy/client/app/css/docs.css b/synergy/client/app/css/docs.css
new file mode 100755
index 0000000..cd592f9
--- /dev/null
+++ b/synergy/client/app/css/docs.css
@@ -0,0 +1,1001 @@
+/* Add additional stylesheets below
+-------------------------------------------------- */
+/*
+ Bootstrap's documentation styles
+ Special styles for presenting Bootstrap's documentation and examples
+*/
+
+
+
+/* Body and structure
+-------------------------------------------------- */
+
+body {
+ position: relative;
+ padding-top: 40px;
+}
+
+/* Code in headings */
+h3 code {
+ font-size: 14px;
+ font-weight: normal;
+}
+
+
+
+/* Tweak navbar brand link to be super sleek
+-------------------------------------------------- */
+
+body > .navbar {
+ font-size: 13px;
+}
+
+/* Change the docs' brand */
+body > .navbar .brand {
+ padding-right: 0;
+ padding-left: 0;
+ margin-left: 20px;
+ float: right;
+ font-weight: bold;
+ color: #000;
+ text-shadow: 0 1px 0 rgba(255,255,255,.1), 0 0 30px rgba(255,255,255,.125);
+ -webkit-transition: all .2s linear;
+ -moz-transition: all .2s linear;
+ transition: all .2s linear;
+}
+body > .navbar .brand:hover {
+ text-decoration: none;
+ text-shadow: 0 1px 0 rgba(255,255,255,.1), 0 0 30px rgba(255,255,255,.4);
+}
+
+
+/* Sections
+-------------------------------------------------- */
+
+/* padding for in-page bookmarks and fixed navbar */
+section {
+ padding-top: 30px;
+}
+section > .page-header,
+section > .lead {
+ color: #5a5a5a;
+}
+section > ul li {
+ margin-bottom: 5px;
+}
+
+/* Separators (hr) */
+.bs-docs-separator {
+ margin: 40px 0 39px;
+}
+
+/* Faded out hr */
+hr.soften {
+ height: 1px;
+ margin: 70px 0;
+ background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
+ background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
+ background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
+ background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
+ border: 0;
+}
+
+
+
+/* Jumbotrons
+-------------------------------------------------- */
+
+/* Base class
+------------------------- */
+.jumbotron {
+ position: relative;
+ padding: 40px 0;
+ color: #fff;
+ text-align: center;
+ text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075);
+ background: #020031; /* Old browsers */
+ background: -moz-linear-gradient(45deg, #020031 0%, #6d3353 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#020031), color-stop(100%,#6d3353)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(45deg, #020031 0%,#6d3353 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(45deg, #020031 0%,#6d3353 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(45deg, #020031 0%,#6d3353 100%); /* IE10+ */
+ background: linear-gradient(45deg, #020031 0%,#6d3353 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
+ -webkit-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
+ -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
+ box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
+}
+.jumbotron h1 {
+ font-size: 80px;
+ font-weight: bold;
+ letter-spacing: -1px;
+ line-height: 1;
+}
+.jumbotron p {
+ font-size: 24px;
+ font-weight: 300;
+ line-height: 30px;
+ margin-bottom: 30px;
+}
+
+/* Link styles (used on .masthead-links as well) */
+.jumbotron a {
+ color: #fff;
+ color: rgba(255,255,255,.5);
+ -webkit-transition: all .2s ease-in-out;
+ -moz-transition: all .2s ease-in-out;
+ transition: all .2s ease-in-out;
+}
+.jumbotron a:hover {
+ color: #fff;
+ text-shadow: 0 0 10px rgba(255,255,255,.25);
+}
+
+/* Download button */
+.masthead .btn {
+ padding: 14px 24px;
+ font-size: 24px;
+ font-weight: 200;
+ color: #fff; /* redeclare to override the `.jumbotron a` */
+ border: 0;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+ -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+}
+.masthead .btn:hover {
+ -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+ -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 5px rgba(0,0,0,.25);
+}
+.masthead .btn:active {
+ -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.1);
+ -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.1);
+ box-shadow: inset 0 2px 4px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.1);
+}
+
+
+/* Pattern overlay
+------------------------- */
+.jumbotron .container {
+ position: relative;
+ z-index: 2;
+}
+.jumbotron:after {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: url(../img/bs-docs-masthead-pattern.png) repeat center center;
+ opacity: .4;
+}
+
+/* Masthead (docs home)
+------------------------- */
+.masthead {
+ padding: 70px 0 80px;
+ margin-bottom: 0;
+ color: #fff;
+}
+.masthead h1 {
+ font-size: 120px;
+ line-height: 1;
+ letter-spacing: -2px;
+}
+.masthead p {
+ font-size: 40px;
+ font-weight: 200;
+ line-height: 1.25;
+}
+
+/* Textual links in masthead */
+.masthead-links {
+ margin: 0;
+ list-style: none;
+}
+.masthead-links li {
+ display: inline;
+ padding: 0 10px;
+ color: rgba(255,255,255,.25);
+}
+
+/* Social proof buttons from GitHub & Twitter */
+.bs-docs-social {
+ padding: 15px 0;
+ text-align: center;
+ background-color: #f5f5f5;
+ border-top: 1px solid #fff;
+ border-bottom: 1px solid #ddd;
+}
+
+/* Quick links on Home */
+.bs-docs-social-buttons {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+}
+.bs-docs-social-buttons li {
+ display: inline-block;
+ padding: 5px 8px;
+ line-height: 1;
+ *display: inline;
+ *zoom: 1;
+}
+
+/* Subhead (other pages)
+------------------------- */
+.subhead {
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+}
+.subhead h1 {
+ font-size: 60px;
+}
+.subhead p {
+ margin-bottom: 20px;
+}
+.subhead .navbar {
+ display: none;
+}
+
+
+
+/* Marketing section of Overview
+-------------------------------------------------- */
+
+.marketing {
+ text-align: center;
+ color: #5a5a5a;
+}
+.marketing h1 {
+ margin: 60px 0 10px;
+ font-size: 60px;
+ font-weight: 200;
+ line-height: 1;
+ letter-spacing: -1px;
+}
+.marketing h2 {
+ font-weight: 200;
+ margin-bottom: 5px;
+}
+.marketing p {
+ font-size: 16px;
+ line-height: 1.5;
+}
+.marketing .marketing-byline {
+ margin-bottom: 40px;
+ font-size: 20px;
+ font-weight: 300;
+ line-height: 25px;
+ color: #999;
+}
+.marketing img {
+ display: block;
+ margin: 0 auto 30px;
+}
+
+
+
+/* Footer
+-------------------------------------------------- */
+
+.footer {
+ padding: 70px 0;
+ margin-top: 70px;
+ border-top: 1px solid #e5e5e5;
+ background-color: #f5f5f5;
+}
+.footer p {
+ margin-bottom: 0;
+ color: #777;
+}
+.footer-links {
+ margin: 10px 0;
+}
+.footer-links li {
+ display: inline;
+ margin-right: 10px;
+}
+
+
+
+/* Special grid styles
+-------------------------------------------------- */
+
+.show-grid {
+ margin-top: 10px;
+ margin-bottom: 20px;
+}
+.show-grid [class*="span"] {
+ background-color: #eee;
+ text-align: center;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ min-height: 40px;
+ line-height: 40px;
+}
+.show-grid:hover [class*="span"] {
+ background: #ddd;
+}
+.show-grid .show-grid {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.show-grid .show-grid [class*="span"] {
+ background-color: #ccc;
+}
+
+
+
+/* Mini layout previews
+-------------------------------------------------- */
+.mini-layout {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+ -moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
+ box-shadow: 0 1px 2px rgba(0,0,0,.075);
+}
+.mini-layout,
+.mini-layout .mini-layout-body,
+.mini-layout.fluid .mini-layout-sidebar {
+ height: 300px;
+}
+.mini-layout {
+ margin-bottom: 20px;
+ padding: 9px;
+}
+.mini-layout div {
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.mini-layout .mini-layout-body {
+ background-color: #dceaf4;
+ margin: 0 auto;
+ width: 70%;
+}
+.mini-layout.fluid .mini-layout-sidebar,
+.mini-layout.fluid .mini-layout-header,
+.mini-layout.fluid .mini-layout-body {
+ float: left;
+}
+.mini-layout.fluid .mini-layout-sidebar {
+ background-color: #bbd8e9;
+ width: 20%;
+}
+.mini-layout.fluid .mini-layout-body {
+ width: 77.5%;
+ margin-left: 2.5%;
+}
+
+
+
+/* Download page
+-------------------------------------------------- */
+
+.download .page-header {
+ margin-top: 36px;
+}
+.page-header .toggle-all {
+ margin-top: 5px;
+}
+
+/* Space out h3s when following a section */
+.download h3 {
+ margin-bottom: 5px;
+}
+.download-builder input + h3,
+.download-builder .checkbox + h3 {
+ margin-top: 9px;
+}
+
+/* Fields for variables */
+.download-builder input[type=text] {
+ margin-bottom: 9px;
+ font-family: Menlo, Monaco, "Courier New", monospace;
+ font-size: 12px;
+ color: #d14;
+}
+.download-builder input[type=text]:focus {
+ background-color: #fff;
+}
+
+/* Custom, larger checkbox labels */
+.download .checkbox {
+ padding: 6px 10px 6px 25px;
+ font-size: 13px;
+ line-height: 18px;
+ color: #555;
+ background-color: #f9f9f9;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+.download .checkbox:hover {
+ color: #333;
+ background-color: #f5f5f5;
+}
+.download .checkbox small {
+ font-size: 12px;
+ color: #777;
+}
+
+/* Variables section */
+#variables label {
+ margin-bottom: 0;
+}
+
+/* Giant download button */
+.download-btn {
+ margin: 36px 0 108px;
+}
+#download p,
+#download h4 {
+ max-width: 50%;
+ margin: 0 auto;
+ color: #999;
+ text-align: center;
+}
+#download h4 {
+ margin-bottom: 0;
+}
+#download p {
+ margin-bottom: 18px;
+}
+.download-btn .btn {
+ display: block;
+ width: auto;
+ padding: 19px 24px;
+ margin-bottom: 27px;
+ font-size: 30px;
+ line-height: 1;
+ text-align: center;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+
+
+/* Misc
+-------------------------------------------------- */
+
+/* Make tables spaced out a bit more */
+h2 + table,
+h3 + table,
+h4 + table,
+h2 + .row {
+ margin-top: 5px;
+}
+
+/* Example sites showcase */
+.example-sites {
+ xmargin-left: 20px;
+}
+.example-sites img {
+ max-width: 100%;
+ margin: 0 auto;
+}
+
+.scrollspy-example {
+ height: 200px;
+ overflow: auto;
+ position: relative;
+}
+
+
+/* Fake the :focus state to demo it */
+.focused {
+ border-color: rgba(82,168,236,.8);
+ -webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
+ -moz-box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
+ box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
+ outline: 0;
+}
+
+/* For input sizes, make them display block */
+.docs-input-sizes select,
+.docs-input-sizes input[type=text] {
+ display: block;
+ margin-bottom: 9px;
+}
+
+/* Icons
+------------------------- */
+.the-icons {
+ margin-left: 0;
+ list-style: none;
+}
+.the-icons li {
+ float: left;
+ width: 25%;
+ line-height: 25px;
+}
+.the-icons i:hover {
+ background-color: rgba(255,0,0,.25);
+}
+
+/* Example page
+------------------------- */
+.bootstrap-examples p {
+ font-size: 13px;
+ line-height: 18px;
+}
+.bootstrap-examples .thumbnail {
+ margin-bottom: 9px;
+ background-color: #fff;
+}
+
+
+
+/* Bootstrap code examples
+-------------------------------------------------- */
+
+/* Base class */
+.bs-docs-example {
+ position: relative;
+ margin: 15px 0;
+ padding: 39px 19px 14px;
+ *padding-top: 19px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+/* Echo out a label for the example */
+.bs-docs-example:after {
+ content: "Example";
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ background-color: #f5f5f5;
+ border: 1px solid #ddd;
+ color: #9da0a4;
+ -webkit-border-radius: 4px 0 4px 0;
+ -moz-border-radius: 4px 0 4px 0;
+ border-radius: 4px 0 4px 0;
+}
+
+/* Remove spacing between an example and it's code */
+.bs-docs-example + .prettyprint {
+ margin-top: -20px;
+ padding-top: 15px;
+}
+
+/* Tweak examples
+------------------------- */
+.bs-docs-example > p:last-child {
+ margin-bottom: 0;
+}
+.bs-docs-example .table,
+.bs-docs-example .progress,
+.bs-docs-example .well,
+.bs-docs-example .alert,
+.bs-docs-example .hero-unit,
+.bs-docs-example .pagination,
+.bs-docs-example .navbar,
+.bs-docs-example > .nav,
+.bs-docs-example blockquote {
+ margin-bottom: 5px;
+}
+.bs-docs-example .pagination {
+ margin-top: 0;
+}
+.bs-navbar-top-example,
+.bs-navbar-bottom-example {
+ z-index: 1;
+ padding: 0;
+ height: 90px;
+ overflow: hidden; /* cut the drop shadows off */
+}
+.bs-navbar-top-example .navbar-fixed-top,
+.bs-navbar-bottom-example .navbar-fixed-bottom {
+ margin-left: 0;
+ margin-right: 0;
+}
+.bs-navbar-top-example {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+.bs-navbar-top-example:after {
+ top: auto;
+ bottom: -1px;
+ -webkit-border-radius: 0 4px 0 4px;
+ -moz-border-radius: 0 4px 0 4px;
+ border-radius: 0 4px 0 4px;
+}
+.bs-navbar-bottom-example {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.bs-navbar-bottom-example .navbar {
+ margin-bottom: 0;
+}
+form.bs-docs-example {
+ padding-bottom: 19px;
+}
+
+/* Images */
+.bs-docs-example-images img {
+ margin: 10px;
+ display: inline-block;
+}
+
+/* Tooltips */
+.bs-docs-tooltip-examples {
+ text-align: center;
+ margin: 0 0 10px;
+ list-style: none;
+}
+.bs-docs-tooltip-examples li {
+ display: inline;
+ padding: 0 10px;
+}
+
+/* Popovers */
+.bs-docs-example-popover {
+ padding-bottom: 24px;
+ background-color: #f9f9f9;
+}
+.bs-docs-example-popover .popover {
+ position: relative;
+ display: block;
+ float: left;
+ width: 260px;
+ margin: 20px;
+}
+
+
+
+/* Responsive docs
+-------------------------------------------------- */
+
+/* Utility classes table
+------------------------- */
+.responsive-utilities th small {
+ display: block;
+ font-weight: normal;
+ color: #999;
+}
+.responsive-utilities tbody th {
+ font-weight: normal;
+}
+.responsive-utilities td {
+ text-align: center;
+}
+.responsive-utilities td.is-visible {
+ color: #468847;
+ background-color: #dff0d8 !important;
+}
+.responsive-utilities td.is-hidden {
+ color: #ccc;
+ background-color: #f9f9f9 !important;
+}
+
+/* Responsive tests
+------------------------- */
+.responsive-utilities-test {
+ margin-top: 5px;
+ margin-left: 0;
+ list-style: none;
+ overflow: hidden; /* clear floats */
+}
+.responsive-utilities-test li {
+ position: relative;
+ float: left;
+ width: 25%;
+ height: 43px;
+ font-size: 14px;
+ font-weight: bold;
+ line-height: 43px;
+ color: #999;
+ text-align: center;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.responsive-utilities-test li + li {
+ margin-left: 10px;
+}
+.responsive-utilities-test span {
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: -1px;
+ bottom: -1px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.responsive-utilities-test span {
+ color: #468847;
+ background-color: #dff0d8;
+ border: 1px solid #d6e9c6;
+}
+
+
+
+/* Sidenav for Docs
+-------------------------------------------------- */
+
+.bs-docs-sidenav {
+ width: 228px;
+ margin: 30px 0 0;
+ padding: 0;
+ background-color: #fff;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 1px 4px rgba(0,0,0,.065);
+ -moz-box-shadow: 0 1px 4px rgba(0,0,0,.065);
+ box-shadow: 0 1px 4px rgba(0,0,0,.065);
+}
+.bs-docs-sidenav > li > a {
+ display: block;
+ *width: 190px;
+ margin: 0 0 -1px;
+ padding: 8px 14px;
+ border: 1px solid #e5e5e5;
+}
+.bs-docs-sidenav > li:first-child > a {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+.bs-docs-sidenav > li:last-child > a {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+.bs-docs-sidenav > .active > a {
+ position: relative;
+ z-index: 2;
+ padding: 9px 15px;
+ border: 0;
+ text-shadow: 0 1px 0 rgba(0,0,0,.15);
+ -webkit-box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+ -moz-box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+ box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+}
+/* Chevrons */
+.bs-docs-sidenav .icon-chevron-right {
+ float: right;
+ margin-top: 2px;
+ margin-right: -6px;
+ opacity: .25;
+}
+.bs-docs-sidenav > li > a:hover {
+ background-color: #f5f5f5;
+}
+.bs-docs-sidenav a:hover .icon-chevron-right {
+ opacity: .5;
+}
+.bs-docs-sidenav .active .icon-chevron-right,
+.bs-docs-sidenav .active a:hover .icon-chevron-right {
+ background-image: url(../../img/glyphicons-halflings-white.png);
+ opacity: 1;
+}
+.bs-docs-sidenav.affix {
+ top: 40px;
+}
+.bs-docs-sidenav.affix-bottom {
+ position: absolute;
+ top: auto;
+ bottom: 270px;
+}
+
+
+
+
+/* Responsive
+-------------------------------------------------- */
+
+/* Desktop large
+------------------------- */
+@media (min-width: 1200px) {
+ .bs-docs-container {
+ max-width: 970px;
+ }
+ .bs-docs-sidenav {
+ width: 258px;
+ }
+}
+
+/* Desktop
+------------------------- */
+@media (max-width: 980px) {
+ /* Unfloat brand */
+ body > .navbar-fixed-top .brand {
+ float: left;
+ margin-left: 0;
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+ /* Inline-block quick links for more spacing */
+ .quick-links li {
+ display: inline-block;
+ margin: 5px;
+ }
+
+ /* When affixed, space properly */
+ .bs-docs-sidenav {
+ top: 0;
+ margin-top: 30px;
+ margin-right: 0;
+ }
+}
+
+/* Tablet to desktop
+------------------------- */
+@media (min-width: 768px) and (max-width: 980px) {
+ /* Remove any padding from the body */
+ body {
+ padding-top: 0;
+ }
+ /* Widen masthead and social buttons to fill body padding */
+ .jumbotron {
+ margin-top: -20px; /* Offset bottom margin on .navbar */
+ }
+ /* Adjust sidenav width */
+ .bs-docs-sidenav {
+ width: 166px;
+ margin-top: 20px;
+ }
+ .bs-docs-sidenav.affix {
+ top: 0;
+ }
+}
+
+/* Tablet
+------------------------- */
+@media (max-width: 767px) {
+ /* Remove any padding from the body */
+ body {
+ padding-top: 0;
+ }
+
+ /* Widen masthead and social buttons to fill body padding */
+ .jumbotron {
+ padding: 40px 20px;
+ margin-top: -20px; /* Offset bottom margin on .navbar */
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .masthead h1 {
+ font-size: 90px;
+ }
+ .masthead p,
+ .masthead .btn {
+ font-size: 24px;
+ }
+ .marketing .span4 {
+ margin-bottom: 40px;
+ }
+ .bs-docs-social {
+ margin: 0 -20px;
+ }
+
+ /* Space out the show-grid examples */
+ .show-grid [class*="span"] {
+ margin-bottom: 5px;
+ }
+
+ /* Sidenav */
+ .bs-docs-sidenav {
+ width: auto;
+ margin-bottom: 20px;
+ }
+ .bs-docs-sidenav.affix {
+ position: static;
+ width: auto;
+ top: 0;
+ }
+
+ /* Unfloat the back to top link in footer */
+ .footer {
+ margin-left: -20px;
+ margin-right: -20px;
+ padding-left: 20px;
+ padding-right: 20px;
+ }
+ .footer p {
+ margin-bottom: 9px;
+ }
+}
+
+/* Landscape phones
+------------------------- */
+@media (max-width: 480px) {
+ /* Remove padding above jumbotron */
+ body {
+ padding-top: 0;
+ }
+
+ /* Change up some type stuff */
+ h2 small {
+ display: block;
+ }
+
+ /* Downsize the jumbotrons */
+ .jumbotron h1 {
+ font-size: 60px;
+ }
+ .jumbotron p,
+ .jumbotron .btn {
+ font-size: 20px;
+ }
+ .jumbotron .btn {
+ display: block;
+ margin: 0 auto;
+ }
+
+ /* center align subhead text like the masthead */
+ .subhead h1,
+ .subhead p {
+ text-align: center;
+ }
+
+ /* Marketing on home */
+ .marketing h1 {
+ font-size: 40px;
+ }
+
+ /* center example sites */
+ .example-sites {
+ margin-left: 0;
+ }
+ .example-sites > li {
+ float: none;
+ display: block;
+ max-width: 280px;
+ margin: 0 auto 18px;
+ text-align: center;
+ }
+ .example-sites .thumbnail > img {
+ max-width: 270px;
+ }
+
+ /* Do our best to make tables work in narrow viewports */
+ table code {
+ white-space: normal;
+ word-wrap: break-word;
+ word-break: break-all;
+ }
+
+ /* Modal example */
+ .modal-example .modal {
+ position: relative;
+ top: auto;
+ right: auto;
+ bottom: auto;
+ left: auto;
+ }
+
+ /* Unfloat the back to top in footer to prevent odd text wrapping */
+ .footer .pull-right {
+ float: none;
+ }
+}
diff --git a/synergy/client/app/css/min/custom.css b/synergy/client/app/css/min/custom.css
new file mode 100644
index 0000000..7eab304
--- /dev/null
+++ b/synergy/client/app/css/min/custom.css
@@ -0,0 +1 @@
+.badge-fails,.label-fails{background-color:#333}.badge-sanity,.label-sanity{background-color:#b94a48}.badge-obsolete,.label-obsolete{background-color:#999}.dropbox{border:4px dashed rgba(0,0,0,.2);text-align:center;color:#999}.dropbox.hover{border:4px dashed #08c}.finished,.row-passed td,.table tbody tr td.finished,.table tbody tr.finished td{background-color:#dff0d8!important}.table tbody tr td.unfinished,.table tbody tr.unfinished td,.unfinished{background-color:#fcf8e3!important}.row- [...]
\ No newline at end of file
diff --git a/synergy/client/app/css/min/docs.css b/synergy/client/app/css/min/docs.css
new file mode 100755
index 0000000..5360e03
--- /dev/null
+++ b/synergy/client/app/css/min/docs.css
@@ -0,0 +1 @@
+body{position:relative;padding-top:40px}h3 code{font-size:14px;font-weight:400}body>.navbar{font-size:13px}body>.navbar .brand{padding-right:0;padding-left:0;margin-left:20px;float:right;font-weight:700;color:#000;text-shadow:0 1px 0 rgba(255,255,255,.1),0 0 30px rgba(255,255,255,.125);-webkit-transition:all .2s linear;-moz-transition:all .2s linear;transition:all .2s linear}body>.navbar .brand:hover{text-decoration:none;text-shadow:0 1px 0 rgba(255,255,255,.1),0 0 30px rgba(255,255,255, [...]
\ No newline at end of file
diff --git a/synergy/client/app/favicon.ico b/synergy/client/app/favicon.ico
new file mode 100755
index 0000000..902c0f2
Binary files /dev/null and b/synergy/client/app/favicon.ico differ
diff --git a/synergy/client/app/img/ajax-loader.gif b/synergy/client/app/img/ajax-loader.gif
new file mode 100644
index 0000000..e377924
Binary files /dev/null and b/synergy/client/app/img/ajax-loader.gif differ
diff --git a/synergy/client/app/img/blue.png b/synergy/client/app/img/blue.png
new file mode 100644
index 0000000..17efcae
Binary files /dev/null and b/synergy/client/app/img/blue.png differ
diff --git a/synergy/client/app/img/bs-docs-bootstrap-features.png b/synergy/client/app/img/bs-docs-bootstrap-features.png
new file mode 100644
index 0000000..bbdbee4
Binary files /dev/null and b/synergy/client/app/img/bs-docs-bootstrap-features.png differ
diff --git a/synergy/client/app/img/bs-docs-masthead-pattern.png b/synergy/client/app/img/bs-docs-masthead-pattern.png
new file mode 100644
index 0000000..a1afdad
Binary files /dev/null and b/synergy/client/app/img/bs-docs-masthead-pattern.png differ
diff --git a/synergy/client/app/img/clock.png b/synergy/client/app/img/clock.png
new file mode 100644
index 0000000..22b1f6a
Binary files /dev/null and b/synergy/client/app/img/clock.png differ
diff --git a/synergy/client/app/img/glyphicons-halflings-white.png b/synergy/client/app/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..da199eb
Binary files /dev/null and b/synergy/client/app/img/glyphicons-halflings-white.png differ
diff --git a/synergy/client/app/img/glyphicons-halflings.png b/synergy/client/app/img/glyphicons-halflings.png
new file mode 100644
index 0000000..05373d1
Binary files /dev/null and b/synergy/client/app/img/glyphicons-halflings.png differ
diff --git a/synergy/client/app/img/grey.png b/synergy/client/app/img/grey.png
new file mode 100644
index 0000000..89957ae
Binary files /dev/null and b/synergy/client/app/img/grey.png differ
diff --git a/synergy/client/app/img/grid-baseline-20px.png b/synergy/client/app/img/grid-baseline-20px.png
new file mode 100644
index 0000000..cf6f98c
Binary files /dev/null and b/synergy/client/app/img/grid-baseline-20px.png differ
diff --git a/synergy/client/app/img/nb.gif b/synergy/client/app/img/nb.gif
new file mode 100644
index 0000000..c5a63d9
Binary files /dev/null and b/synergy/client/app/img/nb.gif differ
diff --git a/synergy/client/app/img/red.png b/synergy/client/app/img/red.png
new file mode 100644
index 0000000..0bcd944
Binary files /dev/null and b/synergy/client/app/img/red.png differ
diff --git a/synergy/client/app/img/user.png b/synergy/client/app/img/user.png
new file mode 100644
index 0000000..9fdd05d
Binary files /dev/null and b/synergy/client/app/img/user.png differ
diff --git a/synergy/client/app/img/yellow.png b/synergy/client/app/img/yellow.png
new file mode 100644
index 0000000..f887f2d
Binary files /dev/null and b/synergy/client/app/img/yellow.png differ
diff --git a/synergy/client/app/index.html b/synergy/client/app/index.html
new file mode 100755
index 0000000..d739b36
--- /dev/null
+++ b/synergy/client/app/index.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:ng="http://angularjs.org" id="ng-app" data-ng-app="synergy" class="ng-app:synergy">
+ <head>
+ <link rel="SHORTCUT ICON" href="favicon.ico"/>
+ <meta charset="utf-8">
+ <link href="./opensearch.xml" rel="search" title="synergy search" type="application/opensearchdescription+xml">
+ <meta http-equiv="X-UA-Compatible" content="IE=10" />
+ <title>Synergy - Test Management Tool</title>
+ <link rel="stylesheet" href="lib/angular-ui/fullcalendar.min.css" >
+ <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/themes/base/jquery-ui.css">
+ <link href="css/min/bootstrap.css?v=1" rel="stylesheet">
+ <link href="css/min/custom.css?v=1521293430023" rel="stylesheet">
+ <link rel="stylesheet" type="text/css" href="js/libs/codemirror/codemirror.min.css">
+ <link href="css/min/bootstrap-responsive.css" rel="stylesheet">
+ <link href="lib/angular-ui/select2.min.css" rel="stylesheet">
+ <!--<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>-->
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="js/libs/jquery-migrate/jquery-migrate-1.2.1.min.js"></script>
+ <!--<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-migrate/1.2.1/jquery-migrate.min.js"></script>-->
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/jquery-ui.min.js"></script>
+ <script src="js/libs/timepicker/picker.min.js"></script>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <script src="js/libs/codemirror/codemirror_combined.js"></script>
+ <script src="lib/angular-ui/fullcalendar.min.js"></script>
+ <script src="js/libs/jsdifflib/diff_combined.min.js"></script>
+ <link href="js/libs/jsdifflib/diffview.css" rel="stylesheet">
+ <script src="js/libs/json/json2-min.js"></script>
+ <!--<script src="js/configuration.js"></script>-->
+ <!--<script src="js/min/models.js"></script>-->
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+ </head>
+ <body id="synergy_session" data-ng-controller="SynergyCtrl" >
+ <div class="navbar navbar-fixed-top" id="navbar-top" >
+ <div class="navbar-inner" >
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#" data-ng-class="busyBrand" >Synergy</a>
+ <div class="nav-collapse" id="synergy_mainbar">
+ <ul class="nav" id="navbar" >
+ <li class="active" id="nav_home"><a href="#">Home</a></li>
+ <li id="nav_runs"><a href="#/runs">Test Runs</a></li>
+ <li id="nav_specs"><a href="#/specifications">Test Specifications</a></li>
+ <li class="dropdown " id="nav_other" >
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Other <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/tribes">Tribes</a></li>
+ <li><a href="#/calendar">Calendar</a></li>
+ <li class="divider"></li>
+ <li><a href="#/about">About</a></li>
+ </ul>
+ </li>
+ <li id="nav_admin" class="dropdown" data-ng-show="role == 'admin' || role == 'manager'">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Administration <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/administration/runs">Test Runs</a></li>
+ <li><a href="#/administration/versions">Versions</a></li>
+ <li><a href="#/administration/platforms">Platforms</a></li>
+ <li><a href="#/administration/projects">Projects</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/users">Users</a></li>
+ <li><a href="#/administration/tribes">Tribes</a></li>
+ <li><a href="#/administration/reviews">Tutorials</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/setting">Server setting</a></li>
+ <li><a href="#/administration/log">View log</a></li>
+ <li><a href="#/administration/database">Database</a></li>
+ </ul>
+ </li>
+ <li>
+ <typeaheads items="suggestions" prompt="Search" model="searchedItem" data-type="searchAhead()" on-select="goToSearch()" data-enter="synergySearch()" />
+ </li>
+ </ul>
+ <div class="pull-right">
+ <button id="synergy_login_form" class="btn " data-ng-click="login();" >Sign in</button>
+ <div id="synergy_usermenu" style="display:none">
+ <ul class="nav pull-right"><li class="dropdown"><a href="#" class="dropdown-toggle btn-primary" data-toggle="dropdown" id="usermenu_user" style="color: white">USER <b class="caret" id="userCaret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/user">Me</a></li>
+ <li data-ng-show="role == 'admin' || role == 'manager'"><a href="#/administration">Administration</a></li>
+ <li><a href="#logout" data-ng-click="logout();">Logout</a></li>
+ </ul></li></ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="container">
+
+
+ <div class="row-fluid" data-ng-cloak>
+ <div class="span12">
+ <ul class="breadcrumb">
+ <li><a href="#">Home</a> <span class="divider">/</span></li>
+ <li data-ng-repeat="b in breadcrumbs"><a href="#/{{b.link}}">{{b.title}}</a> <span class="divider">/</span></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row-fluid" >
+ <div class="span5">
+ <div data-ng-cloak data-ng-show="SYNERGY.logger.print" class="alert {{SYNERGY.logger.style}}">
+ <strong>{{SYNERGY.logger.title}}</strong> <span>{{SYNERGY.logger.msg}}</span><br/>
+ {{SYNERGY.logger.date}}
+ </div>
+ </div></div>
+ <div class="row-fluid" >
+ <div class="span12">
+ <div data-ng-view data-ng-cloak></div>
+ </div>
+ <div class="span2">
+ </div>
+ </div>
+ <hr>
+ <footer>
+ <div>
+ <div></div> <small class="pull-right">Synergy v<span>{{SYNERGY.version}} </span> | <a data-ng-show="!SYNERGY.useSSO" href="#/register">Register</a></small>
+ </div>
+ </footer>
+
+ </div>
+ <div class="modal hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3 id="myModalLabel">Modal header</h3>
+ </div>
+ <div class="modal-body" id="modal-body">
+ <p>One fine body 2…</p>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" data-dismiss="modal">OK</button>
+ </div>
+ </div>
+ <script src="js/bootstrap/bootstrap_combined.js?v=2"></script>
+ <script src="lib/angular/1.0.8/angular_combined.min.js"></script>
+ <script src="lib/angular-ui/ui-codemirror.min.js"></script>
+ <script src="lib/angular-ui/select2_combined.min.js"></script>
+ <script src="lib/bootstrap-custom/ui-bootstrap-custom-tpls-0.6.0.min.js"></script>
+ <script src="js/legacy/polyfills.js?v=1521293430023" type="text/javascript"></script>
+ <script src="js/min/synergy.js?v=1521293430023"></script>
+ </body>
+</html>
diff --git a/synergy/client/app/index2.html b/synergy/client/app/index2.html
new file mode 100755
index 0000000..2b37b8b
--- /dev/null
+++ b/synergy/client/app/index2.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:ng="http://angularjs.org" id="ng-app" data-ng-app="synergy" class="ng-app:synergy">
+ <head>
+ <link rel="SHORTCUT ICON" href="favicon.ico"/>
+ <meta charset="utf-8">
+ <link href="./opensearch.xml" rel="search" title="synergy search" type="application/opensearchdescription+xml">
+ <meta http-equiv="X-UA-Compatible" content="IE=10" />
+ <title>Synergy - Test Management Tool</title>
+ <link rel="stylesheet" href="lib/angular-ui/fullcalendar.min.css" >
+ <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/themes/base/jquery-ui.css">
+ <link href="css/min/bootstrap.css?v=1" rel="stylesheet">
+ <link href="css/min/custom.css?v=1521293430023" rel="stylesheet">
+ <link rel="stylesheet" type="text/css" href="js/libs/codemirror/codemirror.min.css">
+ <link href="css/min/bootstrap-responsive.css" rel="stylesheet">
+ <link href="lib/angular-ui/select2.min.css" rel="stylesheet">
+ <script src="js/libs/jquery-1.10.2/jquery.min.js"></script>
+ <script src="js/libs/jquery-migrate/jquery-migrate-1.2.1.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/jquery-ui.min.js"></script>
+ <script src="js/libs/timepicker/picker.min.js"></script>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <script src="js/libs/codemirror/codemirror_combined.js"></script>
+ <script src="lib/angular-ui/fullcalendar.min.js"></script>
+ <script src="js/libs/jsdifflib/diff_combined.min.js"></script>
+ <link href="js/libs/jsdifflib/diffview.css" rel="stylesheet">
+ <script src="js/libs/json/json2-min.js"></script>
+ <!--<script src="js/configuration.js"></script>-->
+ <!--<script src="js/min/models.js"></script>-->
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+ </head>
+ <body id="synergy_session" data-ng-controller="SynergyCtrl" >
+ <div class="navbar navbar-fixed-top" id="navbar-top" >
+ <div class="navbar-inner" >
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#" data-ng-class="busyBrand" >Synergy</a>
+ <div class="nav-collapse" id="synergy_mainbar">
+ <ul class="nav" id="navbar" >
+ <li class="active" id="nav_home"><a href="#">Home</a></li>
+ <li id="nav_runs"><a href="#/runs">Test Runs</a></li>
+ <li id="nav_specs"><a href="#/specifications">Test Specifications</a></li>
+ <li class="dropdown " id="nav_other" >
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Other <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/tribes">Tribes</a></li>
+ <li><a href="#/calendar">Calendar</a></li>
+ <li class="divider"></li>
+ <li><a href="#/about">About</a></li>
+ </ul>
+ </li>
+ <li id="nav_admin" class="dropdown" data-ng-show="role == 'admin' || role == 'manager'">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Administration <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/administration/runs">Test Runs</a></li>
+ <li><a href="#/administration/versions">Versions</a></li>
+ <li><a href="#/administration/platforms">Platforms</a></li>
+ <li><a href="#/administration/projects">Projects</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/users">Users</a></li>
+ <li><a href="#/administration/tribes">Tribes</a></li>
+ <li><a href="#/administration/reviews">Tutorials</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/setting">Server setting</a></li>
+ <li><a href="#/administration/log">View log</a></li>
+ <li><a href="#/administration/database">Database</a></li>
+ </ul>
+ </li>
+ <li>
+ <typeaheads items="suggestions" prompt="Search" model="searchedItem" data-type="searchAhead()" on-select="goToSearch()" data-enter="synergySearch()" />
+ </li>
+ </ul>
+ <div class="pull-right">
+ <button id="synergy_login_form" class="btn " data-ng-click="login();" >Sign in</button>
+ <div id="synergy_usermenu" style="display:none">
+ <ul class="nav pull-right"><li class="dropdown"><a href="#" class="dropdown-toggle btn-primary" data-toggle="dropdown" id="usermenu_user" style="color: white">USER <b class="caret" id="userCaret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/user">Me</a></li>
+ <li data-ng-show="role == 'admin' || role == 'manager'"><a href="#/administration">Administration</a></li>
+ <li><a href="#logout" data-ng-click="logout();">Logout</a></li>
+ </ul></li></ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="container">
+
+
+ <div class="row-fluid" data-ng-cloak>
+ <div class="span12">
+ <ul class="breadcrumb">
+ <li><a href="#">Home</a> <span class="divider">/</span></li>
+ <li data-ng-repeat="b in breadcrumbs"><a href="#/{{b.link}}">{{b.title}}</a> <span class="divider">/</span></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row-fluid" >
+ <div class="span5">
+ <div data-ng-cloak data-ng-show="SYNERGY.logger.print" class="alert {{SYNERGY.logger.style}}">
+ <strong>{{SYNERGY.logger.title}}</strong> <span>{{SYNERGY.logger.msg}}</span><br/>
+ {{SYNERGY.logger.date}}
+ </div>
+ </div></div>
+ <div class="row-fluid" >
+ <div class="span12">
+ <div data-ng-view data-ng-cloak></div>
+ </div>
+ <div class="span2">
+ </div>
+ </div>
+ <hr>
+ <footer>
+ <div>
+ <div></div> <small class="pull-right">Synergy v<span>{{SYNERGY.version}}</span> | <a data-ng-show="!SYNERGY.useSSO" href="#/register">Register</a></small>
+ </div>
+ </footer>
+
+ </div>
+ <div class="modal hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3 id="myModalLabel">Modal header</h3>
+ </div>
+ <div class="modal-body" id="modal-body">
+ <p>One fine body 2…</p>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" data-dismiss="modal">OK</button>
+ </div>
+ </div>
+ <script src="js/bootstrap/bootstrap_combined.js?v=21"></script>
+ <script src="lib/angular/1.0.8/angular_combined.min.js"></script>
+ <script src="lib/angular-ui/ui-codemirror.min.js"></script>
+ <script src="lib/angular-ui/select2_combined.min.js"></script>
+ <script src="lib/bootstrap-custom/ui-bootstrap-custom-tpls-0.6.0.min.js"></script>
+ <script src="js/legacy/polyfills.js?v=1521293430023" type="text/javascript"></script>
+ <script src="js/min/synergy.js?v=1521293430023"></script>
+ </body>
+</html>
diff --git a/synergy/client/app/index_dev.html b/synergy/client/app/index_dev.html
new file mode 100755
index 0000000..1821d4c
--- /dev/null
+++ b/synergy/client/app/index_dev.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:ng="http://angularjs.org" id="ng-app" data-ng-app="synergy" class="ng-app:synergy">
+ <head>
+ <link rel="SHORTCUT ICON" href="favicon.ico"/>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=10" />
+ <title>Dev Synergy - Test Management Tool</title>
+ <link rel="stylesheet" href="lib/angular-ui/fullcalendar.min.css" >
+ <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/themes/base/jquery-ui.css">
+ <link href="css/min/bootstrap.css" rel="stylesheet">
+ <link href="css/custom.css" rel="stylesheet">
+ <link rel="stylesheet" type="text/css" href="js/libs/codemirror/codemirror.min.css">
+ <link href="css/min/bootstrap-responsive.css" rel="stylesheet">
+ <link href="lib/angular-ui/select2.css" rel="stylesheet">
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-migrate/1.2.1/jquery-migrate.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.22/jquery-ui.min.js"></script>
+ <script src="js/libs/timepicker/picker.min.js"></script>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+ <script src="js/libs/codemirror/codemirror.min.js"></script>
+ <script src="js/libs/codemirror/xml.min.js"></script>
+ <script src="js/libs/codemirror/javascript.min.js"></script>
+ <script src="js/libs/codemirror/css.min.js"></script>
+ <script src="js/libs/codemirror/htmlmixed.min.js"></script>
+ <script src="js/libs/jquery-qrcode-master/jquery.qrcode.min.js"></script>
+ <script src="lib/angular-ui/fullcalendar.min.js"></script>
+ <script src="js/libs/jsdifflib/diffview.js"></script>
+ <link href="js/libs/jsdifflib/diffview.css" rel="stylesheet">
+ <script src="js/libs/jsdifflib/difflib.js"></script>
+
+ <style type="text/css">
+ body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+ }
+ </style>
+ <script src="js/libs/json/json2-min.js"></script>
+ <script src="js/exts.js"></script>
+ <style>
+
+
+ </style>
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+ </head>
+ <body id="synergy_session" data-ng-controller="SynergyCtrl" >
+ <div class="navbar navbar-inverse navbar-fixed-top" id="navbar-top" >
+ <div class="navbar-inner" >
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#" data-ng-class="busyBrand" >Synergy</a>
+ <div class="nav-collapse" id="synergy_mainbar">
+ <ul class="nav" id="navbar" >
+ <li class="active" id="nav_home"><a href="#">Home</a></li>
+ <li id="nav_runs"><a href="#/runs">Test Runs</a></li>
+ <li id="nav_specs"><a href="#/specifications">Test Specifications</a></li>
+ <li class="dropdown " id="nav_other" >
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Other <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/tribes">Tribes</a></li>
+ <li><a href="#/calendar">Calendar</a></li>
+ <li class="divider"></li>
+ <li><a href="#/about">About</a></li>
+ </ul>
+ </li>
+ <li id="nav_admin" class="dropdown" data-ng-show="role == 'admin' || role == 'manager'">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Administration <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/administration/runs">Test Runs</a></li>
+ <li><a href="#/administration/versions">Versions</a></li>
+ <li><a href="#/administration/platforms">Platforms</a></li>
+ <li><a href="#/administration/projects">Projects</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/users">Users</a></li>
+ <li><a href="#/administration/tribes">Tribes</a></li>
+ <li><a href="#/administration/reviews">Tutorials</a></li>
+ <li class="divider"></li>
+ <li><a href="#/administration/setting">Server setting</a></li>
+ <li><a href="#/administration/log">View log</a></li>
+ <li><a href="#/administration/database">Database</a></li>
+ </ul>
+ </li>
+ <li>
+ <typeaheads items="suggestions" prompt="Search" model="searchedItem" data-type="searchAhead()" on-select="goToSearch()" data-enter="synergySearch()" />
+ </li>
+ </ul>
+ <div class="pull-right">
+ <button id="synergy_login_form" class="btn " data-ng-click="login();" >Sign in</button>
+ <div id="synergy_usermenu" style="display:none">
+ <ul class="nav pull-right"><li class="dropdown"><a href="#" class="dropdown-toggle btn-primary" data-toggle="dropdown" id="usermenu_user" style="color: white">USER <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#/user">Me</a></li>
+ <li data-ng-show="role == 'admin' || role == 'manager'"><a href="#/administration">Administration</a></li>
+ <li><a href="#logout" data-ng-click="logout();">Logout</a></li>
+ </ul></li></ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="container">
+
+
+ <div class="row-fluid" data-ng-cloak>
+ <div class="span12">
+ <ul class="breadcrumb">
+ <li><a href="index.html">Home</a> <span class="divider">/</span></li>
+ <li data-ng-repeat="b in breadcrumbs"><a href="#/{{b.link}}">{{b.title}}</a> <span class="divider">/</span></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row-fluid" >
+ <div class="span5">
+ <div data-ng-cloak data-ng-show="SYNERGY.logger.print" class="alert {{SYNERGY.logger.style}}">
+ <strong>{{SYNERGY.logger.title}}</strong> <span>{{SYNERGY.logger.msg}}</span><br/>
+ {{SYNERGY.logger.date}}
+ </div>
+ </div></div>
+ <div class="row-fluid" >
+ <div class="span12">
+ <div data-ng-view data-ng-cloak></div>
+ </div>
+ <div class="span2">
+ </div>
+ </div>
+ <hr>
+ <footer>
+ <div>
+ <div></div> <small class="pull-right" data-ng-click="toggleBusyBrand()">Synergy v<span>{{SYNERGY.version}}</span> | <a data-ng-show="!SYNERGY.useSSO" href="#/register">Register</a></small>
+ </div>
+ </footer>
+
+ </div>
+ <div class="modal hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3 id="myModalLabel">Modal header</h3>
+ </div>
+ <div class="modal-body" id="modal-body">
+ <p>One fine body 2…</p>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" data-dismiss="modal">OK</button>
+ </div>
+ </div>
+ <script src="js/bootstrap/bootstrap-transition.js"></script>
+ <script src="js/bootstrap/bootstrap-alert.js"></script>
+ <script src="js/bootstrap/bootstrap-typeahead.js"></script>
+ <script src="js/bootstrap/bootstrap-modal.js"></script>
+ <script src="js/bootstrap/bootstrap-dropdown.js"></script>
+ <script src="js/bootstrap/bootstrap-tooltip.js"></script>
+ <script src="js/bootstrap/bootstrap-collapse.js"></script>
+ <script src="lib/angular/angular.js"></script>
+ <script src="lib/angular/angular-cookies.js"></script>
+ <script src="lib/angular-ui/ui-codemirror.js"></script>
+ <script src="lib/angular-ui/select2_combined.min.js"></script>
+ <script src="lib/bootstrap-custom/ui-bootstrap-custom-tpls-0.6.0.min.js"></script>
+ <script src="js/legacy/polyfills.js" type="text/javascript"></script>
+ <script src="js/app.js"></script>
+ <script src="js/models.js" type="text/javascript"></script>
+ <script src="js/utils.js" type="text/javascript"></script>
+ <script src="js/configuration.js"></script>
+ <script src="js/handlers.js" type="text/javascript"></script>
+ <script src="js/filters.js" type="text/javascript"></script>
+ <script src="js/controllers.js"></script>
+ <script src="js/factories.js"></script>
+ <script src="js/directives.js"></script>
+ </body>
+</html>
diff --git a/synergy/client/app/js/app.js b/synergy/client/app/js/app.js
new file mode 100755
index 0000000..ccc06f7
--- /dev/null
+++ b/synergy/client/app/js/app.js
@@ -0,0 +1,260 @@
+"use strict";
+
+
+angular.module("synergy", ["ui.codemirror",
+ "infinite-scroll",
+ "ui.select2",
+ "ngCookies",
+ "ngProgress",
+ "ui.bootstrap",
+ "synergy.http",
+ "synergy.test",
+ "synergy.core",
+ "synergy.controllers",
+ "synergy.directives",
+ "synergy.models",
+ "synergy.utils",
+ "synergy.handlers",
+ "synergy.filters"])
+ .config(function ($provide, $routeProvider, $httpProvider) {
+
+ $provide.factory("MyHttpInterceptor", ["$q", "$rootScope", "SynergyApp", "$injector", "$cookieStore", "sessionService",
+ function ($q, $rootScope, SynergyApp, $injector, $cookieStore, sessionService) { // TODO in case of AngularJS update, this is likely obsolete
+
+ var IGNORE_URLS = ["../../server/api/login.php", "../../server/api/login.php?&return=1"];
+ var refreshPromise = null;
+ function SessionRenewal() {
+ this.originalResponse = null;
+ this.intervalId = -1;
+ this.TOKEN_LENGTH = 32;
+ this.token = null;
+ this.counter = 0;
+ }
+
+ SessionRenewal.prototype.getToken = function () {
+ var text = "";
+ var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ for (var i = 0; i < this.TOKEN_LENGTH; i++) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text + new Date().getTime();
+ };
+ SessionRenewal.prototype.reset = function () {
+ window.clearInterval(this.intervalId);
+ this.counter = 0;
+ this.token = null;
+ this.intervalId = -1;
+ this.originalResponse = null;
+ };
+ SessionRenewal.prototype.checkForNewLogin = function () {
+ this.counter++;
+ var self = this;
+
+ $injector.get("$http").get(SynergyApp.getApp().server.buildURL("refresh", {"token": self.token})).success(function (result) {
+ window.clearInterval(self.intervalId);
+ self.reset();
+ refreshPromise.resolve(self.originalResponse);
+ }).error(function (data, status, headers, config) {
+ if (self.counter > 100) {
+ self.reset();
+ refreshPromise.reject(self.originalResponse);
+ }
+ });
+ };
+ SessionRenewal.prototype.getRedirectUrl = function () {
+ var url = window.location.href.substring(0, window.location.href.indexOf("#"));
+ url = url.endsWith(".html") ? url.substring(0, url.lastIndexOf("/") + 1) : url;
+ this.token = this.getToken();
+ url += "login.html?token=" + this.token;
+ if (SynergyApp.getApp().useSSO) {
+ var base = SynergyApp.getApp().getLoginRedirectUrl(SynergyApp.getApp().ssoLoginUrl, url);
+ return base.substring(0, base.indexOf("?revalidate=1"));
+ } else {
+ // todo fix, std login page is synergy/client/app/#/login, which is not visible for server though...
+ throw new Error("Not implemented");
+ }
+ };
+ SessionRenewal.prototype.init = function (response) {
+ refreshPromise = $q.defer();
+
+ this.originalResponse = response;
+ $("#myModalLabel").text("Please login");
+ $("#modal-body").html("Your session has expired. <br/><a href='" + this.getRedirectUrl() + "' target='_blank'>Click to login</a>");
+ if (!$("#myModal").hasClass("in")) {
+ $("#myModal").modal("toggle");
+ }
+ var self = this;
+ this.intervalId = window.setInterval(function () {
+ self.checkForNewLogin();
+ }, 2000);
+
+ return refreshPromise.promise;
+ };
+
+ SessionRenewal.prototype.displayDialog = function (isVisible) {
+ if ($("#myModal").hasClass("in")) {
+ $("#myModal").modal("toggle");
+ }
+ };
+
+ SessionRenewal.prototype.renew = function (response) {
+ var self = this;
+ return this.init(response)
+ .then(function () {
+ return $injector.get("sessionHttp").infoConditionalPromise(SynergyApp.getApp().server.buildURL("session"));
+ })
+ .then(function (data) {
+ SynergyApp.getApp().session.setSession(data.data);
+ sessionService.setSession(SynergyApp.getApp().session);
+ $cookieStore.put("session", ({firstName: data.firstName, lastName: data.lastName, username: data.username, role: data.role, token: data.token, created: 1000 * parseInt(data.created, 10), session_id: data.session_id}));
+ $rootScope.$broadcast("refreshRole");
+ self.displayDialog(false);
+ var $h = $injector.get("$http");
+ return $h(response.config);
+ }, function () {
+ $cookieStore.remove("session");
+ sessionService.clearSession();
+ SynergyApp.getApp().session.clearSession();
+ $rootScope.$broadcast("hideUserMenu", false);
+ $rootScope.$broadcast("refreshRole");
+ self.displayDialog(false);
+ return $q.reject(response);
+ });
+ };
+
+ var sessionRenewal = new SessionRenewal();
+
+ return function (promise) {
+ return promise.then(function (response) {
+ $rootScope.$broadcast("busyMode", false);
+ window.document.body.style.cursor = "default";
+ return response;
+ }, function (response) {
+ $rootScope.$broadcast("busyMode", false);
+ window.document.body.style.cursor = "default";
+ if (parseInt(response.status, 10) === 0) {
+ $rootScope.$broadcast("possibleTimeout", false);
+ }
+ if (parseInt(response.status, 10) === 401 && IGNORE_URLS.indexOf(response.config.url) < 0) { // expired session
+ return sessionRenewal.renew(response);
+ } else {
+ return $q.reject(response);
+ }
+ });
+ };
+ }]);
+
+ ($httpProvider.interceptors) ? $httpProvider.interceptors.push("MyHttpInterceptor") : $httpProvider.responseInterceptors.push("MyHttpInterceptor");
+
+ $provide.factory("utils", function () {
+ var u = {};
+ u.escape = function (s) {
+ return encodeURIComponent(s);
+ };
+ return u;
+ });
+
+ $provide.factory("issue", function ($http) {
+ return {
+ getIssue: function (id, $scope) { // not elegant way, $scope should be injected not from function call
+ $http.get($scope.SYNERGY.server.buildURL("issue", {"id": id})).success(function (data) {
+ return data;
+ }).error(function (data) {
+ window.console.log("Issue " + id + " not found: " + data);
+ });
+ }
+ };
+ });
+
+ // PUBLIC ROUTING
+ $routeProvider.when("/specifications/:id", {templateUrl: "partials/public/view/specpool.html?v=1521293430023", controller: "SpecPoolCtrl"});
+ $routeProvider.when("/statistics/:id", {templateUrl: "partials/public/view/statistics.html?v=1521293430023", controller: "StatisticsCtrl"});
+ $routeProvider.when("/statistics/:id/archive", {templateUrl: "partials/public/view/statistics.html?v=1521293430023", controller: "StatisticsCtrl"});
+ $routeProvider.when("/search/:search", {templateUrl: "partials/public/view/search.html?v=1521293430023", controller: "SearchCtrl"});
+ $routeProvider.when("/specifications", {templateUrl: "partials/public/view/specpool.html?v=1521293430023", controller: "SpecPoolCtrl"});
+ $routeProvider.when("/specification/:id/create", {templateUrl: "partials/public/create/specification.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/specification/:id/edit", {templateUrl: "partials/public/edit/specification.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/specification/:id/v/1", {templateUrl: "partials/public/view/specification_view_1.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/specification/:id/v/2", {templateUrl: "partials/public/view/specification_view_2.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/specification/:id/v/2/:label", {templateUrl: "partials/public/view/specification_view_2.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/title/:simpleName/:simpleVersion", {templateUrl: "partials/public/view/specification_view_2.html?v=1521293430023", controller: "SpecificationCtrl"});
+ $routeProvider.when("/title/:simpleName/", {redirectTo: "/title/:simpleName/latest"});
+ $routeProvider.when("/specification/:id", {redirectTo: "/specification/:id/v/2"});
+ $routeProvider.when("/suite/:id/:specification/:version/create", {templateUrl: "partials/public/create/suite.html?v=1521293430023", controller: "SuiteCtrl"});
+ $routeProvider.when("/suite/:id/edit", {templateUrl: "partials/public/edit/suite.html?v=1521293430023", controller: "SuiteCtrl"});
+ $routeProvider.when("/suite/:id/v/1", {templateUrl: "partials/public/view/suite.html?v=1521293430023", controller: "SuiteCtrl"});
+ $routeProvider.when("/suite/:id", {redirectTo: "/suite/:id/v/1"});
+ $routeProvider.when("/case/:id/suite/:parent/edit", {templateUrl: "partials/public/edit/case.html?v=1521293430023", controller: "CaseCtrl"});
+ $routeProvider.when("/case/:id/suite/:parent/create", {templateUrl: "partials/public/create/case.html?v=1521293430023", controller: "CaseCtrl"});
+ $routeProvider.when("/case/:id/suite/:parent/v/1", {templateUrl: "partials/public/view/case.html?v=1521293430023", controller: "CaseCtrl"});
+ $routeProvider.when("/assignment_comments/:id", {templateUrl: "partials/public/view/assignment_comments.html?v=1521293430023", controller: "AssignmentCommentsCtrl"});
+ $routeProvider.when("/case/:id/suite/:parent", {redirectTo: "/case/:id/suite/:parent/v/1"});
+ $routeProvider.when("/case/:id", {redirectTo: "/case/:id/suite/-1/v/1"});
+ $routeProvider.when("/run/:id/v/1", {templateUrl: "partials/public/view/run_view_1.html?v=1521293430023", controller: "RunCtrl"});
+ $routeProvider.when("/run/:id/coverage", {templateUrl: "partials/public/view/run_coverage.html?v=1521293430023", controller: "RunCoverageCtrl"});
+ $routeProvider.when("/run/:id/v/2", {templateUrl: "partials/public/view/run_view_2.html?v=1521293430023", controller: "RunCtrlUser"});
+ $routeProvider.when("/run/:id/v/3", {templateUrl: "partials/public/view/run_view_3.html?v=1521293430023", controller: "RunCtrlCase"});
+ $routeProvider.when("/run/:id", {redirectTo: "/run/:id/v/2"});
+ $routeProvider.when("/runs/page/:page", {templateUrl: "partials/public/view/runs.html?v=1521293430023", controller: "RunsCtrl"});
+ $routeProvider.when("/runs", {redirectTo: "/runs/page/1"});
+ $routeProvider.when("/user", {templateUrl: "partials/public/view/profile.html?v=1521293430023", controller: "ProfileCtrl"});
+ $routeProvider.when("/user/:user", {templateUrl: "partials/public/view/profile.html?v=1521293430023", controller: "ProfileCtrl"});
+ $routeProvider.when("/label/:label/page/:page", {templateUrl: "partials/public/view/label.html?v=1521293430023", controller: "LabelFilterCtrl"});
+ $routeProvider.when("/label/:label", {redirectTo: "/label/:label/page/1"});
+ $routeProvider.when("/tribe/:id/edit", {templateUrl: "partials/public/edit/tribe.html?v=1521293430023", controller: "TribeCtrl"});
+ $routeProvider.when("/tribe/:id/view", {templateUrl: "partials/public/view/tribe.html?v=1521293430023", controller: "TribeCtrl"});
+ $routeProvider.when("/tribes", {templateUrl: "partials/public/view/tribes.html?v=1521293430023", controller: "TribesCtrl"});
+ $routeProvider.when("/calendar", {templateUrl: "partials/public/view/calendar.html?v=1521293430023", controller: "CalendarCtrl"});
+ $routeProvider.when("/tribe/:id", {redirectTo: "/tribe/:id/view"});
+ $routeProvider.when("/", {templateUrl: "partials/public/view/home.html?v=1521293430023", controller: "HomeCtrl"});
+ $routeProvider.when("/assignment/:id/v/:mode", {templateUrl: "partials/public/view/assignment.html?v=1521293430023", controller: "AssignmentCtrl"});
+ $routeProvider.when("/assignment/create/run/:id", {templateUrl: "partials/public/create/assignment.html?v=1521293430023", controller: "AssignmentVolunteerCtrl"});
+ $routeProvider.when("/assignment/create/tribe/run/:id", {templateUrl: "partials/public/create/assignment_tribe.html?v=1521293430023", controller: "AssignmentTribeCtrl"});
+ $routeProvider.when("/about", {templateUrl: "partials/public/view/about.html?v=1521293430023", controller: "AboutCtrl"});
+ $routeProvider.when("/revisions/:id", {templateUrl: "partials/public/view/revisions.html?v=1521293430023", controller: "RevisionCtrl"});
+ $routeProvider.when("/review/:id/view", {templateUrl: "partials/public/view/review.html?v=1521293430023", controller: "ReviewCtrl"});
+ $routeProvider.when("/review/:id/:action", {templateUrl: "partials/public/edit/review.html?v=1521293430023", controller: "ReviewCtrl"});
+ $routeProvider.when("/register", {templateUrl: "partials/public/view/register.html?v=1521293430023", controller: "RegisterCtrl"});
+ $routeProvider.when("/login", {templateUrl: "partials/public/view/login.html?v=1521293430023", controller: "LoginCtrl"});
+ $routeProvider.when("/recover", {templateUrl: "partials/public/view/recover.html?v=1521293430023", controller: "RecoverCtrl"});
+
+ // ADMINSTRATION ROUTING
+ $routeProvider.when("/administration", {templateUrl: "partials/admin/view/home.html?v=1521293430023", controller: "AdminHomeCtrl"});
+ $routeProvider.when("/administration/versions", {templateUrl: "partials/admin/view/versions.html?v=1521293430023", controller: "AdminVersionCtrl"});
+ $routeProvider.when("/administration/runs/page/:page", {templateUrl: "partials/admin/view/runs.html?v=1521293430023", controller: "AdminRunsCtrl"});
+ $routeProvider.when("/administration/run/-1/create", {templateUrl: "partials/admin/create/run.html?v=1521293430023", controller: "AdminRunCtrl"});
+ $routeProvider.when("/administration/assignment/create/run/:id", {templateUrl: "partials/admin/create/assignment.html?v=1521293430023", controller: "AdminAssignmentCtrl"});
+ $routeProvider.when("/administration/assignment/creatematrix/run/:id", {templateUrl: "partials/admin/create/matrix_assignment.html?v=1521293430023", controller: "AdminMatrixAssignmentCtrl"});
+ $routeProvider.when("/administration/run/:id/edit", {templateUrl: "partials/admin/edit/run.html?v=1521293430023", controller: "AdminRunCtrl"});
+ $routeProvider.when("/administration/runs", {redirectTo: "/administration/runs/page/1"});
+ $routeProvider.when("/administration/tribes/create", {templateUrl: "partials/admin/create/tribe.html?v=1521293430023", controller: "AdminTribesCtrl"});
+ $routeProvider.when("/administration/tribes", {templateUrl: "partials/admin/view/tribes.html?v=1521293430023", controller: "AdminTribesCtrl"});
+ $routeProvider.when("/administration/platforms", {templateUrl: "partials/admin/view/platforms.html?v=1521293430023", controller: "AdminPlatformsCtrl"});
+ $routeProvider.when("/administration/users/page/:page", {templateUrl: "partials/admin/view/users.html?v=1521293430023", controller: "AdminUsersCtrl"});
+ $routeProvider.when("/administration/user/:username/edit", {templateUrl: "partials/admin/edit/user.html?v=1521293430023", controller: "AdminUserCtrl"});
+ $routeProvider.when("/administration/user/:username/create", {templateUrl: "partials/admin/create/user.html?v=1521293430023", controller: "AdminUserCtrl"});
+ $routeProvider.when("/administration/users", {redirectTo: "/administration/users/page/1"});
+ $routeProvider.when("/administration/setting", {templateUrl: "partials/admin/view/settings.html?v=1521293430023", controller: "AdminSettingCtrl"});
+ $routeProvider.when("/administration/log", {templateUrl: "partials/admin/view/log.html?v=1521293430023", controller: "AdminLogCtrl"});
+ $routeProvider.when("/administration/database", {templateUrl: "partials/admin/view/database.html?v=1521293430023", controller: "AdminDatabaseCtrl"});
+ $routeProvider.when("/administration/reviews", {templateUrl: "partials/admin/view/reviews.html?v=1521293430023", controller: "AdminReviewsCtrl"});
+ $routeProvider.when("/administration/projects", {templateUrl: "partials/admin/view/projects.html?v=1521293430023", controller: "AdminProjectsCtrl"});
+ $routeProvider.when("/administration/project/:id/edit", {templateUrl: "partials/admin/edit/project.html?v=1521293430023", controller: "AdminProjectCtrl"});
+ $routeProvider.otherwise({redirectTo: "/"});
+
+ }).run(["$rootScope", "$injector", "sessionService", function ($rootScope, $injector, sessionService) {
+ $injector.get("$http").defaults.transformRequest = function (data, headersGetter) {
+ var _t = sessionService.getToken();
+ if (_t) {
+ headersGetter()["Synergy-Authorization"] = _t;
+ }
+ return data;
+ };
+ /*
+ Receive emitted message and broadcast it.
+ Event names must be distinct or browser will blow up!
+ */
+ $rootScope.$on("handleEmit", function (event, args) {
+ $rootScope.$broadcast("handleBroadcast", args);
+ });
+ }]);
\ No newline at end of file
diff --git a/synergy/client/app/js/configuration.js b/synergy/client/app/js/configuration.js
new file mode 100755
index 0000000..b2833b4
--- /dev/null
+++ b/synergy/client/app/js/configuration.js
@@ -0,0 +1,393 @@
+"use strict";
+(function () {
+ /**
+ *
+ * Container for synergy configuration
+ * @type Synergy
+ */
+ function Synergy(SynergyHandlers) {
+ var synergy = this;
+ this.version = "1.0.12";
+ this.hostname = window.location.hostname;
+ this.baseURL = this.hostname + "/synergy";
+ this.bugtrackingSystems = {};
+ this.issues = new function () {
+ this.singleIssueLink = function (project, issue, includeText) {
+ if (synergy.bugtrackingSystems.hasOwnProperty(project) && typeof synergy.bugtrackingSystems[project].getDisplayLink === "function") {
+ return synergy.bugtrackingSystems[project].getDisplayLink(issue, includeText);
+ }
+ return "";
+ };
+ this.viewLinkObjects = function (project, issues, includeText) {
+ if (issues instanceof Array) {
+ return this.viewLink(project, issues);
+ }
+ var _all = [];
+ for (var i in issues) {
+ if (issues.hasOwnProperty(i)) {
+ _all.push(issues[i]);
+ }
+ }
+ return this.viewLink(project, _all, includeText);
+ };
+
+ this.viewLink = function (project, issues, includeText) {
+ if (synergy.bugtrackingSystems.hasOwnProperty(project) && typeof synergy.bugtrackingSystems[project].getMultiDisplayLink === "function") {
+ return synergy.bugtrackingSystems[project].getMultiDisplayLink(issues, includeText);
+ }
+ return "";
+ };
+ this.reportLink = function (project, product, component, version, summary, caseId, suiteId) {
+ if (synergy.bugtrackingSystems.hasOwnProperty(project) && typeof synergy.bugtrackingSystems[project].getReportLink === "function") {
+ return synergy.bugtrackingSystems[project].getReportLink(product, component, version, summary, caseId, suiteId);
+ }
+ return "";
+ };
+ };
+
+ this.assignmentPage = 20;
+ this.commentsPage = 30;
+ this.adminRoles = ["admin", "manager"];
+ this.publisher = new SynergyHandlers.SynergyObserver();
+ this.logger = new SynergyHandlers.SynergyLogger();
+ this.httpTimeout = 60000;
+ /**
+ * Fallback for backward compatibility (in prev. versions of Synergy, specification didn't have project property
+ * @type String
+ */
+ this.product = "NetBeans";
+
+ /**
+ * If true, Synergy will track how long was given case being tested and update estimated case duration with this value after submitting
+ * @type Boolean
+ */
+ this.trackCaseDuration = true;
+
+ /**
+ * Default number of miliseconds before cookie is expired
+ * @type Number
+ */
+ this.defaultCookiesExpiration = 12 * 60 * 60 * 1000;
+ this.uploadFileLimit = 20000000;
+ /**
+ * For dialogs. Properties modal, modalBody and modalHeader are IDs (with #) of elements in modal div
+ * Sample usage: $scope.SYNERGY.modal.update("Login failed", "Incorrect credentials, please try again" + data.toString());
+ $scope.SYNERGY.modal.show();
+ * @type type
+ */
+ this.modal = {
+ modal: "#myModal",
+ modalBody: "#modal-body",
+ modalHeader: "#myModalLabel",
+ update: function (header, body) {
+ $(this.modalHeader).text(header);
+ $(this.modalBody).text(body);
+ },
+ show: function () {
+ $(this.modal).modal("toggle");
+ }
+ };
+
+
+ this.server = new SynergyHandlers.SynergyServer({
+ "db": "../../server/api/db.php",
+ "specifications": "../../server/api/specifications.php",
+ "specification": "../../server/api/specification.php",
+ "session": "../../server/api/login.php",
+ "assignment": "../../server/api/assignment.php",
+ "assignments": "../../server/api/assignments.php",
+ "assignment_bugs": "../../server/api/assignment_bugs.php",
+ "tribe_assignments": "../../server/api/tribe_assignments.php",
+ "attachment": "../../server/api/attachment.php",
+ "attachments": "../../server/api/attachments.php",
+ "run_attachment": "../../server/api/run_attachment.php",
+ "favorites": "../../server/api/favorites.php",
+ "favorite": "../../server/api/favorite.php",
+ "suite": "../../server/api/suite.php",
+ "case": "../../server/api/case.php",
+ "cases": "../../server/api/cases.php",
+ "job": "../../server/api/job.php",
+ "label": "../../server/api/label.php",
+ "labels": "../../server/api/labels.php",
+ "log": "../../server/api/log.php",
+ "user": "../../server/api/user.php",
+ "profile_img": "../../server/api/profile_img.php",
+ "users": "../../server/api/users.php",
+ "tribe": "../../server/api/tribe.php",
+ "tribe_specification": "../../server/api/tribe_specification.php",
+ "tribes": "../../server/api/tribes.php",
+ "versions": "../../server/api/versions.php",
+ "version": "../../server/api/version.php",
+ "platform": "../../server/api/platform.php",
+ "platforms": "../../server/api/platforms.php",
+ "runs": "../../server/api/runs.php",
+ "image": "../../server/api/image.php",
+ "images": "../../server/api/images.php",
+ "issue": "../../server/api/issue.php",
+ "proxy": "../../server/api/proxy.php",
+ "run": "../../server/api/run.php",
+ "run_notifications": "../../server/api/run_notifications.php",
+ "events": "../../server/api/events.php",
+ "configuration": "../../server/api/configuration.php",
+ "about": "../../server/api/about.php",
+ "sanitizer": "../../server/api/sanitizer.php",
+ "search": "../../server/api/search.php",
+ "products": "../../server/api/products.php",
+ "revisions": "../../server/api/revisions.php",
+ "statistics": "../../server/api/statistics.php",
+ "statistics_archived": "../../server/data/test_runs/",
+ "statistics_fallback": "../../server/archive/test_runs_data/",
+ "statistics_filter": "../../server/api/statistics_filter.php",
+ "comments": "../../server/api/comments.php",
+ "assignment_comments": "../../server/api/assignment_comments.php",
+ "specification_request": "../../server/api/specification_request.php",
+ "versionLength": "../../server/api/specification_length.php",
+ "assignment_exists": "../../server/api/assignment_exists.php",
+ "review": "../../server/api/review.php",
+ "review_assignment": "../../server/api/review_assignment.php",
+ "reviews": "../../server/api/reviews.php",
+ "projects": "../../server/api/projects.php",
+ "project": "../../server/api/project.php",
+ "register": "../../server/api/register.php",
+ "run_specifications": "../../server/api/run_specifications.php",
+ "run_tribes": "../../server/api/run_tribes.php",
+ "refresh": "../../server/api/refresh.php"
+ });
+ /**
+ * To specify server endpoints used by Synergy Client
+ *
+ */
+
+
+ /**
+ * Holds information about current session and modifies page upon session state
+ */
+ this.session = {
+ isLoggedIn: false,
+ username: "",
+ firstName: "",
+ lastName: "",
+ role: "",
+ created: "",
+ session_id: "",
+ token: "",
+ cookieIsValid: function (creationTime) {
+ return ((new Date().getTime() - parseInt(creationTime, 10)) < synergy.defaultCookiesExpiration);
+ },
+ /**
+ * Hides login form after successful login
+ *
+ */
+ hideLoginForm: function () {
+ $("#synergy_login_form").css("display", "none");
+ $("#synergy_login_form_log").css("display", "none");
+ },
+ clearSession: function () {
+ synergy.session.isLoggedIn = false;
+ synergy.session.username = "";
+ synergy.session.lastName = "";
+ synergy.session.firstName = "";
+ synergy.session.role = "";
+ synergy.session.created = -1;
+ synergy.session.session_id = "";
+ synergy.session.token = "";
+ synergy.session.showLoginForm();
+ synergy.session.hideUserMenu();
+ },
+ setSession: function (data) {
+ synergy.session.hideLoginForm();
+ synergy.session.showUserMenu(data.username);
+ synergy.session.isLoggedIn = true;
+ synergy.session.username = data.username;
+ synergy.session.lastName = data.lastName;
+ synergy.session.firstName = data.firstName;
+ synergy.session.role = data.role;
+ synergy.session.session_id = data.session_id;
+ synergy.session.token = data.session_id;
+ synergy.session.created = data.created;
+ },
+ showUserMenu: function (username) {
+ $("#usermenu_user").html(username + " <b class=\"caret\" id=\"userCaret\"></b>");
+ $("#synergy_usermenu").css("display", "block");
+ },
+ showLoginForm: function () {
+ $("#synergy_login_form").css("display", "block");
+ },
+ hideUserMenu: function () {
+ $("#synergy_usermenu").css("display", "none");
+ },
+ hasAdminRights: function () {
+ if (typeof synergy.session !== "undefined" && typeof synergy.session.role !== "undefined" && synergy.adminRoles.indexOf(synergy.session.role) > -1) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+ /**
+ * Displays user menu that is shown if user is logged in
+ *
+ */
+ createUserMenu: function () {
+ $("#synergy_session").append("<ul class=\"nav pull-right\"><li class=\"dropdown\"><a href=\"#\" class=\"dropdown-toggle btn-primary\" data-toggle=\"dropdown\" style=\"color: white\">" + synergy.session.username + " <b class=\"caret\"></b></a>" +
+ "<ul class=\"dropdown-menu\"><li><a href=\"#favorites\">Favorites</a></li><li><span ng-click=\"logout();\">Logout</span></li>" +
+ "</ul></li></ul>");
+ }
+ };
+
+ /**
+ * Some useful functions
+ */
+ this.util = {
+ /**
+ * Converts associated array (object) to indexed based array
+ * @param {type} data
+ * @returns {Array|Synergy.util.toIndexedArray._a}
+ */
+ toIndexedArray: function (data) {
+ var _a = [];
+ for (var i in data) {
+ if (data.hasOwnProperty(i)) {
+ _a.push(data[i]);
+ }
+ }
+ return _a;
+ },
+ /**
+ * Sets cookie value
+ * @param {type} name
+ * @param {type} value
+ */
+ setCookie: function (name, value) {
+ var date = new Date();
+ date.setTime(date.getTime() + (synergy.defaultCookiesExpiration));
+ var expires = "; expires=" + date.toGMTString();
+ window.document.cookie = name + "=" + value + expires + "; path=/";
+ },
+ /**
+ * Scrolls window so the beginning of element with given ID is visible in viewport
+ * @param {type} elementID
+ * @returns {undefined}
+ */
+ scrollTo: function (elementID) {
+ var positionX = 0;
+ var positionY = 0;
+ var navbar = window.document.getElementById("navbar-top");
+ var element = window.document.getElementById(elementID);
+ while (element !== null) {
+ positionX += element.offsetLeft;
+ positionY += element.offsetTop;
+ element = element.offsetParent;
+ }
+ window.scrollTo(positionX, positionY - navbar.offsetHeight);
+ },
+ /**
+ * Returns cookie value
+ * @param {type} name
+ * @returns {unresolved}
+ */
+ getCookie: function (name) {
+ name += "=";
+ var ca = window.document.cookie.split(";");
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) === " ") {
+ c = c.substring(1, c.length);
+ }
+ if (c.indexOf(name) === 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+ },
+ /**
+ * Deletes cookie
+ * @param {type} name
+ * @returns {undefined}
+ */
+ deleteCookie: function (name) {
+ window.document.cookie = name + "=;; path=/";
+ },
+ encodeHTML: function () {
+ // encodes all <pre> tags
+ var pre = $("pre");
+ pre.html($("<div/>").text(pre.html).html());
+
+ }
+
+
+ };
+
+ /**
+ * Manipulates with cache used in Synergy. This implementation relies on localStorage
+ */
+ this.cache = {
+ /**
+ * If value with given key exists, updates it. Otherwise new record is stored in localstorage
+ */
+ "put": function (key, value) {
+ if (window.localStorage) {
+ window.localStorage.removeItem(key);
+ try {
+ window.localStorage.setItem(key, JSON.stringify(value));
+ } catch (e) {
+ if (e.code === 22 || e.code === 21 || e.code === 20) {
+ this.drop();
+ window.localStorage.setItem(key, JSON.stringify(value));
+ }
+ }
+ }
+ },
+ /**
+ * Removes everything from localStorage
+ * @returns {undefined}
+ */
+ "drop": function () {
+ for (var i = 0, max = window.localStorage.length; i < max; i++) {
+ window.localStorage.removeItem(window.localStorage.key(0));
+ }
+ },
+ "clear": function (key) {
+ if (window.localStorage) {
+ window.localStorage.removeItem(key);
+ }
+ },
+ "get": function (key) {
+ if (window.localStorage) {
+ return JSON.parse(window.localStorage.getItem(key));
+ }
+ return null;
+ }
+
+ };
+
+ /**
+ * URL where should Synergy redirect you to login
+ */
+ this.ssoLoginUrl = "https://netbeans.org/people/login?original_uri=";
+ this.ssoLogoutUrl = "https://netbeans.org/people/logout?original_uri=";
+ this.getLoginRedirectUrl = function (loginUrl, redirectUrl) {
+ loginUrl += encodeURI(redirectUrl);
+ return loginUrl + "?revalidate=1";
+ };
+ this.getLogoutRedirectUrl = function (logoutUrl, redirectUrl) {
+ logoutUrl += encodeURI(redirectUrl);
+ return logoutUrl + "?revalidate=1";
+ };
+
+ this.useSSO = false;
+ }
+
+ angular.module("synergy.core", ["synergy.handlers"])
+ .factory("SynergyCore", ["SynergyHandlers", function (SynergyHandlers) {
+ var _appCore = null;
+ return {
+ init: function () {
+ if (_appCore !== null) {
+ throw new Error("Application already initialized");
+ } else {
+ _appCore = new Synergy(SynergyHandlers);
+ return _appCore;
+ }
+ }
+ };
+ }]);
+})();
\ No newline at end of file
diff --git a/synergy/client/app/js/controllers.js b/synergy/client/app/js/controllers.js
new file mode 100755
index 0000000..33bcdf0
--- /dev/null
+++ b/synergy/client/app/js/controllers.js
@@ -0,0 +1,7283 @@
+"use strict";
+
+(function () {
+ function SampleSubscriber($scope, data, event) {
+// window.console.log("Received: " + event);
+ }
+ /**
+ * Top level main controller. All other controllers are nested and have access to this $scope.
+ * Handles all session related features - log in, log out, update session information in
+ * SYNERGY.session and hides/displays login form
+ * @param {SessionFct} sessionHttp description
+ */
+ function SynergyCtrl($scope, $location, sessionHttp, $cookieStore, $timeout, ngProgress, $templateCache, searchHttp, projectsHttp, sessionService, SynergyApp, specificationCache, SynergyUtils, SynergyCore) {
+ $scope.SYNERGY = SynergyCore.init();
+ SynergyApp.setApp($scope.SYNERGY);
+ specificationCache.resetCurrentSpecification();
+ $scope.suggestions = [];
+ $scope.SYNERGY.publisher.subscribe(function ($scope, data, event) {
+ new SampleSubscriber($scope, data, event);
+ }, "specListLoaded");
+ $scope.location = $location;
+ $scope.$on("$routeChangeStart", function (scope, next, current) { // to hide alert box whenever path is changed
+ $scope.SYNERGY.logger.print = false;
+ });
+
+ $scope.username = "";
+ $scope.password = "";
+ $scope.searchedItem = "";
+ $scope.role = "";
+ $scope.cookieChecked = false; // need to check at least once in case PHPSESSID cookie is different in Synergy.cookie
+ $scope.isLoggedIn = false;
+ $scope.breadcrumbs = [];
+ $scope.busyBrand = "";
+
+ /**
+ * Shows generic dialog asking user to wait
+ */
+ $scope.showWaitDialog = function () {
+ $scope.SYNERGY.modal.update("Processing data...", "Please wait");
+ $scope.SYNERGY.modal.show();
+ };
+
+ /**
+ * Makes Synergy logo glowing (and turning off)
+ * @returns {undefined}
+ */
+ $scope.toggleBusyBrand = function () {
+ $scope.busyBrand = $scope.busyBrand.length > 0 ? "" : "brand_busy";
+ };
+
+ $scope.getLocalTime = function (dateString, useShortMonth) {
+ return SynergyUtils.UTCToLocal(dateString, useShortMonth);
+ };
+ $scope.getDate = function (dateString) {
+ return SynergyUtils.UTCToDate(dateString);
+ };
+ $scope.getLocalDateTime = function (dateString) {
+ return SynergyUtils.UTCToLocalDateTime(dateString);
+ };
+ $scope.getUTCTime = function (dateString) {
+ return SynergyUtils.localToUTC(dateString);
+ };
+
+ /**
+ * General method to show & log with level INFO message from HTTP Factory
+ * @param {type} data
+ * @param {type} status HTTP status
+ */
+ $scope.generalHttpFactoryError = function (data, status) {
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ };
+
+ /**
+ * Turns busy mode on, meaning logo is glowing and displays progress bar at the top of the page
+ */
+ $scope.busyModeOn = function () {
+ $scope.busyBrand = "brand_busy";
+ ngProgress.set(0);
+ $timeout(function () {
+ if (ngProgress.status() < 90) {
+ ngProgress.set(ngProgress.status() + Math.round(Math.random() * (10 - 5 + 1) + 5));
+ }
+ }, 50);
+ window.document.body.style.cursor = "wait";
+ };
+ /**
+ * Logs possible timeout
+ */
+ $scope.$on("possibleTimeout", function () {
+ $scope.SYNERGY.logger.error("Uknown response", "Seems like timeout occurred, please try to reload page", "DEBUG");
+ });
+
+ $scope.$on("hideUserMenu", function () {
+ $scope.SYNERGY.session.hideUserMenu();
+ });
+ $scope.$on("refreshRole", function () {
+ $scope.role = $scope.SYNERGY.session.role;
+ });
+
+ $scope.$on("busyMode", function (event, args) {
+ if (args) {
+ $scope.busyBrand = "brand_busy";
+ } else {
+ window.document.body.style.cursor = "default";
+ $scope.busyBrand = "";
+ ngProgress.complete(100);
+ }
+ });
+
+ /**
+ * Sends only check for user session, if there is none, discards any session information stored in browser
+ */
+ $scope.init = function (callback) {
+ var _c = $cookieStore.get("session"); //user key throws error
+ if ($scope.cookieChecked && _c && $scope.SYNERGY.session.cookieIsValid(_c.created) && typeof _c.token !== "undefined") {// && _c.length > 0
+ //var session = window.JSON.parse(_c);
+ var session = _c;
+ $scope.SYNERGY.session.hideLoginForm();
+ $scope.SYNERGY.session.showUserMenu(session.username);
+ $scope.SYNERGY.session.isLoggedIn = true;
+ $scope.SYNERGY.session.username = session.username;
+ $scope.SYNERGY.session.role = session.role;
+ $scope.SYNERGY.session.lastName = (session.hasOwnProperty("lastName") && session.lastName.length > 0) ? session.lastName : "";
+ $scope.SYNERGY.session.firstName = (session.hasOwnProperty("firstName") && session.firstName.length > 0) ? session.firstName : "";
+ $scope.role = session.role;
+ $scope.SYNERGY.session.session_id = session.session_id;
+ $scope.SYNERGY.session.created = session.created;
+ $scope.SYNERGY.session.token = session.session_id;
+ sessionService.setSession($scope.SYNERGY.session);
+ $timeout(callback, 0);
+ } else {
+ sessionHttp.infoConditional($scope, function (data) {
+ $scope.cookieChecked = true;
+ $scope.SYNERGY.session.hideLoginForm();
+ $scope.SYNERGY.session.showUserMenu(data.username);
+ $scope.SYNERGY.session.isLoggedIn = true;
+ $scope.SYNERGY.session.username = data.username;
+ $scope.SYNERGY.session.lastName = data.lastName;
+ $scope.SYNERGY.session.firstName = data.firstName;
+ $scope.SYNERGY.session.role = data.role;
+ $scope.role = data.role;
+ $scope.SYNERGY.session.session_id = data.session_id;
+ $scope.SYNERGY.session.token = data.session_id;
+ $scope.SYNERGY.session.created = data.created;
+ sessionService.setSession($scope.SYNERGY.session);
+ $cookieStore.put("session", ({firstName: data.firstName, lastName: data.lastName, username: data.username, role: data.role, token: data.token, created: 1000 * parseInt(data.created, 10), session_id: data.session_id}));
+ callback();
+ }, function (data) {
+ $cookieStore.remove("session");
+ sessionService.clearSession();
+ $scope.cookieChecked = false;
+ // $scope.SYNERGY.session.showLoginForm();
+ $scope.SYNERGY.session.hideUserMenu();
+ callback();
+ });
+ }
+ };
+
+ /**
+ * Redirects to login page if SSO is not used. If SSO is used, it sends "active" request to server. Server checks SSO session
+ * and if session exists and it's valid, returns user information. If session is not valid, server returns HTTP 307 and client redirects to
+ * SSO login page.
+ */
+ $scope.login = function () {
+ if (!$scope.SYNERGY.useSSO) {
+ $location.path("login");
+ } else {
+ sessionHttp.get($scope, function (data) {
+ $scope.SYNERGY.session.isLoggedIn = true;
+ $scope.SYNERGY.session.username = data.username;
+ $scope.SYNERGY.session.role = data.role;
+ $scope.role = data.role;
+ $scope.SYNERGY.session.lastName = data.lastName;
+ $scope.SYNERGY.session.firstName = data.firstName;
+ $scope.SYNERGY.session.created = 1000 * parseInt(data.created, 10);
+ $scope.SYNERGY.session.session_id = data.session_id;
+ $scope.SYNERGY.session.token = data.session_id;
+ $scope.SYNERGY.session.hideLoginForm();
+ $scope.SYNERGY.session.showUserMenu(data.username);
+ sessionService.setSession($scope.SYNERGY.session);
+ $cookieStore.put("session", ({username: data.username, role: data.role, token: data.token, created: 1000 * parseInt(data.created, 10), session_id: data.session_id}));
+ window.location.reload();
+ }, function (data, status) {
+ $scope.SYNERGY.logger.log("Action failed", data + ":" + status, "DEBUG", "alert-error");
+ sessionService.clearSession();
+ status = parseInt(status, 10);
+ switch (status) {
+ case 307:
+ window.location.href = $scope.SYNERGY.getLoginRedirectUrl($scope.SYNERGY.ssoLoginUrl, window.location.href);
+ break;
+ case 400:
+ $scope.SYNERGY.logger.log("Login failed", data, "INFO", "alert-error");
+ break;
+ default:
+ $scope.SYNERGY.logger.log("Login failed", "Incorrect credentials, please try again ", "INFO", "alert-error");
+ break;
+ }
+ });
+ }
+ };
+
+ /**
+ * Sends logout request to serverpr
+ */
+ $scope.logout = function () {
+ $cookieStore.remove("session");
+ $scope.SYNERGY.session.clearSession();
+ $scope.role = "";
+ sessionService.clearSession();
+ sessionHttp.logout($scope, function (data) {
+ if ($scope.SYNERGY.useSSO) {
+ window.location.href = $scope.SYNERGY.getLogoutRedirectUrl($scope.SYNERGY.ssoLogoutUrl, (window.location.href));
+ } else {
+ window.location.reload();
+ }
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Logout failed", data.toString(), "INFO", "alert-error");
+ });
+ };
+
+ /**
+ * Listens to updateNavbar and highlights received link in top nav bar
+ */
+ $scope.$on("updateNavbar", function (event, args) {
+ try {
+ $("ul#navbar li").each(function () {
+ if ($(this).attr("class") === "active") {
+ $(this).attr("class", "");
+ }
+ });
+ } catch (e) {
+ }
+ try {
+ $("ul#navbar li#" + args.item).attr("class", "active");
+ } catch (e) {
+ }
+ });
+
+
+ function splitMergeBreadCrumbs(currentTitle, currentTitleIndex, breadCrumbs) {
+ var p1 = breadCrumbs.slice(0, currentTitleIndex);
+ var p2 = breadCrumbs.slice(currentTitleIndex + 1);
+ p2.push(currentTitle);
+ return p1.concat(p2);
+ }
+
+ /**
+ * Updates breadcrumbs menu
+ */
+ $scope.$on("updateBreadcrumbs", function (event, args) {
+ var i = 0;
+ if ($scope.breadcrumbs.length === 5) {
+ for (i = 0; i < 5; i++) {
+ if (typeof $scope.breadcrumbs[i] !== "undefined" && $scope.breadcrumbs[i].title === args.title) {
+ $scope.breadcrumbs = splitMergeBreadCrumbs(args, i, $scope.breadcrumbs);
+ return;
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ $scope.breadcrumbs[i] = $scope.breadcrumbs[i + 1];
+ }
+ $scope.breadcrumbs[4] = args;
+ } else {
+ for (i = 0; i < 4; i++) {
+ if (typeof $scope.breadcrumbs[i] !== "undefined" && $scope.breadcrumbs[i].title === args.title) {
+ $scope.breadcrumbs = splitMergeBreadCrumbs(args, i, $scope.breadcrumbs);
+ return;
+ }
+ }
+
+ if (typeof $scope.breadcrumbs[$scope.breadcrumbs.length] === "undefined" && (typeof $scope.breadcrumbs[$scope.breadcrumbs.length - 1] === "undefined" || $scope.breadcrumbs[$scope.breadcrumbs.length - 1].title !== args.title)) {
+ $scope.breadcrumbs[$scope.breadcrumbs.length] = args;
+ }
+ }
+ });
+
+ /**
+ * Listens on key press (Enter) when cursor is in search field
+ */
+ $scope.synergySearch = function () {
+ $location.path("search/" + ($scope.searchedItem));
+ };
+
+ $scope.goToSearch = function (suggestedItem) {
+ $location.path(suggestedItem.link);
+ };
+
+ $scope.searchAhead = function () {
+ if ($scope.searchedItem.length < 2) {
+ return;
+ }
+ searchHttp.getFewSpecifications($scope, $scope.searchedItem, function (data) {
+ var a = [];
+ for (var i = 0, max = data.length; i < max; i++) {
+ if (data[i].project === null || data[i].project.length < 1) {
+ a.push({title: data[i].title + " (" + $scope.SYNERGY.product + " " + data[i].version + ")", link: data[i].type + "/" + data[i].id});
+ } else {
+ a.push({title: data[i].title + " (" + data[i].project + " " + data[i].version + ")", link: data[i].type + "/" + data[i].id});
+ }
+
+ }
+ $scope.suggestions = a;
+ // $("#typeahead").typeahead({source: a});
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+
+ $scope.encodeURIComponent = function (s) {
+ return encodeURIComponent(s);
+ };
+
+ // register bugtracking link scripts for all projects on page load
+ projectsHttp.getAll($scope, function (data) { // todo add caching
+ for (var i = 0, max = data.length; i < max; i++) {
+ if (data[i].reportLink !== null || data[i].viewLink !== null) {
+ var fnca,
+ fncb,
+ fncc;
+ /* jshint ignore:start */
+ try {
+ fnca = eval("(function(){ var a=" + data[i].viewLink + "; return a;})(); ");
+ } catch (e) {
+ fnca = null;
+ }
+ try {
+ fncb = eval("(function(){ var a=" + data[i].multiViewLink + "; return a;})(); ");
+ } catch (e) {
+ fncb = null;
+ }
+ try {
+ fncc = eval("(function(){ var a=" + data[i].reportLink + "; return a;})(); ");
+ } catch (e) {
+ fncc = null;
+ }
+
+ $scope.SYNERGY.bugtrackingSystems[data[i].name] = {
+ getDisplayLink: fnca,
+ getMultiDisplayLink: fncb,
+ getReportLink: fncc
+ };
+ /* jshint ignore:end */
+ }
+ }
+ }, function () {
+ });
+
+ }
+ function SearchCtrl($scope, $routeParams, searchHttp) {
+
+ $scope.searched = $routeParams.search;
+ $scope.results = [];
+ $scope.escapedSearched = decodeURIComponent($scope.searched);
+
+ $scope.fetch = function () {
+ searchHttp.get($scope, $scope.searched, function (data) {
+ $scope.results = data;
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+
+ $scope.printResult = function (item) {
+ switch (item.type) {
+ case "specification":
+ return "<a href=\"#specification/" + item.id + "\">" + item.title + " (" + $scope.SYNERGY.product + " " + item.version + ")" + "</a>";
+ case "suite":
+ return "<a href=\"#suite/" + item.id + "\">" + item.title + "</a>";
+ default:
+ break;
+ }
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * Loads list of test specifications
+ * @param {SpecificationsFct} specificationsHttp description
+ * @param {VersionsFct} versionsHttp description
+ * @param {UserFct} userFct description
+ * @param {SpecificationFct} specificationFct description
+ */
+ function SpecPoolCtrl($scope, $routeParams, specificationsHttp, versionsHttp) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_specs"});
+ $scope.specs = [];
+ $scope.version = $routeParams.id || null; // selected version
+ $scope.versions = []; // all versions available
+ $scope.projects = [];
+ $scope.orderProp = "title";
+ $scope.rights = 0;
+ $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
+ /**
+ * used for caching specifications in given version. This in-memory cache is valid only while user in on this page
+ */
+ var cache = {};
+
+ /**
+ * Loads data from server
+ */
+ function init() {
+ $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
+ if (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) {
+ $scope.rights = 1;
+ }
+ versionsHttp.get($scope, true, function (data) {
+ data.unshift({id: -1, name: "all"});
+ $scope.versions = data;
+ $scope.version = $scope.version || data[0].name;
+ specificationsHttp.get($scope, $scope.version, function (data) {
+
+
+ var _projects = [];
+ for (var i = 0, imax = data.length; i < imax; i++) {
+ if (data[i].projects.length > 0) {
+ data[i]._project = data[i].projects[0].name;
+ if (_projects.indexOf(data[i].projects[0].name) < 0) {
+ _projects.push(data[i].projects[0].name);
+ }
+ } else {
+ data[i]._project = $scope.SYNERGY.product;
+ data[i].projects = [{name: $scope.SYNERGY.product, id: -2}];
+ if (_projects.indexOf($scope.SYNERGY.product) < 0) {
+ _projects.push($scope.SYNERGY.product);
+ }
+ }
+ }
+ _projects.push("All");
+ $scope.projects = _projects;
+ cache[$scope.version + "projects"] = _projects;
+
+ $scope.specs = data;
+ cache[$scope.version] = data;
+ $scope.$emit("updateBreadcrumbs", {link: "specifications", title: "Test Specifications"});
+
+ }, function (data, status) {
+ if (parseInt(status, 10) !== 404) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "INFO", "alert-error");
+ } else {
+ $scope.SYNERGY.logger.log("", "No results", "INFO");
+ }
+ });
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.init(function () {
+ init();
+ });
+
+ /**
+ * Loads specifications for given version. First it checks cache and if it doesn't contain data for
+ * given version, asks server
+ */
+ $scope.filter = function () {
+
+ if (cache.hasOwnProperty($scope.version)) {
+ $scope.specs = cache[$scope.version];
+ $scope.projects = cache[$scope.version + "projects"];
+ return;
+ }
+
+ specificationsHttp.get($scope, $scope.version, function (data) {
+ var _projects = [];
+ for (var i = 0, imax = data.length; i < imax; i++) {
+ if (data[i].ext.hasOwnProperty("projects") && data[i].ext.projects.length > 0) {
+ data[i]._project = data[i].ext.projects[0].name;
+ if (_projects.indexOf(data[i].ext.projects[0].name) < 0) {
+ _projects.push(data[i].ext.projects[0].name);
+ }
+ } else {
+ data[i]._project = $scope.SYNERGY.product;
+ if (_projects.indexOf($scope.SYNERGY.product) < 0) {
+ _projects.push($scope.SYNERGY.product);
+ }
+ }
+ }
+ _projects.push("All");
+ $scope.projects = _projects;
+ $scope.specs = data;
+ cache[$scope.version] = data;
+ cache[$scope.version + "projects"] = _projects;
+ $scope.$emit("updateBreadcrumbs", {link: "specifications", title: "Test Specifications"});
+
+ }, function (data, status) {
+ if (parseInt(status, 10) !== 404) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ } else {
+ $scope.SYNERGY.logger.log("", "No results", "INFO");
+ }
+ });
+ };
+
+ }
+ /**
+ *
+ * @param {SpecificationsFct} specificationsHttp
+ * @param {RunsFct} runsHttp
+ * @returns {undefined}
+ **/
+ function HomeCtrl($scope, specificationsHttp, runsHttp, calendarHttp) {
+
+ $scope.runs = {testRuns: []};
+ $scope.specs = [];
+ $scope.$emit("updateNavbar", {item: "nav_home"});
+
+ /**
+ * Loads latest test runs and test specifications
+ */
+ $scope.fetch = function () {
+ runsHttp.getLatest($scope, 7, function (data) {
+ if (typeof data.testRuns !== "undefined") {
+
+ data.testRuns.forEach(function (trun) {
+ if (trun.projectName === null || trun.projectName === "") {
+ trun.projectName = $scope.SYNERGY.product;
+ }
+ });
+
+ $scope.runs = data;
+ }
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ specificationsHttp.latest($scope, function (result) {
+ $scope.specs = result;
+ $scope.SYNERGY.publisher.publish(1, $scope, "specListLoaded");
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+
+ function loadCalendar() {
+ calendarHttp.getEvents($scope, function (data) {
+ var items = [];
+
+ for (var i = 0, max = data.length; i < max; i += 1) {
+ items.push({url: "#/run/" + data[i].id, title: data[i].title, start: new Date(data[i].start.substr(0, 4), parseInt(data[i].start.substr(4, 2), 10) - 1, data[i].start.substr(6, 2)), end: new Date(data[i].end.substr(0, 4), parseInt(data[i].end.substr(4, 2), 10) - 1, data[i].end.substr(6, 2))});
+ }
+
+ $("#cal").fullCalendar({
+ header: {
+ left: "prev,next today",
+ center: "title",
+ right: "month,agendaWeek,agendaDay"
+ },
+ editable: true,
+ events: items,
+ eventMouseover: function (event, jsEvent, view) {
+ if (view.name !== "agendaDay") {
+ $(jsEvent.target).attr("title", event.title);
+ }
+ }
+ });
+ }, function () {
+ window.console.log("Failed to load calendar events");
+ });
+ }
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ loadCalendar();
+ });
+ }
+ /**
+ * View 1
+ * @param {RunFct} runHttp
+ * @param {AssignmentFct} assignmentHttp description
+ * @param {AttachmentFct} attachmentHttp description
+ * @returns {undefined}
+ */
+ function RunCtrl($scope, utils, $location, $routeParams, runHttp, assignmentHttp, attachmentHttp, reviewHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {
+
+ $scope.$emit("updateNavbar", {item: "nav_runs"});
+ $scope.$emit("updateBreadcrumbs", {link: "runs", title: "Test Runs"});
+ $scope.id = $routeParams.id || -1;
+ $scope.run = {};
+ $scope.rights = 0;
+ var currentActionId = -1;
+ var currentAction = "";
+ $scope.username = $scope.SYNERGY.session.username || "";
+ $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
+ $scope.isLoading = false;
+ $scope.tribes = [];
+ var tribes = [];
+ var leaderIsRemoving = false;
+ $scope.explainModal = "";
+ $scope.filter = {
+ "assignee": "All",
+ "specification": "All",
+ "platform": "All",
+ "tribe": "All"
+ };
+ var issueCollector = new SynergyIssue.RunIssuesCollector();
+ $scope.P1Issues = [];
+ $scope.P2Issues = [];
+ $scope.P3Issues = [];
+ $scope.unresolvedIssues = [];
+ $scope.allIssues = [];
+ $scope.specifications = [];
+ $scope.assignees = [];
+ $scope.project = {"name": "", "id": -1};
+ $scope.pageSize = $scope.SYNERGY.assignmentPage;
+ var _coverage = {};
+ $scope.coverage = {};
+ $scope.sortConfig = {
+ property: ["id"],
+ descending: [false]
+ };
+ $scope.orderingProperties = {
+ "userDisplayName": {
+ "desc": false,
+ "asc": false
+ },
+ "specification": {
+ "desc": false,
+ "asc": false
+ },
+ "platform": {
+ "desc": false,
+ "asc": false
+ }
+ };
+ $scope.testRunIsAvailable = false;
+ $scope.pageReviewsExpanded = false;
+
+ /**
+ * Loads test run
+ */
+ $scope.fetch = function () {
+ tribes = [];
+ $scope.run = {};
+ issueCollector = new SynergyIssue.RunIssuesCollector();
+ $scope.P1Issues = [];
+ $scope.P2Issues = [];
+ $scope.P3Issues = [];
+ $scope.unresolvedIssues = [];
+ $scope.allIssues = [];
+ $scope.specifications = [];
+ $scope.assignees = [];
+ $scope.project = {"name": "", "id": -1};
+ _coverage = {};
+ $scope.coverage = {};
+ $scope.username = $scope.SYNERGY.session.username || "";
+ runHttp.get($scope, $scope.id, function (data) {
+ setProject(data);
+ countResults(data);
+ setTestRunIsAvailable(data);
+ $scope.run = data;
+ collectFilterData(data);
+ $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/1", title: $scope.run.title});
+ SynergyUtils.ProgressChart([((data.completed / data.total) * 100), 100 - ((data.completed / data.total) * 100)], ["#08c", "#ccc"], ["completed", ""], "canvas2");
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function setTestRunIsAvailable(testRun) {
+ var start = $scope.getDate(testRun.start);
+ var stop = $scope.getDate(testRun.end);
+ var today = new Date();
+ $scope.testRunIsAvailable = (today >= start && today <= stop);
+ }
+
+ function setProject(data) {
+ if (data.projectName !== null) {
+ $scope.project = {"name": data.projectName, "id": data.projectId};
+ } else {
+ $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
+ }
+ }
+
+ $scope.toggleReivewSection = function () {
+ $scope.pageReviewsExpanded = !$scope.pageReviewsExpanded;
+ $("#pageReviews").collapse("toggle");
+ };
+
+ function collectFilterData(data) {
+ var assignees = [];
+ var platforms = [];
+ var specifications = [];
+ var tribes = [];
+ for (var i = 0, max = data.assignments.length; i < max; i++) {
+ if (assignees.indexOf(data.assignments[i].userDisplayName) < 0) {
+ assignees.push(data.assignments[i].userDisplayName);
+ }
+ if (specifications.indexOf(data.assignments[i].specification) < 0) {
+ specifications.push(data.assignments[i].specification);
+ }
+ if (platforms.indexOf(data.assignments[i].platform) < 0) {
+ platforms.push(data.assignments[i].platform);
+ }
+ for (var j = 0, max2 = data.assignments[i].tribes.length; j < max2; j++) {
+ if (tribes.indexOf(data.assignments[i].tribes[j]) < 0) {
+ tribes.push(data.assignments[i].tribes[j]);
+ }
+ }
+ }
+ assignees.push("All");
+ assignees.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+
+ specifications.push("All");
+ specifications.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ tribes.push("All");
+ tribes.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+
+ platforms.push("All");
+ platforms.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+
+ $scope.assignees = assignees;
+ $scope.platforms = platforms;
+ $scope.specifications = specifications;
+ $scope.tribes = tribes;
+ }
+
+ $scope.assignessFilter = function (assignmentRecord) {
+ if ($scope.filter.assignee && $scope.filter.assignee !== "All" && assignmentRecord.userDisplayName !== $scope.filter.assignee) {
+ return false;
+ }
+ if ($scope.filter.specification && $scope.filter.specification !== "All" && assignmentRecord.specification !== $scope.filter.specification) {
+ return false;
+ }
+ if ($scope.filter.platform && $scope.filter.platform !== "All" && assignmentRecord.platform !== $scope.filter.platform) {
+ return false;
+ }
+ if ($scope.filter.tribe && $scope.filter.tribe !== "All" && assignmentRecord.tribes.indexOf($scope.filter.tribe) < 0) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * Counts number of passed/failed/skipped cases
+ * @param {TestRun} run
+ */
+ function countResults(run) {
+ var result = {
+ "failed": 0,
+ "passed": 0,
+ "skipped": 0
+ };
+
+ if (SynergyUtils.definedNotNull(run) && SynergyUtils.definedNotNull(run.assignments)) {
+ for (var i = 0, max = run.assignments.length; i < max; i++) {
+ if (!_coverage[run.assignments[i].platform]) {// FIXME
+ _coverage[run.assignments[i].platform] = {total: 0, completed: 0, name: run.assignments[i].platform};
+ }
+ issueCollector.addIssues(run.assignments[i].issues);
+ _coverage[run.assignments[i].platform].total += parseInt(run.assignments[i].total, 10);
+ _coverage[run.assignments[i].platform].completed += parseInt(run.assignments[i].completed, 10);
+ result.failed += Math.floor(run.assignments[i].failed);
+ result.passed += Math.floor(run.assignments[i].passed);
+ result.skipped += Math.floor(run.assignments[i].skipped);
+ }
+
+ var t = (result.failed + result.passed + result.skipped) / 100;
+ if (t > 0) {
+ var f = Math.floor(result.failed * 10 / t) / 10;
+ var p = Math.round(result.passed * 10 / t) / 10;
+ SynergyUtils.ProgressChart([p, f, Math.round(10 * (100 - (f + p))) / 10], ["#62c462", "#ee5f5b", "#c67605"], ["passed", "failed", "skipped"], "canvas1");
+ }
+ }
+
+ for (var j in _coverage) {
+ if (_coverage.hasOwnProperty(j)) {
+ _coverage[j].progress = Math.round(10 * 100 * (_coverage[j].completed / _coverage[j].total)) / 10;
+ }
+ }
+ $scope.coverage = _coverage;
+ SynergyUtils.ProgressChart([issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100), 100 - (issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100))], ["#ccc", "#62c462"], ["Unresolved", "Resolved"], "issuesResolution");
+ SynergyUtils.ProgressChart([issueCollector.issuesStats.P1 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P2 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P3 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P4 / (issueCollector.issuesStats.total / 100)], ["#ee5f5b", "#f89406", "#fbeed5", "#ccc"], ["P1 (" + issueCollector.issuesStats.P1 + ")", "P2 (" + issueCollector.issuesStats.P2 + ")", "P3 (" + issueColle [...]
+ $scope.allIssues = issueCollector.issues;
+ $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
+ $scope.P1Issues = issueCollector.issuesStats.P1Issues;
+ $scope.P2Issues = issueCollector.issuesStats.P2Issues;
+ $scope.P3Issues = issueCollector.issuesStats.P3Issues;
+
+ }
+ $scope.createCoverageChart = function (c) {
+ try {
+ SynergyUtils.ProgressChart([c.progress, 100 - c.progress], ["#08c", "#ccc"], ["finished", ""], "coverage" + c.name);
+ } catch (e) {
+ }
+ };
+
+ $scope.nextPage = function () {
+ $scope.isLoading = true;
+ $scope.pageSize += $scope.SYNERGY.assignmentPage;
+ $scope.isLoading = false;
+ };
+
+ function resetFilters() {
+ $scope.sortConfig = {
+ "property": ["id"],
+ "descending": [false]
+ };
+ $scope.orderingProperties = {
+ "userDisplayName": {
+ "desc": false,
+ "asc": false
+ },
+ "specification": {
+ "desc": false,
+ "asc": false
+ },
+ "platform": {
+ "desc": false,
+ "asc": false
+ }
+ };
+ }
+
+ $scope.changeSorting = function (prop, order) {
+ var _p = prop;
+ prop = order ? prop : "-" + prop;
+ if ($scope.sortConfig.property.indexOf(_p) > -1 || $scope.sortConfig.property.indexOf("-" + _p) > -1) {
+ var i = $scope.sortConfig.property.indexOf(_p);
+ if (i < 0) {
+ i = $scope.sortConfig.property.indexOf("-" + _p);
+ }
+ if ($scope.sortConfig.descending[i] === order) { // click on the same order arrow => remove it from filter
+ $scope.sortConfig.property.splice(i, 1);
+ $scope.sortConfig.descending.splice(i, 1);
+ $scope.orderingProperties[_p].desc = false; // reset css for this property
+ $scope.orderingProperties[_p].asc = false;
+ if ($scope.sortConfig.property.length === 0) { // resel css to default values and reset filter to ID
+ resetFilters();
+ }
+ } else {
+ $scope.orderingProperties[_p].desc = !$scope.orderingProperties[_p].desc; // invert css
+ $scope.orderingProperties[_p].asc = !$scope.orderingProperties[_p].asc;
+ var _orig = {// just place holder
+ "descending": !$scope.sortConfig.descending[i],
+ "property": $scope.sortConfig.property[i]
+ };
+ $scope.sortConfig.descending.splice(i, 1); // remove it from array of filters
+ $scope.sortConfig.property.splice(i, 1);
+ $scope.sortConfig.descending.splice(0, 0, _orig.descending); // insert it to level of filters at the beginning
+ (_orig.property.indexOf("-") === 0) ? $scope.sortConfig.property.splice(0, 0, _orig.property.substring(1, _orig.property.length)) : $scope.sortConfig.property.splice(0, 0, "-" + _orig.property);
+ }
+ } else {
+ if ($scope.sortConfig.property.length === 1 && $scope.sortConfig.property[0] === "id") { // if no ordering so far, simply replace ID with selected property
+ $scope.sortConfig = {
+ property: [prop],
+ descending: [order]
+ };
+ } else {
+ $scope.sortConfig.property.splice(0, 0, prop); // insert at the beginning
+ $scope.sortConfig.descending.splice(0, 0, order);
+ }
+
+ $scope.orderingProperties[_p].desc = !order; // css update
+ $scope.orderingProperties[_p].asc = order;
+ }
+ };
+
+ $scope.bugs = [];
+ $scope.bugsAssignmentId = -1;
+
+ $scope.alterBugs = function (assignment) {
+ $scope.bugsAssignmentId = assignment.id;
+ var a = [];
+ for (var i = 0, max = assignment.issues.length; i < max; i++) {
+ a.push({
+ "id": assignment.issues[i].bugId,
+ "stillValid": true,
+ "changeCount": false
+ });
+ }
+ $scope.bugs = a;
+ $("#ticketsModal").modal("toggle");
+ };
+
+ $scope.performAlterBugs = function () {
+ var newIssues = [];
+ var countDiff = 0;
+ for (var i = 0, max = $scope.bugs.length; i < max; i++) {
+ if ($scope.bugs[i].stillValid) {
+ newIssues.push($scope.bugs[i].id);
+ } else {
+ if ($scope.bugs[i].changeCount) {
+ countDiff++;
+ }
+ }
+ }
+
+ assignmentHttp.alterBugs($scope, $scope.bugsAssignmentId, {issues: newIssues.join(";"), diffCount: countDiff}, function () {
+ $scope.SYNERGY.logger.log("Done", "Issues updated", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+
+
+ $("#ticketsModal").modal("toggle");
+ };
+ /**
+ * Redirects to page so user starts testing and completing his assignments
+ * @param {Number} mode
+ * @param {Number} assignmentId assignment ID
+ */
+ $scope.startAssignment = function (mode, assignmentId) {
+ if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
+ $("#deleteModalLabel").text("Restart assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved progress will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "restartAssignment";
+ currentActionId = assignmentId;
+ } else {
+ $location.path("/assignment/" + assignmentId + "/v/" + mode);
+ }
+ };
+
+ $scope.startReviewAssignment = function (mode, assignmentId) {
+ if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
+ $("#deleteModalLabel").text("Restart assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved comments will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "restartReviewAssignment";
+ currentActionId = assignmentId;
+ } else {
+ $location.path("/review/" + assignmentId + "/continue");
+ }
+ };
+
+ /**
+ * Starts with action on given test run. If the action name is different than "delete", redirection is done.
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ */
+ $scope.performRun = function (action) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test run?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test run?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteRun";
+ break;
+ case "notify":
+ $("#deleteModalLabel").text("Send notifications?");
+ $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "notify";
+ break;
+ case "freeze":
+ var target = ($scope.run.isActive ? 0 : 1);
+ runHttp.freezeRun($scope, $scope.id, target, function (data) {
+ $scope.run.isActive = target;
+ $scope.SYNERGY.logger.log("Done", "Test run " + (target === 1 ? "unfrozen" : "frozen"), "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ $location.path("/administration/run/" + $scope.id + "/" + action);
+ break;
+ }
+ };
+
+ /**
+ * Starts with action on given test assignment
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id assignment ID
+ */
+ $scope.performAssignment = function (action, id, createdBy) {
+ if (action !== "delete") {
+ $location.path("suite/" + id + "/" + action);
+ } else {
+ switch (action) {
+ case "delete":
+ currentAction = "deleteAssignment";
+ currentActionId = id;
+ leaderIsRemoving = (createdBy === 3) ? true : false;
+ $("#deleteModalLabel").text("Delete test assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ $scope.performReviewAssignment = function (action, id, createdBy) {
+ switch (action) {
+ case "delete":
+ currentAction = "deleteReviewAssignment";
+ currentActionId = id;
+ leaderIsRemoving = (createdBy === 3) ? true : false;
+ $("#deleteModalLabel").text("Delete review assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete review assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ break;
+ default:
+ $location.path("/review/" + id + "/" + action);
+ break;
+ }
+ };
+
+ function deleteReviewAssignment() {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ reviewHttp.remove($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.deleteAssignment = function () {
+ if (!leaderIsRemoving) {
+
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ assignmentHttp.remove($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ } else {
+ $("#explainModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.explanation.length < 1) {
+ return;
+ }
+ assignmentHttp.removeByLeader($scope, currentActionId, $scope.explanation, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ /**
+ * Executes some action based on value of $scope.currentAction
+ */
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "restartAssignment":
+ $("#deleteModal").modal("toggle");
+ $location.path("/assignment/" + currentActionId + "/v/2");
+ break;
+ case "restartReviewAssignment":
+ $("#deleteModal").modal("toggle");
+ $location.path("/review/" + currentActionId + "/restart");
+ break;
+ case "deleteAssignment":
+ $("#deleteModal").modal("toggle");
+ leaderIsRemoving ? $("#explainModal").modal("toggle") : $scope.deleteAssignment();
+ break;
+ case "deleteReviewAssignment":
+ $("#deleteModal").modal("toggle");
+ deleteReviewAssignment();
+ break;
+ case "notify":
+ $("#deleteModal").modal("toggle");
+ runHttp.sendNotifications($scope, $scope.id, function (data) {
+ $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ break;
+ case "deleteRun":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ runHttp.remove($scope, $scope.id, function (data) {
+ $scope.SYNERGY.modal.update("Test run removed", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("/runs");
+ }, function (data) {
+ $scope.SYNERGY.modal.update("Action failed", "");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.SYNERGY.modal.show();
+ });
+ break;
+ case "deleteAttachment":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.fetch();
+ });
+ break;
+ default:
+ break;
+ }
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ // ATTACHMENT UPLOAD HANDLING
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ $scope.fetch();
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ });
+ }
+
+
+
+ function RunCtrlCase($scope, utils, $location, $routeParams, runHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {
+ $scope.$emit("updateNavbar", {item: "nav_runs"});
+ $scope.id = $routeParams.id || -1;
+
+ $scope.run = {};
+ $scope.project = {"name": "", "id": -1};
+ $scope.isLoading = false;
+ $scope.testRunIsAvailable = false;
+ $scope.specs = [];
+ $scope.overview = {
+ totalTc: 0,
+ pRate: 0,
+ testers: 0,
+ duration: "",
+ time: ""
+ };
+
+ $scope.labels = [];
+ $scope.selectedLabels = [];
+ $scope.resultFilter = {
+ passed: true,
+ failed: true,
+ skipped: true,
+ passed_with_issues: true
+ };
+
+ var allExpanded = false;
+
+ function setProject(data) {
+ if (data.projectName !== null) {
+ $scope.project = {"name": data.projectName, "id": data.projectId};
+ } else {
+ $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
+ }
+ }
+
+ /**
+ * Loads test run
+ */
+ $scope.fetch = function () {
+ $scope.project = {"name": "", "id": -1};
+ $scope.isLoading = true;
+ $scope.run = {};
+ runHttp.getBlobs($scope, $scope.id, function (data) {
+ $scope.run = data;
+ setProject(data);
+ $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/3", title: $scope.run.title});
+ $scope.isLoading = false;
+ buildData();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.toggleExpand = function (item) {
+ item.expanded = !item.expanded;
+ if (item.suites) {
+ $("#spec" + item.id).collapse("toggle");
+ } else {
+ $("#suite" + item.id).collapse("toggle");
+ }
+ };
+
+ $scope.toggleAll = function () {
+ if (allExpanded) {
+ $(".collapse").collapse("hide");
+ } else {
+ $(".collapse").collapse("show");
+ }
+ allExpanded = !allExpanded;
+ };
+
+ function buildData() {
+ var arrObj = {};
+ var knownSpecs = [];
+ var _currentSuite;
+ var _currentSpec;
+
+ var totalTc = 0;
+ var passedTc = 0;
+ var failedTc = 0;
+ var skippedTc = 0;
+ var totalMins = 0;
+
+ $scope.run.durations.forEach(function (x) {
+ totalMins += x.duration;
+ });
+
+ var passedWithIssues = 0;
+ var _users = [];
+ var _allLabels = [];
+
+ $scope.run.blobs.forEach(function (blob) {
+ if (_users.indexOf(blob.user) < 0) {
+ _users.push(blob.user);
+ }
+ if (blob.label.length > 0 && _allLabels.indexOf(blob.label) < 0) {
+ _allLabels.push(blob.label);
+ }
+
+ if (knownSpecs.indexOf(blob.specification.id) < 0) {
+ knownSpecs.push(blob.specification.id);
+ arrObj["_" + blob.specification.id] = {
+ name: blob.specification.name,
+ id: blob.specification.id,
+ expanded: false,
+ label: blob.label,
+ version: blob.specification.version,
+ suites: {}
+ };
+ }
+ _currentSpec = arrObj["_" + blob.specification.id];
+ // for each suite
+ blob.specification.suites.forEach(function (suite) {
+
+ if (!_currentSpec.suites.hasOwnProperty("_" + suite.id)) {
+ _currentSpec.suites["_" + suite.id] = {
+ name: suite.name,
+ id: suite.id,
+ expanded: false,
+ testCases: {}
+ };
+ }
+
+ _currentSuite = _currentSpec.suites["_" + suite.id];
+
+ suite.testCases.forEach(function (tc) {
+ if (!_currentSuite.testCases.hasOwnProperty("_" + tc.id)) {
+ _currentSuite.testCases["_" + tc.id] = {
+ name: tc.name,
+ id: tc.id,
+ results: []
+ };
+ }
+ if (tc.finished === 1) {
+ totalTc++;
+ var _s = getResult(tc);
+ if (_s === "passed") {
+ passedTc++;
+ } else if (_s === "passed with issues") {
+ passedTc++;
+ passedWithIssues++;
+ } else if (_s === "failed") {
+ failedTc++;
+ } else if (_s === "skipped") {
+ skippedTc++;
+ }
+ _currentSuite.testCases["_" + tc.id].results.push({
+ result: _s,
+ visible: true,
+ issuesLbl: tc.issues.length > 1 ? "issues" : (tc.issues.length === 0 ? "" : tc.issues[0]),
+ link: "#/case/" + tc.id + "/suite/" + suite.id,
+ resultClass: _s.replace(/\s/g, "_"),
+ platform: blob.platform,
+ user: blob.user,
+ issues: tc.issues.length > 0 ? tc.issues : []
+ });
+ }
+
+ });
+ });
+ });
+
+
+ var arr = [];
+ var _x;
+ var _y;
+ var _suites;
+ var _cases;
+ for (var k in arrObj) {
+ if (arrObj.hasOwnProperty(k)) {
+ _x = arrObj[k];
+ _suites = [];
+ for (var l in _x.suites) {
+ if (_x.suites.hasOwnProperty(l)) {
+
+ _y = _x.suites[l];
+ _cases = [];
+ for (var m in _y.testCases) {
+ if (_y.testCases.hasOwnProperty(m)) {
+ _cases.push(_y.testCases[m]);
+ }
+ }
+ _y.testCases = _cases;
+ _suites.push(_y);
+ }
+ }
+ _x.suites = _suites;
+ arr.push(_x);
+ }
+ }
+
+ var _pRateRound = Math.round(100 * 10 * passedTc / totalTc) / 10;
+ var _fRateRound = Math.round(100 * 10 * failedTc / totalTc) / 10;
+ var _pRateRound2 = Math.round(100 * 10 * passedWithIssues / totalTc) / 10;
+ var _sRateRound = Math.round(10 * (100 - _pRateRound - _fRateRound - _pRateRound2)) / 10;
+
+ var start = $scope.getDate($scope.run.start);
+ var stop = $scope.getDate($scope.run.end);
+
+ $scope.overview = {
+ totalTc: totalTc,
+ pRate: _pRateRound + "% passed, " + _pRateRound2 + "% passed with issues, " + _fRateRound + "% failed and " + _sRateRound + "% skipped",
+ testers: _users.length,
+ duration: getDuration(stop.getTime() - start.getTime()),
+ time: totalMins > 59 ? Math.floor(totalMins / 60) + " hours and " + (totalMins % 60) + " minutes" : totalMins + " minutes"
+ };
+ $scope.specs = arr;
+ $scope.labels = _allLabels;
+ }
+
+
+ function getDuration(duration) {
+
+ var seconds = Math.floor(duration / 1000);
+ var minutes = Math.floor(seconds / 60);
+ var hours = Math.floor(minutes / 60);
+ var days = Math.floor(hours / 24);
+ hours = hours - (days * 24);
+ minutes = minutes - (days * 24 * 60) - (hours * 60);
+ seconds = seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60);
+
+ var result = "";
+ if (days > 0) {
+ result += (days + " days|");
+ }
+ if (hours > 0) {
+ result += (hours + " hours|");
+ }
+ if (minutes > 0) {
+ result += (minutes + " minutes|");
+ }
+ if (seconds > 0) {
+ result += (seconds + " seconds|");
+ }
+
+ result = result.replace(/\|/g, " ");
+ if (result.length === 0) {
+ result = "0 seconds";
+ }
+
+ return result;
+ }
+
+ $scope.filter = function () {
+ var _v;
+ $scope.specs.forEach(function (spec) {
+ spec.suites.forEach(function (suite) {
+ suite.testCases.forEach(function (tcase) {
+ tcase.results.forEach(function (result) {
+ _v = shouldDisplay(result);
+ _v = _v && ($scope.selectedLabels.length > 0 ? $scope.selectedLabels.indexOf(spec.label) > -1 : true);
+ result.visible = _v;
+ });
+ });
+ });
+ });
+
+
+
+
+
+ };
+
+ function shouldDisplay(result) {
+ return $scope.resultFilter[result.resultClass];
+ }
+
+ function getResult(tc) {
+ if (tc.result === "passed" && tc.issues.length > 0) {
+ return "passed with issues";
+ } else {
+ return tc.result;
+ }
+ }
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+
+ function RunCtrlUser($scope, $location, $routeParams, runHttp, assignmentHttp, attachmentHttp, reviewHttp, SynergyUtils, SynergyHandlers, SynergyIssue) {
+
+ $scope.$emit("updateNavbar", {item: "nav_runs"});
+ $scope.id = $routeParams.id || -1;
+ $scope.platforms = [];
+ $scope.run = {};
+ $scope.rights = 0;
+ var leaderIsRemoving = false;
+ $scope.explainModal = "";
+ var currentActionId = -1;
+ var currentAction = "";
+ $scope.username = $scope.SYNERGY.session.username || "";
+ $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
+ $scope.assignees = [];
+ $scope.specifications = [];
+ $scope.tribes = [];
+ $scope.project = {"name": "", "id": -1};
+ var tribes = [];
+ $scope.filter = {
+ "assignee": "All",
+ "specification": "All",
+ "tribe": "All"
+ };
+ $scope.coverage = [];
+ var _coverage = [];
+ $scope.isLoading = false;
+ $scope.pageSize = $scope.SYNERGY.assignmentPage;
+ $scope.sortConfig = {
+ property: ["id"],
+ descending: [false]
+ };
+ $scope.P1Issues = [];
+ $scope.P2Issues = [];
+ $scope.P3Issues = [];
+ $scope.unresolvedIssues = [];
+ $scope.allIssues = [];
+ $scope.orderingProperties = {
+ "userDisplayName": {
+ "desc": false,
+ "asc": false
+ },
+ "specification": {
+ "desc": false,
+ "asc": false
+ }
+ };
+ $scope.pageReviewsExpanded = false;
+ $scope.testRunIsAvailable = false;
+ var issueCollector = new SynergyIssue.RunIssuesCollector();
+
+ /**
+ * Loads test run
+ */
+ $scope.fetch = function () {
+ issueCollector = new SynergyIssue.RunIssuesCollector();
+ $scope.P1Issues = [];
+ tribes = [];
+ _coverage = [];
+ $scope.project = {"name": "", "id": -1};
+ $scope.P2Issues = [];
+ $scope.P3Issues = [];
+ $scope.unresolvedIssues = [];
+ $scope.allIssues = [];
+ $scope.isLoading = true;
+ $scope.coverage = [];
+ $scope.tribes = [];
+ $scope.platforms = [];
+ $scope.assignees = [];
+ $scope.specifications = [];
+ $scope.run = {};
+ $scope.username = $scope.SYNERGY.session.username || "";
+ runHttp.getUserCentric($scope, $scope.id, function (data) {
+ setTestRunIsAvailable(data);
+ $scope.run = data;
+ setProject(data);
+ $scope.$emit("updateBreadcrumbs", {link: "run/" + $scope.id + "/v/2", title: $scope.run.title});
+ getPlatforms();
+ buildAssignments();
+ SynergyUtils.ProgressChart([((data.completed / data.total) * 100), 100 - ((data.completed / data.total) * 100)], ["#08c", "#ccc"], ["completed", ""], "canvas2");
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ $scope.isLoading = false;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.toggleReivewSection = function () {
+ $scope.pageReviewsExpanded = !$scope.pageReviewsExpanded;
+ $("#pageReviews").collapse("toggle");
+ };
+
+ function setTestRunIsAvailable(testRun) {
+ var start = $scope.getDate(testRun.start);
+ var stop = $scope.getDate(testRun.end);
+ var today = new Date();
+ $scope.testRunIsAvailable = (today >= start && today <= stop);
+ }
+
+ function setProject(data) {
+ if (data.projectName !== null) {
+ $scope.project = {"name": data.projectName, "id": data.projectId};
+ } else {
+ $scope.project = {"name": $scope.SYNERGY.product, "id": -2};
+ }
+ }
+
+ $scope.assignessFilter = function (assignmentRecord) {
+ if ($scope.filter.assignee && $scope.filter.assignee !== "All" && assignmentRecord.userDisplayName !== $scope.filter.assignee) {
+ return false;
+ }
+ if ($scope.filter.specification && $scope.filter.specification !== "All" && assignmentRecord.specification !== $scope.filter.specification) {
+ return false;
+ }
+ if ($scope.filter.tribe && $scope.filter.tribe !== "All" && assignmentRecord.tribes.indexOf($scope.filter.tribe) < 0) {
+ return false;
+ }
+ return true;
+ };
+
+ $scope.nextPage = function () {
+ $scope.isLoading = true;
+ $scope.pageSize += $scope.SYNERGY.assignmentPage;
+ $scope.isLoading = false;
+ };
+
+ $scope.bugs = [];
+ $scope.bugsAssignmentId = -1;
+
+ $scope.alterBugs = function (assignment) {
+ $scope.bugsAssignmentId = assignment.id;
+ var a = [];
+ for (var i = 0, max = assignment.issues.length; i < max; i++) {
+ a.push({
+ "id": assignment.issues[i].bugId,
+ "stillValid": true,
+ "changeCount": false
+ });
+ }
+ $scope.bugs = a;
+ $("#ticketsModal").modal("toggle");
+ };
+
+ $scope.performAlterBugs = function () {
+ var newIssues = [];
+ var countDiff = 0;
+ for (var i = 0, max = $scope.bugs.length; i < max; i++) {
+ if ($scope.bugs[i].stillValid) {
+ newIssues.push($scope.bugs[i].id);
+ } else {
+ if ($scope.bugs[i].changeCount) {
+ countDiff++;
+ }
+ }
+ }
+
+ assignmentHttp.alterBugs($scope, $scope.bugsAssignmentId, {issues: newIssues.join(";"), diffCount: countDiff}, function () {
+ $scope.SYNERGY.logger.log("Done", "Issues updated", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+
+
+ $("#ticketsModal").modal("toggle");
+ };
+
+ function resetFilters() {
+ $scope.sortConfig = {
+ "property": ["id"],
+ "descending": [false]
+ };
+ $scope.orderingProperties = {
+ "userDisplayName": {
+ "desc": false,
+ "asc": false
+ },
+ "specification": {
+ "desc": false,
+ "asc": false
+ }
+ };
+ }
+
+ $scope.changeSorting = function (prop, order) {
+ var _p = prop;
+ prop = order ? prop : "-" + prop;
+ if ($scope.sortConfig.property.indexOf(_p) > -1 || $scope.sortConfig.property.indexOf("-" + _p) > -1) {
+ var i = $scope.sortConfig.property.indexOf(_p);
+ if (i < 0) {
+ i = $scope.sortConfig.property.indexOf("-" + _p);
+ }
+ if ($scope.sortConfig.descending[i] === order) { // click on the same order arrow => remove it from filter
+ $scope.sortConfig.property.splice(i, 1);
+ $scope.sortConfig.descending.splice(i, 1);
+ $scope.orderingProperties[_p].desc = false; // reset css for this property
+ $scope.orderingProperties[_p].asc = false;
+ if ($scope.sortConfig.property.length === 0) { // resel css to default values and reset filter to ID
+ resetFilters();
+ }
+ } else {
+ $scope.orderingProperties[_p].desc = !$scope.orderingProperties[_p].desc; // invert css
+ $scope.orderingProperties[_p].asc = !$scope.orderingProperties[_p].asc;
+ var _orig = {// just place holder
+ "descending": !$scope.sortConfig.descending[i],
+ "property": $scope.sortConfig.property[i]
+ };
+ $scope.sortConfig.descending.splice(i, 1); // remove it from array of filters
+ $scope.sortConfig.property.splice(i, 1);
+ $scope.sortConfig.descending.splice(0, 0, _orig.descending); // insert it to level of filters at the beginning
+ (_orig.property.indexOf("-") === 0) ? $scope.sortConfig.property.splice(0, 0, _orig.property.substring(1, _orig.property.length)) : $scope.sortConfig.property.splice(0, 0, "-" + _orig.property);
+ }
+ } else {
+ if ($scope.sortConfig.property.length === 1 && $scope.sortConfig.property[0] === "id") { // if no ordering so far, simply replace ID with selected property
+ $scope.sortConfig = {
+ property: [prop],
+ descending: [order]
+ };
+ } else {
+ $scope.sortConfig.property.splice(0, 0, prop); // insert at the beginning
+ $scope.sortConfig.descending.splice(0, 0, order);
+ }
+
+ $scope.orderingProperties[_p].desc = !order; // css update
+ $scope.orderingProperties[_p].asc = order;
+ }
+ };
+
+ $scope.createCoverageChart = function (c) {
+ try {
+ SynergyUtils.ProgressChart([c.progress, 100 - c.progress], ["#08c", "#ccc"], ["finished", ""], "coverage" + c.name);
+ } catch (e) {
+ }
+ };
+
+ function buildAssignments() {
+ var result = {
+ "failed": 0,
+ "passed": 0,
+ "skipped": 0
+ };
+ var assignees = [];
+ var specs = [];
+ tribes = [];
+ var duplicateAssignments = [];
+ var prettyAssignments = [];
+ var allResolved;
+ for (var username in $scope.run.assignments) {
+ if ($scope.run.assignments.hasOwnProperty(username)) {
+ var user = $scope.run.assignments[username];
+ var line = {
+ assignments: []
+ };
+ for (var assign = 0, maxAssign = user.assignments.length; assign < maxAssign; assign++) {
+ var assignment = user.assignments[assign];
+ line.userDisplayName = assignment.userDisplayName;
+ line.username = assignment.username;
+ line.specificationId = assignment.specificationId;
+ line.label = assignment.label;
+ line.labelId = assignment.labelId;
+ line.specification = assignment.specification;
+ line.tribes = assignment.tribes;
+ assignees.indexOf(line.userDisplayName) < 0 && assignees.push(line.userDisplayName);
+ specs.indexOf(line.specification) < 0 && specs.push(line.specification);
+ getTribes(assignment);
+ assignment.total = parseInt(assignment.total, 10);
+ assignment.completed = parseInt(assignment.completed, 10);
+ assignment.failedColor = "#62c462";
+ allResolved = allIssuesResolved(assignment.issues);
+ if (parseInt(assignment.failed, 10) > 0 && !allResolved) {
+ assignment.failedColor = "#ee5f5b";
+ }
+ if (assignment.total > 0) {
+ assignment.progress = Math.round(100 * 10 * assignment.completed / assignment.total) / 10;
+ assignment.progressLabel = "Completed " + assignment.progress + "%";
+ } else {
+ assignment.progressLabel = "No cases to test";
+ assignment.progress = 100;
+ }
+ assignment.info = (allResolved && assignment.progress === 100 && parseInt(assignment.failed, 10) === 0) ? "finished" : assignment.info;
+ var _t = (assignment.started.length > 0 ? "Started: " + assignment.started : "");
+ _t += (assignment.lastUpdated.length > 0 ? "; Last updated: " + assignment.lastUpdated : "");
+ assignment.tooltip = assignment.total > 0 ? _t : "At the time of assignment creation, there were no matching cases. You can try to start this assignment to see if situation has changed";
+ if (assignment.issues.length === 1) {
+ assignment.issuesLink = (assignment.issues.length > 0) ? "<a style='color: " + assignment.failedColor + "; font-weight: bold' href='" + $scope.SYNERGY.issues.viewLink($scope.project.name, assignment.issues) + "'>" + assignment.issues.length + " issue</a> " : " ";
+ } else if (assignment.issues.length > 1) {
+ assignment.issuesLink = (assignment.issues.length > 0) ? "<a style='color: " + assignment.failedColor + "; font-weight: bold' href='" + $scope.SYNERGY.issues.viewLink($scope.project.name, assignment.issues) + "'>" + assignment.issues.length + " issues</a> " : " ";
+ }
+ if (typeof line.assignments[$scope.platforms.indexOf(assignment.platform)] !== "undefined") {
+ line.assignments[$scope.platforms.indexOf(assignment.platform)].duplicates = true;
+ assignment.duplicates = true;
+ duplicateAssignments.push(assignment);
+ } else {
+ assignment.duplicates = false;
+ line.assignments[$scope.platforms.indexOf(assignment.platform)] = assignment;
+ }
+ if (!_coverage[$scope.platforms.indexOf(assignment.platform)]) { // FIXME
+ _coverage[$scope.platforms.indexOf(assignment.platform)] = {total: 0, completed: 0, name: assignment.platform};
+ }
+
+ _coverage[$scope.platforms.indexOf(assignment.platform)].total += parseInt(assignment.total, 10);
+ _coverage[$scope.platforms.indexOf(assignment.platform)].completed += parseInt(assignment.completed, 10);
+
+ result.failed += Math.floor(assignment.failed);
+ result.passed += Math.floor(assignment.passed);
+ result.skipped += Math.floor(assignment.skipped);
+ }
+
+ for (var p = 0, maxp = $scope.platforms.length; p < maxp; p++) {
+ if (!line.assignments[p]) {
+ line.assignments[p] = {"_hidden": new Date()};
+ }
+ }
+
+ prettyAssignments.push(line);
+ }
+ }
+
+ // duplicates
+ for (var dupl = 0, maxa = duplicateAssignments.length; dupl < maxa; dupl++) {
+ prettyAssignments.push(getLineForDupliciteAssignment(duplicateAssignments[dupl]));
+ }
+
+ assignees.push("All");
+ assignees.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ specs.push("All");
+ specs.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ tribes.push("All");
+ tribes.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ $scope.assignees = assignees;
+ $scope.specifications = specs;
+ $scope.tribes = tribes;
+ countResults(result);
+ $scope.prettyAssignments = prettyAssignments;
+ SynergyUtils.ProgressChart([issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100), 100 - (issueCollector.issuesStats.opened / (issueCollector.issuesStats.total / 100))], ["#ccc", "#62c462"], ["Unresolved", "Resolved"], "issuesResolution");
+ SynergyUtils.ProgressChart([issueCollector.issuesStats.unknown / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P1 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P2 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P3 / (issueCollector.issuesStats.total / 100), issueCollector.issuesStats.P4 / (issueCollector.issuesStats.total / 100)], ["#E8EBAB", "#ee5f5b", "#f89406", "#fbeed5", "#ccc"], ["Unknown (" + issueCo [...]
+ $scope.allIssues = issueCollector.issues;
+ $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
+ $scope.P1Issues = issueCollector.issuesStats.P1Issues;
+ $scope.P2Issues = issueCollector.issuesStats.P2Issues;
+ $scope.P3Issues = issueCollector.issuesStats.P3Issues;
+ }
+
+ function getLineForDupliciteAssignment(assignment) {
+ var line = {
+ assignments: []
+ };
+ line.userDisplayName = assignment.userDisplayName;
+ line.username = assignment.username;
+ line.specificationId = assignment.specificationId;
+ line.label = assignment.label;
+ line.labelId = assignment.labelId;
+ line.specification = assignment.specification;
+ line.tribes = assignment.tribes;
+ line.assignments[$scope.platforms.indexOf(assignment.platform)] = assignment;
+ for (var p = 0, maxp = $scope.platforms.length; p < maxp; p++) {
+ if (!line.assignments[p]) {
+ line.assignments[p] = {"_hidden": new Date()};
+ }
+ }
+ return line;
+ }
+
+ function allIssuesResolved(issues) {
+ var result = true;
+ for (var i = 0, max = issues.length; i < max; i++) {
+ issueCollector.addIssue(issues[i]);
+ if (issues[i].status.toLowerCase() !== "resolved" && issues[i].status.toLowerCase() !== "closed" && issues[i].status.toLowerCase() !== "verified") {
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ function getTribes(assignment) {
+ for (var i = 0, max = assignment.tribes.length; i < max; i++) {
+ if (tribes.indexOf(assignment.tribes[i]) < 0) {
+ tribes.push(assignment.tribes[i]);
+ }
+ }
+ }
+
+ // retrieves all distinct platforms from all assignments and assign them to $scope.platforms
+ function getPlatforms() {
+ for (var username in $scope.run.assignments) {
+ if ($scope.run.assignments.hasOwnProperty(username)) {
+ var user = $scope.run.assignments[username];
+ for (var assignment in user.assignments) {
+ if ($scope.platforms.indexOf(user.assignments[assignment].platform) < 0) {
+ $scope.platforms.push(user.assignments[assignment].platform);
+ }
+ }
+ }
+ }
+ $scope.platforms.sort();
+ }
+ /**
+ * Counts number of passed/failed/skipped cases
+ * @param {TestRun} run
+ */
+ function countResults(result) {
+ var t = (result.failed + result.passed + result.skipped) / 100;
+ if (t > 0) {
+ var f = Math.floor(result.failed * 10 / t) / 10;
+ var p = Math.round(result.passed * 10 / t) / 10;
+ SynergyUtils.ProgressChart([p, f, Math.round(10 * (100 - (f + p))) / 10], ["#62c462", "#ee5f5b", "#c67605"], ["passed", "failed", "skipped"], "canvas1");
+ }
+
+ for (var i = 0, max = _coverage.length; i < max; i++) {
+ _coverage[i].progress = Math.round(10 * 100 * (_coverage[i].completed / _coverage[i].total)) / 10;
+ }
+ $scope.coverage = _coverage;
+
+ }
+
+ /**
+ * Redirects to page so user starts testing and completing his assignments
+ * @param {Number} mode
+ * @param {Number} assignmentId assignment ID
+ */
+ $scope.startAssignment = function (mode, assignmentId) {
+ if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
+ $("#deleteModalLabel").text("Restart assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved progress will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "restartAssignment";
+ currentActionId = assignmentId;
+ } else {
+ $location.path("/assignment/" + assignmentId + "/v/" + mode);
+ }
+ };
+
+ /**
+ * Starts with action on given test run. If the action name is different than "delete", redirection is done.
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ */
+ $scope.performRun = function (action) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test run?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test run?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteRun";
+ break;
+ case "notify":
+ $("#deleteModalLabel").text("Send notifications?");
+ $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "notify";
+ break;
+ case "freeze":
+ var target = ($scope.run.isActive ? 0 : 1);
+ runHttp.freezeRun($scope, $scope.id, target, function (data) {
+ $scope.run.isActive = target;
+ (target === 1) ? $scope.SYNERGY.logger.log("Done", "Test run unfrozen", "INFO", "alert-success") : $scope.SYNERGY.logger.log("Done", "Test run frozen", "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ $location.path("/administration/run/" + $scope.id + "/" + action);
+ break;
+ }
+ };
+
+ /**
+ * Starts with action on given run attachment
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id attachment ID
+ */
+ $scope.performAttachment = function (action, id) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete attachment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteAttachment";
+ currentActionId = id;
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Redirects user to page where he can create a new run assignment
+ */
+ $scope.forwardToCreateAssignment = function () {
+ $location.path("/administration/assignment/create/run/" + $scope.id);
+ };
+
+ /**
+ * Redirects user to page where he can create a new matrix run assignment
+ */
+ $scope.forwardToCreateMatrixAssignment = function () {
+ $location.path("/administration/assignment/creatematrix/run/" + $scope.id);
+ };
+
+ /**
+ * Starts with action on given test assignment
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id assignment ID
+ */
+ $scope.performAssignment = function (action, id, createdBy) {
+ if (action !== "delete") {
+ $location.path("suite/" + id + "/" + action);
+ } else {
+ switch (action) {
+ case "delete":
+ leaderIsRemoving = (createdBy === 3) ? true : false;
+ $("#deleteModalLabel").text("Delete test assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteAssignment";
+ currentActionId = id;
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ $scope.deleteAssignment = function () {
+ if (!leaderIsRemoving) {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ assignmentHttp.remove($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ } else {
+ $("#explainModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.explanation.length < 1) {
+ return;
+ }
+ assignmentHttp.removeByLeader($scope, currentActionId, $scope.explanation, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ $scope.startReviewAssignment = function (mode, assignmentId) {
+ if (parseInt(mode, 10) === 2) {// restart => show modal confirmation
+ $("#deleteModalLabel").text("Restart assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to restart this assignment? All saved comments will be lost as if you never started it. If you want to Continue saved assignment, please use 'Play' button instead</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "restartReviewAssignment";
+ currentActionId = assignmentId;
+ } else {
+ $location.path("/review/" + assignmentId + "/continue");
+ }
+ };
+
+ $scope.performReviewAssignment = function (action, id, createdBy) {
+ switch (action) {
+ case "delete":
+ currentAction = "deleteReviewAssignment";
+ currentActionId = id;
+ leaderIsRemoving = (createdBy === 3) ? true : false;
+ $("#deleteModalLabel").text("Delete review assignment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete review assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ break;
+ default:
+ $location.path("/review/" + id + "/" + action);
+ break;
+ }
+ };
+
+ function deleteReviewAssignment() {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ reviewHttp.remove($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Assignment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ }
+ /**
+ * Executes some action based on value of $scope.currentAction
+ */
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "restartAssignment":
+ $("#deleteModal").modal("toggle");
+ $location.path("/assignment/" + currentActionId + "/v/2");
+ break;
+ case "deleteAssignment":
+ $("#deleteModal").modal("toggle");
+ leaderIsRemoving ? $("#explainModal").modal("toggle") : $scope.deleteAssignment();
+ break;
+ case "restartReviewAssignment":
+ $("#deleteModal").modal("toggle");
+ $location.path("/review/" + currentActionId + "/restart");
+ break;
+ case "deleteReviewAssignment":
+ $("#deleteModal").modal("toggle");
+ deleteReviewAssignment();
+ break;
+ case "notify":
+ $("#deleteModal").modal("toggle");
+ runHttp.sendNotifications($scope, $scope.id, function (data) {
+ $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "deleteRun":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ runHttp.remove($scope, $scope.id, function (data) {
+ $scope.SYNERGY.modal.update("Test run removed", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("/runs");
+ }, function (data) {
+ $scope.SYNERGY.modal.update("Action failed", "");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.SYNERGY.modal.show();
+ });
+ break;
+ case "deleteAttachment":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.fetch();
+ });
+ break;
+ default:
+ break;
+ }
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ // ATTACHMENT UPLOAD HANDLING
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ $scope.fetch();
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ });
+ }
+ /**
+ * @param {VersionsFct} versionsHttp
+ * @param {SpecificationFct} specificationHttp description
+ * @param {SuiteFct} suiteHttp description
+ * @param {AttachmentFct} attachmentHttp description
+ * @param {UsersFct} usersHttp description
+ * * @param {JobFct} jobHttp description
+ */
+ function SpecificationCtrl($scope, utils, $location, $routeParams, versionsHttp, specificationHttp, suiteHttp, attachmentHttp, usersHttp, jobHttp, userHttp, sanitizerHttp, labelsHttp, projectsHttp, specificationCache, SynergyUtils, SynergyModels, SynergyHandlers) {//authService
+
+ var self = this;
+ $scope.project = null;
+ $scope.$emit("updateNavbar", {item: "nav_specs"});
+ $scope.specification = {};
+ $scope.refreshCodemirror = false; // on edit/create page it is necessary to refresh editor element
+ $scope.id = $routeParams.id || -1;
+ $scope.rights = 0;
+ $scope.readOnlyJobs = [];
+ $scope.users = []; // used on edit page to select owner
+ $scope.filename = "";
+ $scope.attachmentBase = $scope.SYNERGY.server.buildURL("attachment", {});
+ $scope.versions = [];
+ $scope.version = "";
+ $scope.newLabel = "";
+ $scope.removeLabel = "";
+ self.simpleName = $routeParams.simpleName || "";
+ self.simpleVersion = $routeParams.simpleVersion || "";
+ self.originalSimpleName = "";
+ $scope.keepSimpleNameTrack = true;
+ $scope.filterLabel = $routeParams.label || "All";
+ $scope.labels = [];
+ $scope.realLabels = []; // without all
+ $scope.removalUsers = "";
+ $scope.requestMsg = "";
+ $scope.projects = [];
+ var specificationDurationCache = {};
+ var currentAction = "";
+ var currentActionId = -1;
+ var currentSuiteId = -1;
+
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function (useCache) {
+ if (self.simpleName.length > 0) {
+ loadSpecificationFromAlias();
+ return;
+ }
+
+ if (window.location.href.indexOf("/title/") > -1) {// shouldn't be, caused by some issue in .htaccess
+ $location.path("");
+ return;
+ }
+
+ switch (getAction()) {
+ case "create":
+ versionsHttp.get($scope, true, function (data) {
+ $scope.versions = data;
+ $scope.version = data[0].name || "";
+ $scope.refreshCodemirror = true;
+ }, $scope.generalHttpFactoryError);
+
+ projectsHttp.getAll($scope, function (data) {
+ $scope.projects = data;
+ $scope.project = $scope.projects[0];
+ }, $scope.generalHttpFactoryError);
+
+ break;
+ case "1":
+ if ($scope.id < 0) {
+ return;
+ }
+ if (specificationCache.getCurrentSpecificationId() === parseInt($scope.id, 10)) {
+ displaySimpleSpecification(specificationCache.getCurrentSpecification());
+ } else {
+ specificationCache.resetCurrentSpecification();
+ specificationHttp.get($scope, useCache, $scope.id, function (data) {
+ displaySimpleSpecification(data);
+ }, $scope.generalHttpFactoryError);
+ }
+ break;
+ case "2":
+ if ($scope.id < 0) {
+ return;
+ }
+ specificationHttp.getFull($scope, useCache, $scope.id, function (data) {
+ $scope.specification = data;
+ setProject(data);
+ specificationDurationCache.All = data.estimation;
+ specificationCache.setCurrentSpecification(data, $scope.project);
+ getRemovalUsers();
+ $scope.labels = getLabels(data);
+ $scope.newname = data.title;
+ resolveContinuousJobs(data.ext.continuous_integration);
+ $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id + "/v/2", title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+
+ }, $scope.generalHttpFactoryError);
+ break;
+ default : // edit
+ if ($scope.id < 0) {
+ return;
+ }
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ break;
+ }
+
+ specificationHttp.get($scope, false, $scope.id, function (data) {
+ self.originalSimpleName = data.simpleName;
+ setProject(data);
+ $scope.refreshCodemirror = true;
+ if (SynergyUtils.definedNotNull(data.ext.continuous_integration)) {
+ for (var j = 0, max = data.ext.continuous_integration.length; j < max; j++) {
+ data.ext.continuous_integration[j].jobUrl = data.ext.continuous_integration[j].jobUrl.substring(0, data.ext.continuous_integration[j].jobUrl.indexOf("/lastCompletedBuild"));
+ }
+ }
+ $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id, title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ $scope.specification = data;
+ $scope.specification.originalOwner = data.owner;
+
+ projectsHttp.getAll($scope, function (data) {
+
+ var defaultProject = true;
+ for (var p = 0, maxp = data.length; p < maxp; p++) {
+ if (data[p].name === $scope.project.name) {
+ $scope.project.id = data[p].id;
+ defaultProject = false;
+ break;
+ }
+ }
+ if (defaultProject) {
+ data.push($scope.project);
+ }
+ $scope.projects = data;
+
+ }, $scope.generalHttpFactoryError);
+
+ usersHttp.getAll($scope, function (data) {
+ $scope.users = data.users;
+ }, $scope.generalHttpFactoryError);
+ }, $scope.generalHttpFactoryError);
+
+ break;
+ }
+ };
+
+ function displaySimpleSpecification(data) {
+ $scope.specification = data;
+ $scope.newname = data.title;
+ setProject(data);
+ resolveContinuousJobs(data.ext.continuous_integration);
+ getRemovalUsers();
+ $scope.$emit("updateBreadcrumbs", {link: "specification/" + $scope.id + "/v/1", title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }
+
+ function setProject(data) {
+ if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
+ $scope.project = data.ext.projects[0];
+ } else {
+ $scope.project = {"name": $scope.SYNERGY.product, id: -2};
+ }
+ }
+
+ /**
+ * Sets all users that requested specification removal to $scope.removalUsers
+ */
+ function getRemovalUsers() {
+ var _l = "";
+ for (var i = 0, max = $scope.specification.ext.removalRequests.length; i < max; i++) {
+ _l += $scope.specification.ext.removalRequests[i].username + ", ";
+ }
+ $scope.removalUsers = (_l.length === 1) ? "" : _l.substr(0, _l.length - 2);
+ }
+
+ /**
+ * Returns action based on URL (edit, create, 1,2)
+ * @returns {String} action
+ */
+ function getAction() {
+ var url = window.location.href;
+ var stringId = $scope.id + "";
+ var _s = url.lastIndexOf("/" + $scope.id + "/") + stringId.length + 2;
+ var _e = url.indexOf("/", _s);
+ var action = (_e > -1) ? url.substring(_s, _e) : url.substring(_s, url.length);
+ if (action === "v") {
+ return (url.indexOf($scope.id + "/v/1") > -1) ? "1" : "2";
+ }
+ return action;
+ }
+
+ $scope.getSpecificationDuration = function () {
+ if (!$scope.filterLabel || $scope.filterLabel === "All") {
+ return $scope.specification.estimation;
+ }
+
+ if (typeof specificationDurationCache[$scope.filterLabel] !== "undefined") {
+ return specificationDurationCache[$scope.filterLabel];
+ }
+
+ var time = 0;
+ for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
+ for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
+ for (var k = 0, max3 = $scope.specification.testSuites[i].testCases[j].keywords.length; k < max3; k++) {
+ if ($scope.specification.testSuites[i].testCases[j].keywords[k] === $scope.filterLabel) {
+ time += $scope.specification.testSuites[i].testCases[j].duration;
+ }
+ }
+ }
+ }
+ specificationDurationCache[$scope.filterLabel] = time;
+ return time;
+ }
+
+ /**
+ * Returns true or false if test case matches label filter
+ * @param {type} testCase
+ * @returns {Boolean} true if test case has given label or if searched label is set to All, false if it doesn't
+ */
+ $scope.hasLabel = function (testCase, a) {
+ if (!$scope.filterLabel || $scope.filterLabel === "All") {
+ return true;
+ }
+ for (var i = 0, max = testCase.keywords.length; i < max; i++) {
+ if (testCase.keywords[i] === $scope.filterLabel) {
+ return true;
+ }
+ }
+ return false;
+ };
+ /**
+ * Asks server to sanitize input and renders it to the Preview tab
+ */
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.specification.title || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.specification.desc || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Toggle state of specification in user's favorites list
+ */
+ $scope.toggleFavorite = function () {
+ var target = parseInt($scope.specification.isFavorite, 10) > 0 ? 0 : 1;
+ var spec = new SynergyModels.Specification("", "", "", "", $scope.id);
+ spec.isFavorite = target;
+ userHttp.toggleFavorite($scope, spec, function (data) {
+ $scope.specification.isFavorite = target;
+ if (target === 0) {
+ $scope.SYNERGY.logger.log("Done", "Specification removed from favorites", "INFO", "alert-success");
+ } else {
+ $scope.SYNERGY.logger.log("Done", "Specification added to favorites", "INFO", "alert-success");
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Loads specification based on simple name property in URL
+ * @returns {undefined}
+ */
+ function loadSpecificationFromAlias() {
+ self.simpleName = decodeURIComponent(decodeURIComponent(self.simpleName));
+ specificationHttp.getFullAlias($scope, false, self.simpleName, self.simpleVersion, function (data) {
+ $scope.specification = data;
+ $scope.labels = getLabels(data);
+ specificationDurationCache.All = data.estimation;
+ setProject(data);
+ specificationCache.setCurrentSpecification(data, $scope.project);
+ $scope.newname = data.title;
+ $scope.id = data.id;
+ resolveContinuousJobs(data.ext.continuous_integration);
+ $scope.$emit("updateBreadcrumbs", {link: "specification/" + data.id + "/v/2", title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+
+ /**
+ * Collects all distinct labels from specification, adds "All" label and sorts them alphabetically
+ * @param {Specification} data
+ * @returns {Array} array of strings
+ */
+ function getLabels(data) {
+ var labels = [];
+ var realLabels = [];
+ for (var i = 0, max = data.testSuites.length; i < max; i++) {
+ for (var j = 0, max2 = data.testSuites[i].testCases.length; j < max2; j++) {
+ for (var k = 0, max3 = data.testSuites[i].testCases[j].keywords.length; k < max3; k++) {
+ if (labels.indexOf(data.testSuites[i].testCases[j].keywords[k]) < 0) {
+ labels.push(data.testSuites[i].testCases[j].keywords[k]);
+ realLabels.push(data.testSuites[i].testCases[j].keywords[k]);
+ }
+ }
+ }
+ }
+
+ labels.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ realLabels.sort(function (a, b) {
+ return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
+ });
+ $scope.realLabels = realLabels;
+ if ($scope.realLabels.length > 0) {
+ $scope.removeLabel = $scope.realLabels[0];
+ }
+ labels.push("All");
+ return labels;
+ }
+
+ /**
+ * Resolve each continuous job
+ */
+ function resolveContinuousJobs(jobs) {
+
+ function doResolve(data) {
+ $scope.readOnlyJobs.push(data);
+ }
+
+ function logError(data) {
+ $scope.SYNERGY.logger.log("Failed to load data", data, "DEBUG", "alert-error");
+ }
+
+ $scope.readOnlyJobs = [];
+ if (!SynergyUtils.definedNotNull(jobs)) {
+ return;
+ }
+ for (var i = 0, max = jobs.length; i < max; i++) {
+ jobHttp.resolve($scope, jobs[i], doResolve, logError);
+ }
+ }
+
+ /**
+ * General attachment action
+ * @param {String} action
+ * @param {Number} id attachment ID
+ */
+ $scope.performAttachment = function (action, id) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete attachment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
+ $("#modal_confirm_ok").attr("ng-click", "deleteAttachment()");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteAttachment";
+ currentActionId = id;
+ break;
+ default:
+ $location.path("specification_attachment/" + id + "/" + action);
+ break;
+ }
+ };
+
+ $scope.showJobsModal = function () {
+ $("#jobsModal").modal("toggle");
+ };
+
+ /**
+ * Removes job from specification
+ */
+ $scope.removeJob = function (jobId) {
+ jobHttp.remove($scope, jobId, $scope.specification.id, function (data) {
+ for (var i = 0, max = $scope.specification.ext.continuous_integration.length; i < max; i++) {
+ if ($scope.specification.ext.continuous_integration[i].id === jobId) {
+ $scope.specification.ext.continuous_integration.splice(i, 1);
+ break;
+ }
+ }
+ $scope.SYNERGY.modal.update("Job removed", "");
+ $scope.SYNERGY.modal.show();
+ }, function (data) {
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+ /**
+ * Adds job to specification
+ */
+ $scope.addJob = function () {
+ var job = new SynergyModels.Job($scope.jobToBeAdded, $scope.id, -1);
+ jobHttp.create($scope, job, function (data) {
+ $scope.specification.ext.continuous_integration.push(job);
+ $scope.SYNERGY.modal.update("Job added", "");
+ $scope.SYNERGY.modal.show();
+ }, $scope.generalHttpFactoryError);
+ };
+ /**
+ * Clones specification (sends request to server)
+ * @returns {unresolved}
+ */
+ $scope.clone = function () {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ if (!$scope.newname) {
+ $scope.newname = $scope.specification.title;
+ }
+ if ($scope.newname.length < 1 || $scope.cloneVersion.length < 1) {
+ $scope.SYNERGY.modal.update("Missing parameters", "Please add a new name and version");
+ $scope.SYNERGY.modal.show();
+ } else {
+ specificationHttp.clone($scope, currentActionId, $scope.newname, $scope.cloneVersion, function (newLink) {
+ var id = newLink.substring(newLink.indexOf("=") + 1);
+ $scope.SYNERGY.logger.log("Done", "Specification created, see " + window.location.hostname + window.location.pathname + "#/specification/" + id, "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ /**
+ * Starts with action on given test suite
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id suite ID
+ */
+ $scope.performSuite = function (action, id) {
+ // delete could be done on a same page
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test suite?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test suite?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteSuite";
+ currentActionId = id;
+ break;
+ case "labels":
+ // $("#addLabelsModalLabel").text("Add label to all cases in suite");
+ $("#addLabelsModal").modal("toggle");
+ currentAction = "labels";
+ currentActionId = id;
+ break;
+ default:
+ $location.path("suite/" + id + "/" + action);
+ break;
+ }
+
+ };
+
+ $scope.performCase = function (action, id, suiteId) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test case?");
+ $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteCase";
+ currentActionId = id;
+ currentSuiteId = suiteId;
+ break;
+ default:
+ break;
+ }
+ };
+
+ $scope.deleteCase = function () {
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var toBeRemovedCaseId = parseInt(currentActionId, 10);
+ var toBeRemovedSuiteId = parseInt(currentSuiteId, 10);
+ suiteHttp.removeCase($scope, currentSuiteId, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Test Case removed from test suite", "INFO", "alert-success");
+ for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
+ if ($scope.specification.testSuites[i].id === toBeRemovedSuiteId) {
+ var s = $scope.specification.testSuites[i];
+ for (var j = 0, max2 = s.testCases.length; j < max2; j++) {
+ if (s.testCases[j].id === toBeRemovedCaseId) {
+ s.testCases.splice(j, 1);
+ return;
+ }
+ }
+
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Executes some action based on value of $scope.currentAction
+ */
+ $scope.performAction = function (labelMode) {
+ switch (currentAction) {
+ case "deleteAttachment":
+ $scope.deleteAttachment();
+ break;
+ case "cloneSpecification":
+ $scope.clone();
+ break;
+ case "ownershipRequest":
+ specificationHttp.requestOwnership($scope, new SynergyModels.OwnershipRequest($scope.id, $scope.SYNERGY.session.username, $scope.requestMsg), function () {
+ $scope.SYNERGY.logger.log("Done", "Request has been sent to owner", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "deleteSpecification":
+ $scope.deleteSpecification();
+ break;
+ case "deleteSuite":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var toBeRemovedSuiteId = parseInt(currentActionId, 10);
+ suiteHttp.remove($scope, currentActionId, function () {
+ $scope.SYNERGY.logger.log("Done", "Test suite deleted", "INFO", "alert-success");
+ for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
+ if ($scope.specification.testSuites[i].id === toBeRemovedSuiteId) {
+ $scope.specification.testSuites.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "deleteCase":
+ $scope.deleteCase();
+ break;
+ case "labels":
+ if (labelMode === "add") {
+ addLabels();
+ } else {
+ removeLabels();
+ }
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Removes label from suite and then removes it from $scope.specification
+ */
+ function removeLabels() {
+ if ($scope.removeLabel.length < 1) {
+ return;
+ }
+ var id = parseInt(currentActionId, 10);
+ specificationCache.resetCurrentSpecification();
+ labelsHttp.removeFromSuite($scope, currentActionId, $scope.removeLabel, function () {
+ $scope.SYNERGY.logger.log("Done", "Labels removed", "INFO", "alert-success");
+ for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
+ if (parseInt($scope.specification.testSuites[i].id, 10) === id) {
+ for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
+ var index = $scope.specification.testSuites[i].testCases[j].keywords.indexOf($scope.removeLabel);
+ if (index > -1) {
+ $scope.specification.testSuites[i].testCases[j].keywords.splice(index, 1);
+ }
+ }
+ break;
+ }
+ }
+ $scope.labels = getLabels($scope.specification);
+ }, $scope.generalHttpFactoryError);
+ $("#addLabelsModal").modal("toggle");
+ }
+
+ function addLabels() {
+ if ($scope.newLabel.length < 1) {
+ return;
+ }
+ var id = parseInt(currentActionId, 10);
+ specificationCache.resetCurrentSpecification();
+ labelsHttp.createForSuite($scope, currentActionId, $scope.newLabel, function () {
+ $scope.SYNERGY.logger.log("Done", "Labels added", "INFO", "alert-success");
+ for (var i = 0, max = $scope.specification.testSuites.length; i < max; i++) {
+ if (parseInt($scope.specification.testSuites[i].id, 10) === id) {
+ for (var j = 0, max2 = $scope.specification.testSuites[i].testCases.length; j < max2; j++) {
+ if ($scope.specification.testSuites[i].testCases[j].keywords.indexOf($scope.newLabel) < 0) {
+ $scope.specification.testSuites[i].testCases[j].keywords.push($scope.newLabel);
+ }
+ }
+ break;
+ }
+ }
+ $scope.labels = getLabels($scope.specification);
+ }, $scope.generalHttpFactoryError);
+ $("#addLabelsModal").modal("toggle");
+ }
+
+ /**
+ * Removes attachment (calls server)
+ * @returns {unresolved}
+ */
+ $scope.deleteAttachment = function () {
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ var toBeRemoved = currentActionId;
+ attachmentHttp.removeSpecAttachment($scope, currentActionId, $scope.specification.id, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
+ for (var i = 0, max = $scope.specification.attachments.length; i < max; i++) {
+ if ($scope.specification.attachments[i].id === toBeRemoved) {
+ $scope.specification.attachments.splice(i, 1);
+ return;
+ }
+ }
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.fetch();
+ });
+ };
+
+ /**
+ * Executes some action with specification based on value of $scope.currentAction
+ */
+ $scope.performSpecification = function (action) {
+ // delete could be done on a same page
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete specification?");
+ $("#deleteModalBody").html("<p>Do you really want to delete specification?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteSpecification";
+ break;
+ case "clone":
+ currentActionId = $scope.id;
+ currentAction = "cloneSpecification";
+ versionsHttp.get($scope, true, function (data) {
+ $scope.versions = data;
+ $("#dupliciteSpecModal").modal("toggle");
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "ownershipRequest":
+ if ($scope.SYNERGY.session.username.length < 1) {
+ return;
+ }
+ $("#ownershipRequestModal").modal("toggle");
+ currentActionId = $scope.id;
+ currentAction = "ownershipRequest";
+ break;
+ default:
+ $location.path("specification/" + $scope.specification.id + "/" + action);
+ break;
+ }
+
+ };
+
+ /**
+ * Goes back in history by 1 step
+ */
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Deletes specification (calls server)
+ * @returns {unresolved}
+ */
+ $scope.deleteSpecification = function () {
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ specificationHttp.remove($scope, $scope.specification.id, function (data, status) {
+ if (status === 202) {
+ $scope.SYNERGY.logger.log("Done", "Request to remove this specification has been sent to owner", "INFO", "alert-success");
+ $scope.specification.ext.removalRequests.push({"username": $scope.SYNERGY.session.username});
+ getRemovalUsers();
+ } else {
+ $scope.SYNERGY.modal.update("Specification deleted", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("specifications");
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Saves modified specification
+ */
+ $scope.save = function () {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ var spec = new SynergyModels.Specification($scope.specification.title, $scope.specification.desc, "", $scope.specification.owner, $scope.specification.id);
+ var intProjectId = parseInt($scope.project.id, 10);
+ spec.ext.projects = [{"name": $scope.projects.filter(function (item, index) {
+ if (parseInt(item.id, 10) === intProjectId) {
+ return item.name;
+ }
+ })[0].name, "id": intProjectId}];
+ spec.setSimpleName($scope.specification.simpleName);
+ if ($scope.myForm.$invalid || typeof spec.desc === "undefined" || spec.desc.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ specificationHttp.edit($scope, spec, $scope.minorEdit ? true : false, $scope.keepSimpleNameTrack, function (data) {
+ $scope.SYNERGY.modal.update("Specification updated", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("specification/" + $scope.specification.id);
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Creates a new specification
+ * @returns {unresolved}
+ */
+ $scope.create = function () {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ if ($scope.myForm.$invalid || typeof $scope.specification.desc === "undefined" || $scope.specification.desc.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var spec = new SynergyModels.Specification($scope.specification.title, $scope.specification.desc, $scope.version, "", -1);
+ var intProjectId = parseInt($scope.project.id, 10);
+ spec.ext.projects = [{"name": $scope.projects.filter(function (item, index) {
+ if (parseInt(item.id, 10) === intProjectId) {
+ return item.name;
+ }
+ })[0].name, "id": intProjectId}];
+ spec.setSimpleName($scope.specification.title);
+ specificationHttp.create($scope, spec, function (data) {
+ $scope.SYNERGY.modal.update("Specification created", "");
+ var id = data.substring(data.indexOf("=") + 1, data.length - 1);
+ $location.path("specification/" + id);
+ $scope.SYNERGY.modal.show();
+ }, $scope.generalHttpFactoryError);
+ };
+
+// ATTACHMENT UPLOAD
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("attachment", {"id": $scope.id, "type": "specification"}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ specificationCache.resetCurrentSpecification();
+ attachmentHttp.getAttachmentsForSpecification($scope, $scope.specification.id, function (data) {
+ $scope.specification.attachments = data;
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Failed to refresh list of attachments", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ });
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ });
+
+ $scope.uploadFile = function () {
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("attachment", {"id": $scope.id, "type": "specification"}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ specificationCache.resetCurrentSpecification();
+ attachmentHttp.getAttachmentsForSpecification($scope, $scope.specification.id, function (data) {
+ $scope.specification.attachments = data;
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Failed to refresh list of attachments", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ });
+ }, function (title, msg, level, style) {
+
+ $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ }).uploadFileFromFileChooser("fileToUpload");
+ };
+ var scope = $scope;
+ $scope.init(function () {
+ scope.fetch(true);
+ });
+ }
+ /**
+ *
+ * @param {SpecificationFct} specificationHttp
+ * @param {SuiteFct} suiteHttp
+ * @param {CasesFct} casesHttp
+ * @returns {undefined}
+ */
+ function SuiteCtrl($scope, utils, $location, $routeParams, $timeout, specificationHttp, suiteHttp, casesHttp, productsHttp, sanitizerHttp, specificationCache, SynergyModels) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_specs"});
+ $scope.suite = {};
+ $scope.project = "";
+ $scope.refreshCodemirror = false;
+ $scope.id = $routeParams.id || -1;
+ $scope.rights = 0;
+ // for create only
+ $scope.c_version = $routeParams.version || ""; // used on create page to display version
+ $scope.c_specificationId = $routeParams.specification || ""; // used on create page to display specification link
+ $scope.c_specification = {}; // used on create page to display specification title
+ $scope.case_suggestions = []; // matching cases when adding existing case to suite
+ $scope.caseToBeAdded = ""; // the name that user types in add existing case dialog
+ $scope.availableProducts = false;
+ $scope.products = [];
+ $scope.oldNotification = "";
+ $scope.components = [];
+
+ var currentAction = "";
+ var currentActionId = -1;
+
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function () {
+ var action = window.location + "";
+ action = action.substring(action.lastIndexOf("/") + 1);
+ switch (action) {
+ case "create":
+ if (parseInt($scope.c_specificationId, 10) > 0) {
+ specificationHttp.get($scope, true, $scope.c_specificationId, function (data) {
+ setLastSuiteOrder(data);
+ setProject(data);
+ $scope.c_specification = data;
+ loadProducts();
+ }, $scope.generalHttpFactoryError);
+ }
+ break;
+ case "1":
+ if ($scope.id < 0) {
+ return;
+ }
+ var cachedSuite = specificationCache.getCurrentSuite(parseInt($scope.id, 10));
+ if (cachedSuite) {
+ $scope.suite = cachedSuite;
+ $scope.project = specificationCache.getCurrentProjectName();
+ $scope.$emit("updateBreadcrumbs", {link: "suite/" + $scope.id + "/v/1", title: $scope.suite.title});
+ try {
+ if ($scope.suite.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ return;
+ }
+
+
+
+ suiteHttp.get($scope, true, $scope.id, function (data) {
+ $scope.suite = data;
+ setProject(data);
+ $scope.$emit("updateBreadcrumbs", {link: "suite/" + $scope.id + "/v/1", title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }, $scope.generalHttpFactoryError);
+ break;
+ default :
+ if ($scope.id < 0) {
+ return;
+ }
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ break;
+ }
+ suiteHttp.get($scope, false, $scope.id, function (data) {
+ $scope.suite = data;
+ setProject(data);
+ $scope.refreshCodemirror = true;
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+
+ loadProducts();
+
+ }, $scope.generalHttpFactoryError);
+ break;
+ }
+ };
+
+ function setProject(data) {
+ if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
+ $scope.project = data.ext.projects[0].name;
+ } else {
+ $scope.project = $scope.SYNERGY.product;
+ }
+ }
+
+ function setLastSuiteOrder(data) {
+ try {
+ $scope.suite.order = (data.testSuites[data.testSuites.length - 1].order + 1) || 1;
+ } catch (e) {
+ $scope.suite.order = 1;
+ }
+ }
+ /**
+ * Loads sanitized preview from server
+ */
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.suite.title || "") + "</h1><h3>Setup</h3><div class='well'>" + ($scope.suite.desc || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function loadProducts() {
+ productsHttp.get($scope, function (data) {
+ if (data.length > 0) {
+ var p = [];
+ for (var j = 0, max2 = data.length; j < max2; j++) {
+ p.push(new SynergyModels.Product(data[j].name, data[j].components));
+ }
+ $scope.products = p;
+ $scope.availableProducts = true;
+
+ var oldPreferences = $scope.SYNERGY.cache.get("product_component");
+ if (oldPreferences && (($scope.suite.product === "unknown" && $scope.suite.component === "unknown") || (typeof $scope.suite.product === "undefined"))) {
+ for (var i = 0, max = $scope.products.length; i < max; i++) {
+ if (data[i].name === oldPreferences.product) {
+ $scope.suite.product = $scope.products[i];//select current product in form
+ $scope.suite.component = oldPreferences.component;
+ setComponent(i);
+ $scope.oldNotification = "Selected product/component are based on previously used values and do not match actual settings of this suite";
+ return;
+ }
+ }
+ } else {
+ for (var i = 0, max = $scope.products.length; i < max; i++) {
+ if (data[i].name === $scope.suite.product || typeof $scope.suite.product === "undefined") {
+ $scope.suite.product = $scope.products[i];//select current product in form
+ setComponent(i);
+ return;
+ }
+ }
+ }
+ $scope.suite.product = $scope.products[0];
+ setComponent(0);
+
+ }
+ }, function () {
+ });
+ }
+
+ function setComponent(productIndex) {
+ $scope.components = $scope.products[productIndex].components;
+ for (var i = 0, max = $scope.components.length; i < max; i++) {
+ if ($scope.components[i].name === $scope.suite.component || typeof $scope.suite.component === "undefined" || (typeof $scope.suite.component.name !== "undefined" && $scope.components[i].name === $scope.suite.component.name)) {
+ $scope.suite.component = $scope.components[i];
+ return;
+ }
+ }
+
+ $scope.suite.component = $scope.components[0];
+ }
+
+ $scope.productChanged = function () {
+ for (var i = 0, max = $scope.products.length; i < max; i++) {
+ if ($scope.products[i].name === $scope.suite.product.name) {
+ $scope.suite.product = $scope.products[i];//select current product in form
+ setComponent(i);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Starts with action on given test case
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id suite ID
+ */
+ $scope.performCase = function (action, id) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test case?");
+ $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteCase";
+ currentActionId = id;
+ break;
+ default:
+ $location.path("case/" + id + "/suite/" + $scope.id + "/" + action);
+ break;
+ }
+ };
+
+ /**
+ * Executes some action based on value of currentAction
+ */
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "deleteCase":
+ $scope.deleteCase();
+ break;
+ case "deleteSuite":
+ $scope.deleteSuite();
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Removes test case
+ */
+ $scope.deleteCase = function () {
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var caseToBeRemovedId = parseInt(currentActionId, 10);
+ suiteHttp.removeCase($scope, $scope.suite.id, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Test Case removed from test suite", "INFO", "alert-success");
+ for (var i = 0, max = $scope.suite.testCases.length; i < max; i++) {
+ if ($scope.suite.testCases[i].id === caseToBeRemovedId) {
+ $scope.suite.testCases.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Adds case to suite
+ * @param {type} caseId
+ */
+ $scope.addCase = function (caseId) {
+ $("#addCaseModal").modal("toggle");
+ specificationCache.resetCurrentSpecification();
+ suiteHttp.addCase($scope, $scope.id, caseId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Test Case added to test suite", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Starts with action on given test suite
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id suite ID
+ */
+ $scope.performSuite = function (action) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test suite?");
+ $("#deleteModalBody").html("<p>Do you really want to delete test suite?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteSuite";
+ break;
+ default:
+ $location.path("suite/" + $scope.suite.id + "/" + action);
+ break;
+ }
+ };
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Deletes test suite
+ */
+ $scope.deleteSuite = function () {
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ suiteHttp.remove($scope, $scope.suite.id, function (data) {
+ $scope.SYNERGY.modal.update("Test Suite deleted", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("specification/" + $scope.suite.specificationId);
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Saves modifications in suite
+ */
+ $scope.save = function () {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.suite.desc === "undefined" || $scope.suite.desc.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var product = ($scope.availableProducts) ? $scope.suite.product.name : $scope.suite.product;
+ var component = ($scope.availableProducts) ? $scope.suite.component.name : $scope.suite.component;
+ var suite = new SynergyModels.Suite($scope.suite.title, $scope.suite.desc, product, component, $scope.suite.id);
+ suite.order = $scope.suite.order;
+ suiteHttp.edit($scope, suite, $scope.minorEdit ? true : false, function (data) {
+ $scope.SYNERGY.modal.update("Test Suite updated", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+
+ if ($scope.availableProducts) {
+ $scope.SYNERGY.cache.put("product_component", {"product": $scope.suite.product.name, "component": $scope.suite.component.name});
+ }
+
+ };
+
+ /**
+ * Creates a new test suite
+ */
+ $scope.create = function () {
+ $scope.suite.specificationId = $scope.c_specificationId;
+
+ if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.suite.desc === "undefined" || $scope.suite.desc.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var product = ($scope.availableProducts) ? $scope.suite.product.name : $scope.suite.product;
+ var component = ($scope.availableProducts) ? $scope.suite.component.name : $scope.suite.component;
+ var suite = new SynergyModels.Suite($scope.suite.title, $scope.suite.desc, product, component, -1);
+ suite.order = $scope.suite.order;
+ suite.specificationId = $scope.suite.specificationId;
+ suiteHttp.create($scope, suite, function (data) {
+ $scope.SYNERGY.modal.update("Test Suite created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+
+ if ($scope.availableProducts) {
+ $scope.SYNERGY.cache.put("product_component", {"product": $scope.suite.product.name, "component": $scope.suite.component});
+ }
+
+ };
+
+ /**
+ * Displays dialog for adding existing cases to suite
+ */
+ $scope.showAddCaseModal = function () {
+ $scope.case_suggestions = [];
+ $scope.caseToBeAdded = "";
+ $("#addCaseModal").modal("toggle");
+ };
+
+ /**
+ * Reduces list of offered cases based on $scope.caseToBeAdded
+ */
+ $scope.filterCases = function () {
+ $timeout(function () {
+ casesHttp.getMatching($scope, $scope.caseToBeAdded, function (data) {
+ $scope.case_suggestions = data;
+ }, $scope.generalHttpFactoryError);
+ }, 600);
+ };
+// INIT
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {SuiteFct} suiteHttp
+ * @param {CaseFct} caseHttp
+ * @param {ImageFct} imageHttp
+ * @returns {undefined} */
+ function CaseCtrl($scope, utils, $location, $routeParams, suiteHttp, caseHttp, imageHttp, issueHttp, labelHttp, sanitizerHttp, specificationCache, SynergyModels, SynergyHandlers) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_specs"});
+ $scope.testCase = {};
+ $scope.project = "";
+ $scope.refreshCodemirror = false;
+ $scope.id = $routeParams.id || -1;
+ $scope.parentSuite = $routeParams.parent || -1;
+ $scope.rights = 0;
+ var currentAction = "";
+ var currentActionId = -1;
+ $scope.labelToBeAdded = "";
+ $scope.preview = ($scope.parentSuite < 0) ? 1 : 0; // whether or not the case is displayed without suite context
+ $scope.c_suite = {}; // used in create page to display suite information
+ var originalDuration = 0; // used in edit page, when user submits edited case, when submitted duration is different than originalDuration, server restarts duration count
+
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.testCase.title || "") + "</h1><h3>Steps</h3><div>" + ($scope.testCase.steps || "") + "</div><h3>Expected result:</h3><div class='result well'>" + ($scope.testCase.result || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function (useCache) {
+ var action = window.location + "";
+ action = action.substring(action.lastIndexOf("/") + 1);
+ switch (action) {
+ case "create":
+ $scope.testCase.steps = "<ol>\n<li></li>\n<li></li>\n<li></li>\n<li></li>\n<ol>";
+ $scope.testCase.duration = 1;
+ if (parseInt($scope.parentSuite, 10) > 0) {
+ suiteHttp.get($scope, useCache, $scope.parentSuite, function (data) {
+ setProject(data);
+ setLastCaseOrder(data);
+
+ $scope.c_suite = data;
+ }, $scope.generalHttpFactoryError);
+ }
+ break;
+ case "1":
+ if ($scope.id < 0) {
+ return;
+ }
+ var cachedCase = specificationCache.getCurrentCase(parseInt($scope.id, 10), parseInt($scope.parentSuite, 10));
+ if (cachedCase) {
+ $scope.testCase = cachedCase;
+ $scope.project = specificationCache.getCurrentProjectName();
+ $scope.$emit("updateBreadcrumbs", {link: "case/" + $scope.id + "/suite/" + $scope.parentSuite + "/v/1", title: $scope.testCase.title});
+ try {
+ if ($scope.testCase.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ return;
+ }
+
+ caseHttp.get($scope, useCache, $scope.id, $scope.parentSuite, function (data) {
+ $scope.testCase = data;
+ setProject(data);
+ $scope.$emit("updateBreadcrumbs", {link: "case/" + $scope.id + "/suite/" + $scope.parentSuite + "/v/1", title: data.title});
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }, $scope.generalHttpFactoryError);
+ break;
+ default :
+ if ($scope.id < 0) {
+ return;
+ }
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ break;
+ }
+ caseHttp.get($scope, false, $scope.id, $scope.parentSuite, function (data) {
+ $scope.testCase = data;
+ setProject(data);
+ $scope.refreshCodemirror = true;
+ $scope.testCase.suiteId = $scope.parentSuite;
+ originalDuration = parseInt(data.duration, 10);
+ try {
+ if (data.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ } catch (e) {
+ }
+ }, $scope.generalHttpFactoryError);
+ break;
+ }
+ // FIXME this is loaded twice on document load
+ };
+
+ function setProject(data) {
+ if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
+ $scope.project = data.ext.projects[0].name;
+ } else {
+ $scope.project = $scope.SYNERGY.product;
+ }
+ }
+
+ /**
+ * When creating a new case, set order to be (previous case+1)
+ * @param {Suite} data
+ * @returns {undefined}
+ */
+ function setLastCaseOrder(data) {
+ try {
+ $scope.testCase.order = (data.testCases[data.testCases.length - 1].order + 1) || 1;
+ } catch (e) {
+ $scope.testCase.order = 1;
+ }
+ }
+
+ /**
+ * Creates a new test case
+ */
+ $scope.create = function () {
+ if (parseInt($scope.parentSuite, 10) < 1) {// parent suite HAS to be defined
+ return;
+ }
+ if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.testCase.steps === "undefined" || typeof $scope.testCase.steps.length < 0 || typeof $scope.testCase.result === "undefined" || typeof $scope.testCase.result.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var testCase = new SynergyModels.TestCase($scope.testCase.title, $scope.testCase.steps, $scope.testCase.result, $scope.testCase.duration, -1);
+ testCase.suiteId = $scope.parentSuite;
+ testCase.order = $scope.testCase.order;
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ caseHttp.create($scope, testCase, function (data) {
+ $scope.SYNERGY.modal.update("Test Case created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Based on $scope.currentAction, performs some action
+ */
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "deleteCase":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1 || $scope.parentSuite < 1) {
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ suiteHttp.removeCase($scope, $scope.parentSuite, $scope.id, function (data) {
+ $scope.SYNERGY.modal.update("Test Case removed from test suite", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "deleteImage":
+ var idToBeRemoved = currentActionId;
+ specificationCache.resetCurrentSpecification();
+ $("#deleteImageModal").modal("toggle");
+ imageHttp.remove($scope, currentActionId, $scope.parentSuite, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Image removed", "INFO", "alert-success");
+ for (var i = 0, max = $scope.testCase.images.length; i < max; i++) {
+ if ($scope.testCase.images[i].id === idToBeRemoved) {
+ $scope.testCase.images.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Starts with action on current test case
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ */
+ $scope.performCase = function (action) {
+ // delete could be done on a same page
+ if ($scope.parentSuite < 0) {
+ return;
+ }
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test case?");
+ $("#deleteModalBody").html("<p>This will only remove reference to this test case in this suite. Continue?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteCase";
+ break;
+ default:
+ $location.path("case/" + $scope.testCase.id + "/suite/" + $scope.parentSuite + "/" + action);
+ break;
+ }
+ };
+
+ /**
+ * Starts with action on given test case's image
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id image ID
+ */
+ $scope.performImage = function (action, id) {
+ // delete could be done on a same page
+ if ($scope.parentSuite < 0) {
+ return;
+ }
+ switch (action) {
+ case "delete":
+ $("#deleteImageModalLabel").text("Delete image?");
+ $("#deleteImageModalBody").html("<p>This will delete image from this test case. Continue?</p>");
+ $("#deleteImageModal").modal("toggle");
+ currentAction = "deleteImage";
+ currentActionId = id;
+ break;
+ default:
+ $location.path("image/" + id + "/" + action);
+ break;
+ }
+
+ };
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Saves modified test case to server
+ * @param {Number} mode if 0, this test case will be cloned and modifications will be applied only to this suite, if 1 all suites will be affected
+ */
+ $scope.save = function (mode) {
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ if ($scope.myForm.$invalid || $scope.myForm2.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ specificationCache.resetCurrentSpecification();
+ var t_case = new SynergyModels.TestCase($scope.testCase.title, $scope.testCase.steps, $scope.testCase.result, $scope.testCase.duration, $scope.testCase.id);
+ t_case.suiteId = $scope.parentSuite;
+ t_case.order = $scope.testCase.order;
+ t_case.orginalDuration = originalDuration;
+ caseHttp.edit($scope, mode, t_case, $scope.minorEdit ? true : false, function (data) {
+ $scope.SYNERGY.modal.update("Test Case updated", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+ $scope.showAddIssueModal = function () {
+ $scope.issueToBeAdded = "";
+ $("#addIssueModal").modal("toggle");
+ };
+ $scope.addIssue = function () {
+ specificationCache.resetCurrentSpecification();
+ issueHttp.create($scope, {testCaseId: $scope.testCase.id, id: $scope.issueToBeAdded}, function (data) {
+ $scope.SYNERGY.logger.log("Issue added", "", "INFO", "alert-success");
+ $scope.testCase.issues.push({"bugId": $scope.issueToBeAdded, "title": "", "resolution": "", "id": -1});
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.removeIssue = function (id) {
+ specificationCache.resetCurrentSpecification();
+ issueHttp.remove($scope, {testCaseId: $scope.testCase.id, id: id}, function (data) {
+ $scope.SYNERGY.logger.log("Issue removed", "", "INFO", "alert-success");
+ for (var i = 0, max = $scope.testCase.issues.length; i < max; i++) {
+ if ($scope.testCase.issues[i].bugId === id) {
+ $scope.testCase.issues.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.showAddLabelModal = function () {
+ $scope.labelToBeAdded = "";
+ $("#addLabelModal").modal("toggle");
+ };
+ $scope.addLabel = function () {
+ specificationCache.resetCurrentSpecification();
+ labelHttp.create($scope, {"label": $scope.labelToBeAdded, "testCaseId": $scope.testCase.id, "suiteId": $scope.parentSuite}, function (data) {
+ $scope.SYNERGY.logger.log("Label added", "", "INFO", "alert-success");
+ $scope.testCase.keywords.push($scope.labelToBeAdded.toLowerCase());
+ }, $scope.generalHttpFactoryError);
+ };
+ $scope.removeLabel = function (label) {
+ specificationCache.resetCurrentSpecification();
+ labelHttp.remove($scope, {"label": label, "testCaseId": $scope.testCase.id, "suiteId": $scope.parentSuite}, function (data) {
+ $scope.SYNERGY.logger.log("Label removed", "", "INFO", "alert-success");
+ $scope.testCase.keywords.splice($scope.testCase.keywords.indexOf(label), 1);
+ }, $scope.generalHttpFactoryError);
+ };
+// INIT
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch(true);
+ });
+
+ // D&D images
+
+ var fu = new SynergyHandlers.FileUploader([], "fakeID", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("image", {"id": $scope.id, "suiteId": $scope.parentSuite, "title": encodeURIComponent($scope.imageTitle)}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ specificationCache.resetCurrentSpecification();
+ imageHttp.getImagesForCase($scope, $scope.testCase.id, $scope.parentSuite, function (data) {
+ $scope.testCase.images = data;
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Unable to refresh list of images", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ });
+ fu.initForImages("dropbox");
+
+ $scope.uploadFile = function () {
+ fu.uploadImage($scope.SYNERGY.server.buildURL("image", {"id": $scope.id, "suiteId": $scope.parentSuite, "title": encodeURIComponent($scope.imageTitle)}));
+ };
+
+ }
+ /**
+ *
+ * @param {UserFct} userHttp
+ */
+ function ProfileCtrl($scope, $routeParams, userHttp, $location, SynergyModels, SynergyHandlers) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_home"});
+ $scope.user = {authorOf: [], membership: [], favorites: [], assignments: []};
+ $scope.username = $routeParams.user || "";
+ $scope.rights = 0;
+ $scope.passwordChangeAllowed = !$scope.SYNERGY.useSSO;
+ $scope.updatePassword = false;
+
+ $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function () {
+ $scope.isLoggedIn = (typeof $scope.SYNERGY.session.session_id !== "undefined" && $scope.SYNERGY.session.session_id.length > 1) ? 1 : 0;
+ var action = window.location + "";
+ action = action.substring(action.lastIndexOf("/") + 1);
+ if ($scope.username.length < 1) {
+ if (typeof $scope.SYNERGY.session.session_id !== "undefined" || $scope.SYNERGY.session.session_id.length > 1) {
+ $scope.username = $scope.SYNERGY.session.username;
+ }
+ }
+ if ($scope.username.length < 1) {
+ return;
+ }
+ userHttp.get($scope, $scope.username, function (data) {
+
+ data.assignments.forEach(function (trun) {
+ if (trun.projectName === null || trun.projectName === "") {
+ trun.projectName = $scope.SYNERGY.product;
+ }
+ });
+ setProject(data.authorOf);
+ setProject(data.ownerOf);
+ setProject(data.favorites);
+ $scope.user = data;
+ if ($scope.username === $scope.SYNERGY.session.username) {
+ $scope.rights = 1;
+ }
+
+ $scope.$emit("updateBreadcrumbs", {link: "user/" + $scope.username, title: $scope.username});
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function setProject(specifications) {
+ for (var i = 0, max = specifications.length; i < max; i++) {
+ specifications[i]._project = specifications[i].ext.hasOwnProperty("projects") && specifications[i].ext.projects.length > 0 ? specifications[i].ext.projects[0].name : $scope.SYNERGY.product;
+ }
+ }
+
+ $scope.editName = function () {
+ if ($scope.rights === 1) {
+ var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
+ if ($scope.profileForm.$invalid || ($scope.updatePassword && invalidPassword)) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ var _u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, "", -1);
+ _u.emailNotifications = $scope.user.emailNotifications;
+ _u.email = $scope.user.email;
+ if ($scope.updatePassword) {
+ _u.password = $scope.user.password;
+ }
+ userHttp.edit($scope, _u, function (data) {
+ $scope.SYNERGY.logger.log("Done", "updated", "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ /**
+ * Removes specification from user's list of favorites
+ * @param {Number} id specification ID
+ */
+ $scope.toggleFavorite = function (id) {
+ var spec = {"id": id, "isFavorite": 0};
+ userHttp.toggleFavorite($scope, spec, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Specification removed from favorites", "INFO", "alert-success");
+ for (var k = 0, max = $scope.user.favorites.length; k < max; k += 1) {
+ if (parseInt($scope.user.favorites[k].id, 10) === parseInt(id, 10)) {
+ $scope.user.favorites.splice(k, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ $scope.uploadFile = function () {
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("profile_img", {"id": $scope.user.id}), function (title, msg, level, style, fileName, newSrc) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.user.profileImg = newSrc;
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log("Action failed", msg, "INFO", "alert-error");
+ try {
+ if (!$scope.$$phase) {
+ $scope.$apply();
+ }
+ } catch (e) {
+ }
+ }).uploadFileFromFileChooser("fileToUpload");
+ };
+
+ $scope.resetFile = function () {
+ userHttp.resetProfileImg($scope, $scope.user.id, function (data) {
+ $scope.user.profileImg = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ }
+ /**
+ * @param {LabelFct} labelHttp
+ * @returns {undefined}
+ */
+ function LabelFilterCtrl($scope, $routeParams, labelHttp) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_home"});
+ $scope.result = {};
+ $scope.label = $routeParams.label || "";
+ $scope.page = $routeParams.page || 1;
+ $scope.next = 0;
+ $scope.prev = 0;
+ $scope.nextPage = 1;
+ $scope.prevPage = 1;
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function () {
+ if ($scope.label.length > 0) {
+ labelHttp.findCases($scope, $scope.label, $scope.page, function (data) {
+ $scope.result = data;
+ $scope.$emit("updateBreadcrumbs", {link: "label/" + $scope.label + "/page/1", title: $scope.label});
+ $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
+ $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
+ $scope.nextPage = parseInt($scope.page, 10) + 1;
+ $scope.prevPage = parseInt($scope.page, 10) - 1;
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {UsersFct} usersHttp
+ * @param {TribeFct} tribeHttp description
+ * @returns {undefined}
+ */
+ function TribeCtrl($scope, $location, $routeParams, $timeout, usersHttp, tribeHttp, sanitizerHttp, specificationsHttp, SynergyUtils, SynergyModels) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_home"});
+ $scope.tribe = {};
+ $scope.id = $routeParams.id || -1;
+ $scope.rights = 0;
+ $scope.refreshCodemirror = false;
+ var currentAction = "";
+ var currentId = "";
+ $scope.suggestions = [];
+ $scope.users_suggestions = [];
+ $scope.userToBeAdded = "pepa";
+ $scope.specifications = [];
+ $scope.newSpecification = -1;
+ $scope.users = [];
+ $scope.loadingUsers = false;
+ $scope.toggleMembers = false;
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function () {
+ var action = window.location + "";
+ action = action.substring(action.lastIndexOf("/") + 1);
+ if ($scope.id > 0) {
+ tribeHttp.get($scope, false, $scope.id, function (data) {
+ setProject(data.ext);
+ $scope.tribe = data;
+ $scope.refreshCodemirror = true;
+ $scope.$emit("updateBreadcrumbs", {link: "tribe/" + $scope.id, title: data.name});
+ if ($scope.tribe.controls.length > 0) {
+ $scope.rights = 1;
+ }
+ if (action === "edit") {
+ loadSpecifications();
+ loadUsers();
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ function setProject(ext) {
+ if (!ext.hasOwnProperty("specifications")) {
+ return;
+ }
+ for (var i = 0, max = ext.specifications.length; i < max; i++) {
+ ext.specifications[i]._project = ext.specifications[i].hasOwnProperty("projects") && ext.specifications[i].projects.length > 0 ? ext.specifications[i].projects[0].name : $scope.SYNERGY.product;
+ }
+ }
+
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.tribe.name || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.tribe.description || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Loads all specifications (in edit page so tribe leader can add specification to tribe)
+ */
+ function loadSpecifications() {
+ specificationsHttp.get($scope, "allRaw", function (data) {
+ var d = [];
+ var p;
+ for (var i = 0, max = data.length; i < max; i += 1) {
+ p = data[i].ext.hasOwnProperty("projects") && data[i].ext.projects.length > 0 ? data[i].ext.projects[0].name : $scope.SYNERGY.product;
+ d[i] = {
+ title: data[i].title,
+ version: data[i].version,
+ value: data[i].title + " (" + p + " " + data[i].version + ")",
+ id: data[i].id
+ };
+ }
+ $scope.specifications = d;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.addSpecification = function () {
+ var _index = parseInt($scope.newSpecification);
+ for (var i = 0, max = $scope.tribe.ext.specifications.length; i < max; i++) {
+ if (parseInt($scope.tribe.ext.specifications[i].id) === _index) {
+ $scope.SYNERGY.logger.log("Oops", "Specification already added to tribe", "INFO", "alert-info");
+ return;
+ }
+ }
+
+ tribeHttp.addSpecification($scope, $scope.id, $scope.newSpecification, function (data, specificationId) {
+ $scope.SYNERGY.logger.log("Done", "Specification added to tribe", "INFO", "alert-success");
+ var matchingIndex = findSpecification(parseInt(specificationId, 10));
+ if (matchingIndex > 0) {
+ $scope.tribe.ext.specifications.push($scope.specifications[matchingIndex]);
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.removeSpecification = function (specificationId) {
+ tribeHttp.removeSpecification($scope, $scope.id, specificationId, function (data, specificationId) {
+ $scope.SYNERGY.logger.log("Done", "Specification removed from tribe", "INFO", "alert-success");
+ for (var i = 0, max = $scope.tribe.ext.specifications.length; i < max; i++) {
+ if (parseInt($scope.tribe.ext.specifications[i].id, 10) === specificationId) {
+ $scope.tribe.ext.specifications.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function findSpecification(id) {
+ for (var i = 0, max = $scope.specifications.length; i < max; i++) {
+ if ($scope.specifications[i].id === id) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function loadUsers() {
+ if ($scope.users.length > 0) { // already loaded
+ return;
+ }
+ $scope.loadingUsers = true;
+ usersHttp.getAll($scope, function (data) {
+ if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
+ for (var i = 0, max = data.users.length; i < max; i++) {
+ data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
+ }
+ }
+ $scope.loadingUsers = false;
+ $scope.users = data.users;
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "Unable to load list of users", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ }
+ /**
+ * Shows confirmation dialog to remove user from tribe and saves selected username as currentId
+ * @param {String} username
+ */
+ $scope.removeFromTribe = function (username) {
+ $("#deleteModalLabel").text("Remove user from tribe?");
+ $("#deleteModalBody").html("<p>Do you really want to revoke user's membership?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteUser";
+ currentId = username;
+ };
+
+ /**
+ * Removes users from tribe (calls server)
+ * @param {String} username username of users to be removed from tribe
+ */
+ $scope.performRemoveFromTribe = function (username) {
+ $("#deleteModal").modal("toggle");
+ var memberToBeRemoved = username;
+ tribeHttp.revokeMembership($scope, username, $scope.id, function (data) {
+ $scope.SYNERGY.logger.log("Done", "User removed from tribe", "INFO", "alert-success");
+ for (var i = 0, max = $scope.tribe.members.length; i < max; i++) {
+ if ($scope.tribe.members[i].username === memberToBeRemoved) {
+ $scope.tribe.members.splice(i, 1);
+ return;
+ }
+ }
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Bases od $scope.currentAction it calls some method
+ */
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "deleteUser":
+ $scope.performRemoveFromTribe(currentId);
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Performrs action with current tribe
+ * @param {String} action unless it is "delete", it reroutes to tribe/id/action otherwise shows confirmation dialog
+ */
+ $scope.performTribeAction = function (action) {
+ if (action !== "delete") {
+ $location.path("tribe/" + $scope.id + "/" + action);
+ }
+ };
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Saves modifications made to tribe
+ * @returns {unresolved}
+ */
+ $scope.save = function () {
+ if ($scope.myForm.$invalid || $scope.myForm2.$invalid || typeof $scope.tribe.description === "undefined" || $scope.tribe.description.length < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var tribe = new SynergyModels.Tribe($scope.tribe.name, $scope.tribe.description, $scope.tribe.leaderUsername, $scope.tribe.id);
+ tribeHttp.edit($scope, tribe, function (data) {
+ $scope.SYNERGY.modal.update("Tribe updated", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("tribe/" + $scope.tribe.id);
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Adds user of given username to tribe
+ */
+ $scope.addUser = function () {
+ tribeHttp.newMembership($scope, {username: $scope.userToBeAdded}, $scope.tribe.id, function (data) {
+ $scope.SYNERGY.logger.log("Done", "User added to tribe", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.showAddUserModal = function () {
+ $scope.toggleMembers = !$scope.toggleMembers;
+ loadUsers();
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {RunsFct} runsHttp
+ */
+ function RunsCtrl($scope, $routeParams, runsHttp) {
+ $scope.runs = [];
+ $scope.page = $routeParams.page || 1;
+ $scope.next = 0;
+ $scope.prev = 0;
+ $scope.nextPage = 1;
+ $scope.prevPage = 1;
+
+ /**
+ * Retrieves list of test runs
+ */
+ $scope.fetch = function () {
+ $scope.$emit("updateBreadcrumbs", {link: "runs", title: "Test Runs"});
+ $scope.$emit("updateNavbar", {item: "nav_runs"});
+ runsHttp.get($scope, $scope.page, function (data) {
+
+ data.testRuns.forEach(function (trun) {
+ if (trun.projectName === null || trun.projectName === "") {
+ trun.projectName = $scope.SYNERGY.product;
+ }
+ });
+
+ $scope.runs = data;
+ $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
+ $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
+ $scope.nextPage = parseInt($scope.page, 10) + 1;
+ $scope.prevPage = parseInt($scope.page, 10) - 1;
+ }, $scope.generalHttpFactoryError, true);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {AssignmentFct} assignmentHttp
+ * @returns {undefined} */
+ function AssignmentCtrl($scope, $routeParams, assignmentHttp) {//authService
+ $scope.$emit("updateNavbar", {item: "nav_home"});
+ $scope.assignment = {};
+ $scope.currentCase = {};
+ $scope.currentCaseId = -1;
+ $scope.currentSuiteId = -1;
+ $scope.id = $routeParams.id || -1;
+ $scope.mode = $routeParams.mode || 2;
+ $scope.project = {};
+ $scope.timeLeft = 0;
+ $scope.suiteIndex = 0;
+ $scope.caseIndex = 0;
+ $scope.casesFinished = 0;
+ $scope.newIssue = "";
+ $scope.attachmentBase = $scope.SYNERGY.server.buildURL("attachment", {});
+ var timeFinished = 0;
+ var _time = 0;
+ $scope.failedAttempt = false;
+ var started = 0;
+ var cachedData = {};
+ $scope.cacheDate = {};
+ $scope.comments = [];
+ $scope.allCases = [];
+ $scope.suiteSetupDisplayed = false;
+ $scope.toggleAction = "display";
+ $scope.caseToPrint = {};
+ $scope.errorMsg = "";
+ $scope.somethingCompleted = false;
+ $scope.pauseButtonTitle = "No test case has been completed yet or nothing has changed since resuming testing";
+ /**
+ * Loads data from server
+ */
+ $scope.fetch = function () {
+ if ($scope.id > 0) {
+
+ if (cachedDataExists()) {
+ $scope.cacheDate = cachedData.date;
+ $("#attemptModal").modal("toggle");
+ return;
+ }
+
+ assignmentHttp.getCommentTypes($scope, function (data) {
+ data.push({"name": "No comment", "id": -1});
+ $scope.comments = data;
+ }, $scope.generalHttpFactoryError);
+
+ switch ($scope.mode) {
+ case "1":
+ assignmentHttp.start($scope, $scope.id, function (data) {
+ $scope.assignment = data;
+ setProject(data.specificationData);
+ getTimeLeft();
+ collectCases();
+ $scope.casesFinished = parseInt(100 * (parseInt(data.completed, 10) / parseInt(data.total, 10)), 10) || 0;
+ parseData(0, 0, 0);
+ $scope.caseToPrint = $scope.allCases.filter(function (e) {
+ return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
+ })[0];
+ }, $scope.generalHttpFactoryError);
+ break;
+ case "2":
+ assignmentHttp.restart($scope, $scope.id, function (data) {
+ $scope.assignment = data;
+ setProject(data.specificationData);
+ $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10);
+ collectCases();
+ $scope.casesFinished = parseInt(100 * (parseInt(data.completed, 10) / parseInt(data.total, 10)), 10) || 0;
+ parseData(0, 0, 0);
+ $scope.caseToPrint = $scope.allCases.filter(function (e) {
+ return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
+ })[0];
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ break;
+ }
+
+ }
+ };
+ function setProject(data) {
+ if (data.hasOwnProperty("ext") && data.ext.hasOwnProperty("projects") && data.ext.projects.length > 0) {
+ $scope.project = data.ext.projects[0];
+ } else {
+ $scope.project = {"name": $scope.SYNERGY.product, id: -2};
+ }
+ }
+ /**
+ * Collects all cases to show them in navigation combo box together with their potential progress
+ * @returns {undefined}
+ */
+ function collectCases() {
+ var cases = [];
+ for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
+ for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {
+ var p = getProgressForCase($scope.assignment.specificationData.testSuites[i].testCases[j].id, $scope.assignment.specificationData.testSuites[i].id);
+ cases.push({
+ "name": $scope.assignment.specificationData.testSuites[i].testCases[j].title + " (" + $scope.assignment.specificationData.testSuites[i].title + ")" + ((parseInt(p.finished, 10) === 1) ? " - [" + p.result + "]" : ""),
+ "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
+ "suiteId": $scope.assignment.specificationData.testSuites[i].id,
+ "progress": p,
+ "result": p.result
+ });
+ }
+ }
+ $scope.allCases = cases;
+ }
+
+ /**
+ * Sets common properties and prints case selected in combo box
+ */
+ $scope.traverseCase = function () {
+ var oldSuiteId = $scope.currentSuiteId;
+ $scope.currentCaseId = parseInt($scope.caseToPrint.caseId, 10);
+ $scope.currentSuiteId = parseInt($scope.caseToPrint.suiteId, 10);
+
+ var _s;
+ for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
+ _s = $scope.assignment.progress.specification.testSuites[i];
+ if (parseInt(_s.id, 10) === parseInt($scope.caseToPrint.suiteId, 10)) {
+ for (var j = 0, max2 = _s.testCases.length; j < max2; j += 1) {
+ if (parseInt(_s.testCases[j].id, 10) === parseInt($scope.caseToPrint.caseId, 10)) {
+ $scope.suiteIndex = i;
+ $scope.caseIndex = j;
+ }
+ }
+ }
+ }
+
+ if (oldSuiteId !== $scope.currentSuiteId) {
+ $scope.suiteSetupDisplayed = true;
+ $scope.toggleAction = "hide";
+ }
+
+ printCase($scope.currentCaseId, $scope.currentSuiteId);
+ };
+
+ $scope.toggleSetup = function () {
+ if ($scope.suiteSetupDisplayed) {
+ $scope.toggleAction = "display";
+ } else {
+ $scope.toggleAction = "hide";
+ }
+ $scope.suiteSetupDisplayed = !$scope.suiteSetupDisplayed;
+ };
+
+ /**
+ * Checks cache for possibly not submitted data
+ * @returns {Boolean} true if there are cached data
+ */
+ function cachedDataExists() {
+ cachedData = $scope.SYNERGY.cache.get("assignment_progress_" + $scope.id);
+ return cachedData && cachedData.date && cachedData.progress ? true : false;
+ }
+
+ $scope.sendCached = function (send) {
+ if (send) {
+ $("#attemptModal").modal("toggle");
+ $scope.assignment.progress = cachedData.progress;
+ $scope.sendResults();
+ } else {
+ $scope.SYNERGY.cache.clear("assignment_progress_" + $scope.id);
+ $scope.fetch();
+ }
+ };
+
+ /**
+ * Finds first unfinished test case. There are 2 approaches: iterate over progress and find matching test case or iterate over specification
+ * data and find matching progress. The better is 2nd one because if specification has some new cases thar are not in this progress, it
+ * can be dynamically adjusted.
+ * @param {Number} caseStartIndex
+ * @param {Number} suiteStartIndex
+ * @param {Number} timeOffset
+ */
+ function parseData(caseStartIndex, suiteStartIndex, timeOffset) {
+ var _s;
+ // find 1st not yet tested test case
+ for (var i = suiteStartIndex, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
+ _s = $scope.assignment.progress.specification.testSuites[i];
+ for (var j = caseStartIndex, max2 = _s.testCases.length; j < max2; j += 1) {
+ if (parseInt(_s.testCases[j].finished, 10) === 0) { // find first not finished case
+ $scope.currentCaseId = parseInt(_s.testCases[j].id, 10);
+ $scope.currentSuiteId = parseInt(_s.id, 10);
+ printCase($scope.currentCaseId, $scope.currentSuiteId);
+ $scope.suiteIndex = i;
+ $scope.caseIndex = j;
+ if (i !== suiteStartIndex || (caseStartIndex === 0 && suiteStartIndex === 0)) {
+ $scope.suiteSetupDisplayed = true;
+ $scope.toggleAction = "hide";
+ }
+ return;
+ }
+ }
+ caseStartIndex = 0; // to start from beginning in next suite
+ }
+ $scope.timeLeft = 0;
+ $scope.sendResults(); // this happens if all cases are finished (otherwise return in the for statement above is used this code not executed)
+ }
+
+ function getTimeLeft() {
+ var _s;
+ for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i += 1) {
+ _s = $scope.assignment.progress.specification.testSuites[i];
+ for (var j = 0, max2 = _s.testCases.length; j < max2; j += 1) {
+ if (parseInt(_s.testCases[j].finished, 10) !== 0) {
+ if (_s.testCases[j].hasOwnProperty("originalDuration")) {
+ timeFinished += parseInt(Math.round(_s.testCases[j].originalDuration), 10);
+ } else {
+ timeFinished += parseInt(Math.round(_s.testCases[j].duration), 10);
+ }
+ }
+ }
+
+ }
+ $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
+ }
+
+ /**
+ * Sends results of testing to server
+ */
+ $scope.sendResults = function () {
+ if ($scope.failedAttempt) {
+ $("#deleteModal").modal("toggle");
+ }
+ $scope.showWaitDialog();
+ assignmentHttp.submitResults($scope, $scope.id, $scope.assignment.progress, function (data) {
+ $scope.SYNERGY.modal.update("Results submitted", "Thank you for testing");
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.cache.clear("assignment_progress_" + $scope.id);
+ $scope.assignment = {};
+ $scope.currentCase = {};
+ $scope.currentCaseId = -1;
+ $scope.currentSuiteId = -1;
+ $scope.id = $routeParams.id || -1;
+ $scope.mode = $routeParams.mode || 2;
+ $scope.timeLeft = 0;
+ $scope.suiteIndex = 0;
+ $scope.caseIndex = 0;
+ $scope.casesFinished = 0;
+ $scope.newIssue = "";
+ $scope.failedAttempt = false;
+ window.history.back();
+ }, function (data) {
+ $scope.errorMsg = data || "";
+ $scope.SYNERGY.modal.show();// hide wait dialog
+ if (!$scope.failedAttempt) {
+ $scope.failedAttempt = true;
+ $("#deleteModal").modal("toggle");
+ }
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+
+ /**
+ * Procceeds to the next case in testing. Based on result parameter it marks test case as passed, failed or skipped
+ * and if $scope.trackCaseDuration, it sets new duration of the case (not that it sends time to server but server must be
+ * configured to track case duration as well)
+ * @param {type} result
+ * @returns {unresolved}
+ */
+ $scope.next = function (result) {
+
+ if (parseInt($scope.assignment.completed, 10) === parseInt($scope.assignment.total, 10)) {
+ $scope.SYNERGY.modal.update("Testing finished", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var alreadyTested = (parseInt($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished, 10) === 1) ? true : false;
+ switch (result) {
+ case "passed":
+ if (!alreadyTested) {
+ $scope.assignment.completed++;
+ $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);
+
+ if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
+ } else {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
+ }
+ $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
+
+ }
+
+ var timeTaken = (new Date().getTime()) - started;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";
+
+ if (!$scope.SYNERGY.trackCaseDuration) {
+ timeTaken = $scope.currentCase.duration * 60000;
+ }
+
+ if ($scope.newIssue.length > 0) {
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = $scope.newIssue.split(" ");
+ } else {
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = "";
+ }
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration = timeTaken;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
+ parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);
+ break;
+ case "failed":
+ if ($scope.newIssue.length > 0) {
+ if (!alreadyTested) {
+ $scope.assignment.completed++;
+ $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);
+
+ if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
+ } else {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
+ }
+ $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
+
+ }
+ var timeTaken = (new Date().getTime()) - started;
+ if (!$scope.SYNERGY.trackCaseDuration) {
+ timeTaken = $scope.currentCase.duration * 60000;
+ }
+
+
+
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = $scope.newIssue.split(" ");
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration = timeTaken;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";
+ parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);
+
+ } else {
+ $scope.SYNERGY.modal.update("Missing issue number", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ break;
+ case "skipped":
+ if ($scope.currentCase.comment === -1) {
+ $scope.SYNERGY.modal.update("Missing reason for skipping", "Please select comment using the combo box below Skip button");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+
+
+ if (!alreadyTested) {
+ $scope.assignment.completed++;
+ $scope.casesFinished = parseInt(100 * (parseInt($scope.assignment.completed, 10) / parseInt($scope.assignment.total, 10)), 10);
+ if ($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].hasOwnProperty("originalDuration")) {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration), 10);
+ } else {
+ timeFinished += parseInt(Math.round($scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].duration), 10);
+ }
+ $scope.timeLeft = parseInt($scope.assignment.specificationData.estimation, 10) - timeFinished;
+ }
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].issue = "";
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].finished = 1;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].result = result;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].originalDuration = $scope.currentCase.duration;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].comment = $scope.currentCase.comment;
+ $scope.assignment.progress.specification.testSuites[$scope.suiteIndex].testCases[$scope.caseIndex].commentFreeText = ($scope.currentCase.comment !== -1) ? $scope.currentCase.commentFreeText.substr(0, 100) : "";
+ parseData($scope.caseIndex + 1, $scope.suiteIndex, $scope.currentCase.duration);
+ break;
+ default:
+ break;
+ }
+ $scope.somethingCompleted = true;
+ $scope.pauseButtonTitle = "Save current progress and continue later";
+ $scope.SYNERGY.cache.put("assignment_progress_" + $scope.id, {"date": new Date().toString(), "progress": $scope.assignment.progress});
+ collectCases();
+ $scope.caseToPrint = $scope.allCases.filter(function (e) {
+ return e.caseId === $scope.currentCase.caseId && e.suiteId === $scope.currentCase.suiteId;
+ })[0];
+ $scope.SYNERGY.util.scrollTo("caseTitle");
+ };
+
+ /**
+ * It goes through specification and sets case in suite with suiteId with caseId to current so it is displayed to user.
+ * If caseId is -1, first case in first suite is displayed
+ * @param {Number} caseId
+ * @param {Number} suiteId
+ */
+ function printCase(caseId, suiteId) {
+ $scope.newIssue = "";
+ if (caseId === -1) {
+ for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
+ for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {
+
+ $scope.suiteIndex = i;
+ $scope.caseIndex = j;
+ started = new Date().getTime();
+ $scope.currentCase = {
+ "title": $scope.assignment.specificationData.testSuites[i].testCases[j].title,
+ "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
+ "suiteId": $scope.assignment.specificationData.testSuites[i].id,
+ "images": $scope.assignment.specificationData.testSuites[i].testCases[j].images,
+ "duration": parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].duration, 10),
+ "steps": $scope.assignment.specificationData.testSuites[i].testCases[j].steps,
+ "suiteTitle": $scope.assignment.specificationData.testSuites[i].title,
+ "result": $scope.assignment.specificationData.testSuites[i].testCases[j].result,
+ "issues": $scope.assignment.specificationData.testSuites[i].testCases[j].issues,
+ "suiteSetup": $scope.assignment.specificationData.testSuites[i].desc,
+ "product": $scope.assignment.specificationData.testSuites[i].product,
+ "comment": -1,
+ "commentFreeText": "",
+ "progress": {"finished": 0, "id": caseId, "result": "", "duration": 0, "issue": [], "comment": -1, "commentFreeText": ""},
+ "component": $scope.assignment.specificationData.testSuites[i].component
+ };
+ return;
+ }
+ }
+
+ return;
+ }
+ for (var i = 0, max = $scope.assignment.specificationData.testSuites.length; i < max; i += 1) {
+ if (suiteId === parseInt($scope.assignment.specificationData.testSuites[i].id, 10)) {
+ for (var j = 0, max2 = $scope.assignment.specificationData.testSuites[i].testCases.length; j < max2; j += 1) {
+ if (caseId === parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].id, 10)) {
+ $scope.currentCase = {
+ "title": $scope.assignment.specificationData.testSuites[i].testCases[j].title,
+ "caseId": $scope.assignment.specificationData.testSuites[i].testCases[j].id,
+ "suiteId": $scope.assignment.specificationData.testSuites[i].id,
+ "images": $scope.assignment.specificationData.testSuites[i].testCases[j].images,
+ "duration": parseInt($scope.assignment.specificationData.testSuites[i].testCases[j].duration, 10),
+ "steps": $scope.assignment.specificationData.testSuites[i].testCases[j].steps,
+ "suiteTitle": $scope.assignment.specificationData.testSuites[i].title,
+ "result": $scope.assignment.specificationData.testSuites[i].testCases[j].result,
+ "issues": $scope.assignment.specificationData.testSuites[i].testCases[j].issues,
+ "suiteSetup": $scope.assignment.specificationData.testSuites[i].desc,
+ "product": $scope.assignment.specificationData.testSuites[i].product,
+ "comment": -1,
+ "commentFreeText": "",
+ "progress": getProgressForCase(caseId, suiteId),
+ "component": $scope.assignment.specificationData.testSuites[i].component
+ };
+ initValuesFromProgress();
+ started = new Date().getTime();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+
+ function initValuesFromProgress() {
+ // issues
+ if ($scope.currentCase.progress.issue && $scope.currentCase.progress.issue.length > 0) {
+ $scope.newIssue = $scope.currentCase.progress.issue.join(" ");
+ }
+ // comment
+ if ($scope.currentCase.progress.comment && $scope.currentCase.progress.comment > 0) {
+ $scope.currentCase.comment = $scope.currentCase.progress.comment;
+ $scope.currentCase.commentFreeText = ($scope.currentCase.progress.hasOwnProperty("commentFreeText")) ? $scope.currentCase.progress.commentFreeText : "";
+ }
+ }
+
+ function getProgressForCase(caseId, suiteId) {
+ for (var i = 0, max = $scope.assignment.progress.specification.testSuites.length; i < max; i++) {
+ if (parseInt($scope.assignment.progress.specification.testSuites[i].id, 10) === parseInt(suiteId, 10)) {
+ for (var j = 0, max2 = $scope.assignment.progress.specification.testSuites[i].testCases.length; j < max2; j++) {
+ if (parseInt($scope.assignment.progress.specification.testSuites[i].testCases[j].id, 10) === parseInt(caseId, 10)) {
+ return $scope.assignment.progress.specification.testSuites[i].testCases[j];
+ }
+ }
+ }
+ }
+ return {"finished": 0, "id": caseId, "result": "", "duration": 0, "issue": [], "comment": -1};
+ }
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+// ADMINISTRATION
+ function AdminHomeCtrl($scope) {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+
+ }
+ /**
+ *
+ * @param {VersionsFct} versionsHttp
+ * @param {VersionFct} versionHttp
+ */
+ function AdminVersionCtrl($scope, versionsHttp, versionHttp, SynergyModels) {
+
+ $scope.versions = [];
+ $scope.versionAffectedId = 0; // when editing version, modal dialog is opened, this is to show version name when typing new name
+ $scope.versionAffected = "";
+ $scope.newname = "";
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ versionsHttp.getAll($scope, function (data) {
+ $scope.versions = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Based on action parameter, it displays some window and sets versionId and version as to be affected by future modifications
+ * @param {String} action
+ * @param {Number} versionId
+ * @param {String} version
+ */
+ $scope.perform = function (action, versionId, version, isObsolete) {
+ switch (action) {
+ case "edit":
+ $scope.newname = version;
+ $scope.isObsolete = (parseInt(isObsolete, 10) === 1) ? true : false;
+ $scope.versionAffectedId = versionId;
+ $scope.versionAffected = version;
+ $("#editVersionModal").modal("toggle");
+ break;
+ case "create":
+ $scope.newname = "";
+ $("#createVersionModal").modal("toggle");
+ break;
+ case "delete":
+ $scope.versionAffectedId = versionId;
+ $scope.versionAffected = version;
+ $("#deleteModal").modal("toggle");
+ break;
+ default :
+ break;
+ }
+ };
+
+ /**
+ * Renames version
+ */
+ $scope.rename = function () {
+
+ if ($scope.myForm.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ versionHttp.edit($scope, new SynergyModels.Version($scope.newname, $scope.versionAffectedId, $scope.isObsolete), function () {
+ $scope.SYNERGY.logger.log("Done", "Version renamed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Deletes version
+ */
+ $scope.remove = function () {
+ $("#deleteModal").modal("toggle");
+ versionHttp.remove($scope, $scope.versionAffectedId, function () {
+ $scope.SYNERGY.logger.log("Done", "Version removed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Creates a new version
+ */
+ $scope.create = function () {
+
+ if ($scope.myForm2.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ versionHttp.create($scope, new SynergyModels.Version($scope.newname, -1, false), function (data) {
+ $scope.SYNERGY.logger.log("Done", "Version created", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {type} $scope
+ * @param {PlatformsFct} platformsHttp
+ * @param {PlatformFct} platformHttp
+ * @returns {undefined} */
+ function AdminPlatformsCtrl($scope, platformsHttp, platformHttp, SynergyModels) {
+
+ $scope.platforms = [];
+ $scope.platformAffectedId = 0;
+ $scope.platformAffected = "";
+ $scope.newname = "";
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ platformsHttp.get($scope, function (data) {
+ $scope.platforms = data;
+ }, $scope.generalHttpFactoryError, false);
+ };
+
+ /**
+ * Opens dialog based on action value and saves $scope.platformAffectedId and $scope.platformAffected (platform name and ID to be affected
+ * by future changes)
+ * @param {String} action action name
+ * @param {Number} platformId platform ID
+ * @param {Number} platform platform name
+ */
+ $scope.perform = function (action, platformId, platform, isActive) {
+ switch (action) {
+ case "edit":
+ $scope.platformAffectedId = platformId;
+ $scope.platformAffected = platform;
+ $scope.newname = platform;
+ $scope.isActive = (parseInt(isActive, 10) === 1) ? true : false;
+ $("#editPlatformModal").modal("toggle");
+ break;
+ case "create":
+ $("#createPlatformModal").modal("toggle");
+ break;
+ case "delete":
+ $scope.platformAffectedId = platformId;
+ $scope.platformAffected = platform;
+ $("#deleteModal").modal("toggle");
+ break;
+ default :
+ break;
+ }
+ };
+
+ /**
+ * Removes platform
+ */
+ $scope.remove = function () {
+ $("#deleteModal").modal("toggle");
+ platformHttp.remove($scope, $scope.platformAffectedId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Platform removed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Renames platform
+ */
+ $scope.rename = function () {
+
+ if ($scope.myForm.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ platformHttp.edit($scope, new SynergyModels.Platform($scope.newname, $scope.platformAffectedId, $scope.isActive), function (data) {
+ $scope.SYNERGY.logger.log("Done", "Platform renamed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Creates a new platform
+ * @returns {unresolved}
+ */
+ $scope.create = function () {
+
+ if ($scope.myForm2.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ platformHttp.create($scope, new SynergyModels.Platform($scope.newname, -1), function (data) {
+ $scope.SYNERGY.logger.log("Done", "Platform created", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {UsersFct} usersHttp
+ * @param {TribeFct} tribeHttp
+ * @param {TribesFct} tribesHttp
+ * @returns {undefined} */
+ function AdminTribesCtrl($scope, $location, $timeout, usersHttp, tribeHttp, tribesHttp, sanitizerHttp, SynergyUtils, SynergyModels) {
+
+ $scope.tribes = [];
+ $scope.tribe = {}; // for new tribe page
+ $scope.tribeAffectedId = 0;
+ $scope.tribeAffected = "";
+ $scope.newname = "";
+ $scope.users = [];
+ $scope.importTribesUrl = "http://" + window.location.host + "/dashboard/web/a_tribes.php?export=true";
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ loadUsers();
+ tribesHttp.get($scope, function (data) {
+ $scope.tribes = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+
+ $scope.importTribes = function () {
+ tribesHttp.importTribes($scope, $scope.importTribesUrl, function (data) {
+ $scope.SYNERGY.logger.log("Done", data + " tribes imported", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+
+ /**
+ * Removes tribe
+ */
+ $scope.deleteTribe = function () {
+ $("#deleteModal").modal("toggle");
+ tribeHttp.remove($scope, $scope.tribeAffectedId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Tribe removed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Redirects to URL based on action value
+ * @param {String} action action to be done
+ * @param {String} id Tribe ID
+ * @param {String} tribe Tribe name
+ */
+ $scope.perform = function (action, id, tribe) {
+ switch (action) {
+ case "create":
+ $location.path("administration/tribes/" + action);
+ break;
+ default :
+ break;
+ }
+ };
+
+ /**
+ * Shows delete confirmation dialog
+ * @param {Number} id tribe ID to be removed
+ * @param {String} tribe tribe name
+ */
+ $scope.deleteModal = function (id, tribe) {
+ $scope.tribeAffectedId = id;
+ $scope.tribeAffected = tribe;
+ $("#deleteModal").modal("toggle");
+ };
+
+ function loadUsers() {
+ usersHttp.getAll($scope, function (data) {
+ if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
+ for (var i = 0, max = data.users.length; i < max; i++) {
+ data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
+ }
+ }
+
+ $scope.users = data.users;
+ if (SynergyUtils.definedNotNull(data) && data.users.length > 0) {
+ $scope.tribe.leaderUsername = data.users[0].displayName;
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.tribe.name || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.tribe.description || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Creates a new tribe
+ */
+ $scope.create = function () {
+
+ if ($scope.myForm2.$invalid || $scope.myForm.$invalid) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var tribe = new SynergyModels.Tribe($scope.tribe.name, $scope.tribe.description, ($scope.tribe.leaderUsername), -1);
+ tribeHttp.create($scope, tribe, function (data) {
+ $scope.SYNERGY.modal.update("Tribe created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {RunsFct} runsHttp
+ * @param {RunFct} runHttp
+ */
+ function AdminRunsCtrl($scope, $location, $routeParams, runsHttp, runHttp) {
+ $scope.runs = [];
+ $scope.page = $routeParams.page || 1;
+ $scope.orderProp = "title";
+ $scope.next = 0;
+ $scope.prev = 0;
+ $scope.nextPage = 1;
+ $scope.prevPage = 1;
+ var currentActionId = -1;
+ var currentAction = "";
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ runsHttp.get($scope, $scope.page, function (data) {
+
+ data.testRuns.forEach(function (trun) {
+ if (trun.projectName === null || trun.projectName === "") {
+ trun.projectName = $scope.SYNERGY.product;
+ }
+ });
+
+ $scope.runs = data;
+ $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
+ $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
+ $scope.nextPage = parseInt($scope.page, 10) + 1;
+ $scope.prevPage = parseInt($scope.page, 10) - 1;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Unless action is delete, it redirects to action URL, otherwise opens confirmation dialog
+ * @param {String} action action
+ * @param {Number} id run ID
+ * @param {String} run run name
+ */
+ $scope.perform = function (action, id, run) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete test run?");
+ $("#deleteModalBody").html("<p>This action will also delete all test assignments for this test run. Do you want to continue?</p>");
+ $("#deleteModal").modal("toggle");
+ currentActionId = id;
+ currentAction = "delete";
+ break;
+ case "notify":
+ $("#deleteModalLabel").text("Send notifications?");
+ $("#deleteModalBody").html("<p>Do you really want to send email notifications to testers with incomplete test assignment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "notify";
+ currentActionId = id;
+ break;
+ case "freeze":
+ var _run = $scope.runs.testRuns.filter(function (e) {
+ return e.id === id;
+ })[0];
+
+ var target = (_run.isActive ? 0 : 1);
+ runHttp.freezeRun($scope, id, target, function (data) {
+ _run = $scope.runs.testRuns.filter(function (e) {
+ return e.id === id;
+ })[0];
+ _run.isActive = target;
+ $scope.SYNERGY.logger.log("Done", "Test run " + (target === 1 ? "unfrozen" : "frozen"), "INFO", "alert-success");
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ $location.path("administration/run/" + id + "/" + action);
+ break;
+ }
+ };
+
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "delete":
+ remove();
+ break;
+ case "notify":
+ $("#deleteModal").modal("toggle");
+ runHttp.sendNotifications($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", data, "INFO", "alert-success");
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Removes test run
+ */
+ function remove() {
+ $("#deleteModal").modal("toggle");
+ runHttp.remove($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Test run removed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ }
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ * @param {RunFct} runHttp
+ */
+ function AdminRunCtrl($scope, $routeParams, runHttp, $location, attachmentHttp, sanitizerHttp, projectsHttp, SynergyModels, SynergyHandlers) {
+ $scope.attachmentBase = $scope.SYNERGY.server.buildURL("run_attachment", {});
+ $scope.refreshCodemirror = false;
+ $scope.projects = [];
+ $scope.testRun = new SynergyModels.TestRun("", "", "", "", "");
+ $scope.id = $routeParams.id || -1;
+ var currentAction = "";
+ var currentActionId = -1;
+
+ try {
+ $("#start").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
+ $("#end").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
+ } catch (e) {
+ }
+ $scope.loadPreview = function () {
+ var _t = "<h1>" + ($scope.testRun.title || "") + "</h1><h3>Description</h3><div class='well'>" + ($scope.testRun.desc || "") + "</div>";
+ sanitizerHttp.getSanitizedInput($scope, _t, function (data) {
+ $scope.preview = data;
+ }, $scope.generalHttpFactoryError);
+ };
+ /**
+ * Starts with action on given run attachment
+ * Otherwise confirmation dialog is opened
+ * @param {String} action action name
+ * @param {Number} id attachment ID
+ */
+ $scope.performAttachment = function (action, id) {
+ switch (action) {
+ case "delete":
+ $("#deleteModalLabel").text("Delete attachment?");
+ $("#deleteModalBody").html("<p>Do you really want to delete attachment?</p>");
+ $("#deleteModal").modal("toggle");
+ currentAction = "deleteAttachment";
+ currentActionId = id;
+ break;
+ default:
+ break;
+ }
+ };
+
+ $scope.performAction = function () {
+ switch (currentAction) {
+ case "deleteAttachment":
+ $("#deleteModal").modal("toggle");
+ if (typeof $scope.SYNERGY.session.session_id === "undefined" || $scope.SYNERGY.session.session_id.length < 1) {
+ return;
+ }
+ attachmentHttp.removeRunAttachment($scope, currentActionId, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Attachment deleted", "INFO", "alert-success");
+ $scope.fetch();
+ }, function (data) {
+ $scope.SYNERGY.logger.log("Action failed", "", "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ $scope.fetch();
+ });
+ break;
+ default:
+ break;
+ }
+ };
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Creates a new test run
+ */
+ $scope.create = function () {
+ var start = $("#start").val();
+ var stop = $("#end").val();
+ if ($scope.myForm.$invalid || start.length < 1 || stop.length < 1) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var _run = new SynergyModels.TestRun($scope.testRun.title, $scope.testRun.desc, $scope.getUTCTime(start), $scope.getUTCTime(stop), $scope.id).setNotifications($scope.testRun.notifications);
+ _run.projectId = $scope.testRun.projectId;
+ runHttp.create($scope, _run, function (data) {
+ $scope.SYNERGY.modal.update("Test run created", "");
+ $scope.SYNERGY.modal.show();
+ $location.path("administration/runs/page/1");
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Modifies test run
+ */
+ $scope.edit = function () {
+ var start = $("#start").val();
+ var stop = $("#end").val();
+ if ($scope.myForm.$invalid || start.length < 1 || stop.length < 1) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var _run = new SynergyModels.TestRun($scope.testRun.title, $scope.testRun.desc, $scope.getUTCTime(start), $scope.getUTCTime(stop), $scope.id).setNotifications($scope.testRun.notifications);
+ _run.projectId = $scope.testRun.projectId;
+ runHttp.edit($scope, _run, function (data) {
+ $scope.SYNERGY.modal.update("Test run updated", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.uploadFile = function () {
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ $scope.fetch();
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ }).uploadFileFromFileChooser("fileToUpload");
+ };
+
+ $scope.validDates = function () {
+ $scope.testRun.start = $("#start").val();
+ $scope.testRun.stop = $("#end").val();
+ return ($scope.testRun.start.length < 1 || $scope.testRun.stop.length < 1) ? false : true;
+ };
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ projectsHttp.getAll($scope, function (data) {
+ $scope.projects = data;
+ if ($scope.testRun.projectName === null && $scope.projects.length > 0) {
+ $scope.testRun.projectId = $scope.projects[0].id;
+ }
+ }, $scope.generalHttpFactoryError);
+ var action = window.location + "";
+ action = action.substring(action.lastIndexOf("/") + 1);
+ switch (action) {
+ case "edit":
+ if ($scope.id < 0) {
+ return;
+ }
+ runHttp.getOverview($scope, false, $scope.id, function (data) {
+ $scope.testRun = data;
+ $scope.testRun.start = $scope.getLocalDateTime($scope.testRun.start);
+ $scope.testRun.end = $scope.getLocalDateTime($scope.testRun.end);
+ if ($scope.projects.length > 0 && data.projectName === null) {
+ $scope.testRun.projectId = $scope.projects[0].id;
+ }
+ $scope.refreshCodemirror = true;
+ }, $scope.generalHttpFactoryError);
+ break;
+ default:
+ break;
+ }
+ };
+
+ new SynergyHandlers.FileUploader([], "dropbox", $scope.SYNERGY.uploadFileLimit, $scope.SYNERGY.server.buildURL("run_attachment", {"id": $scope.id}), function (title, msg, level, style, fileName) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ $scope.fileName = fileName;
+ $scope.fetch();
+ }, function (title, msg, level, style) {
+ $scope.SYNERGY.logger.log(title, msg, level, style);
+ });
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {type} $scope
+ * @param {type} $routeParams
+ * @param {AssignmentsFct} assignmentsHttp
+ * @param {UsersFct} usersHttp
+ * @param {LabelsFct} labelsHttp
+ * @param {PlatformsFct} platformsHttp
+ * @param {TribesFct} tribesHttp
+ * @param {VersionsFct} versionsHttp
+ * @returns {undefined}
+ */
+ function AdminMatrixAssignmentCtrl($scope, $routeParams, assignmentsHttp, usersHttp, labelsHttp, platformsHttp, tribesHttp, versionsHttp, SynergyUtils) {
+ $scope.assignment = {platforms: [], users: [], tribes: [], runId: $routeParams.id};
+ $scope.selectedPlatforms = [];
+ $scope.platforms = [];
+ $scope.platformId = -1;
+ $scope.ready = 0; // if < 5, page shows "please wait" message
+
+ $scope.fetch = function () {
+ // load all required data
+ loadPlatforms();
+ loadLabels();
+ loadUsers();
+ loadVersions();
+ tribesHttp.get($scope, function (data) {
+ $scope.tribes = data;
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function loadVersions() {
+ versionsHttp.get($scope, false, function (data) {
+ $scope.versions = data;
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ function loadPlatforms() {
+ platformsHttp.get($scope, function (data) {
+ $scope.platforms = data;
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError, false);
+ }
+
+ function loadUsers() {
+ usersHttp.getAll($scope, function (data) {
+ if (SynergyUtils.definedNotNull(data) && SynergyUtils.definedNotNull(data.users)) {
+ for (var i = 0, max = data.users.length; i < max; i++) {
+ data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
+ }
+ }
+ $scope.ready++;
+ $scope.users = data.users;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ function loadLabels() {
+ labelsHttp.getAll($scope, function (data) {
+ $scope.ready++;
+ data.push({"label": "None", "id": "-1"});
+ $scope.labels = data;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.create = function () {
+ if (($scope.assignment.users.length < 1 && $scope.assignment.tribes.length < 1) || $scope.platforms.length < 1) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ $scope.showWaitDialog();
+ assignmentsHttp.create($scope, $scope.assignment, function (data) {
+ $scope.SYNERGY.modal.update("Assignment created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, function (data, status) {
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.logger.log("Action failed for users ", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+ /**
+ * Adds platform to selected
+ */
+ $scope.addSelectedPlatform = function () {
+ if (!$scope.platformId || platformAlreadySelected(parseInt($scope.platformId, 10))) { // avoid on load displaying empty
+ return;
+ }
+ $scope.assignment.platforms.push({id: parseInt($scope.platformId, 10), name: getPlatformName(parseInt($scope.platformId, 10))});
+ $scope.platformId = -1;
+ };
+
+ $scope.addSelectedTribe = function () {
+ if (!$scope.tribeId || tribeAlreadySelected(parseInt($scope.tribeId, 10))) { // avoid on load displaying empty
+ return;
+ }
+ $scope.assignment.tribes.push({id: parseInt($scope.tribeId, 10), name: getTribeName(parseInt($scope.tribeId, 10))});
+ $scope.tribeId = -1;
+ };
+
+ $scope.addSelectedUser = function () {
+ if (!$scope.username || userAlreadySelected($scope.username)) { // avoid on load displaying empty
+ return;
+ }
+ $scope.assignment.users.push({username: $scope.username, displayName: getDisplayName($scope.username)});
+ $scope.username = "";
+ };
+
+ function userAlreadySelected(username) {
+ for (var i = 0, max = $scope.assignment.users.length; i < max; i++) {
+ if (username === $scope.assignment.users[i].username) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function tribeAlreadySelected(tribeID) {
+ for (var i = 0, max = $scope.assignment.tribes.length; i < max; i++) {
+ if (tribeID === $scope.assignment.tribes[i].id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function platformAlreadySelected(platformID) {
+ for (var i = 0, max = $scope.assignment.platforms.length; i < max; i++) {
+ if (platformID === $scope.assignment.platforms[i].id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * removes platform from selected
+ * @param {Number} platformID
+ */
+ $scope.removeSelectedPlatform = function (platformID) {
+ for (var i = 0, max = $scope.assignment.platforms.length; i < max; i++) {
+ if (platformID === $scope.assignment.platforms[i].id) {
+ $scope.assignment.platforms.splice(i, 1);
+ $scope.platformId = -1;
+ return;
+ }
+ }
+ };
+
+ $scope.removeSelectedTribe = function (tribeId) {
+ for (var i = 0, max = $scope.assignment.tribes.length; i < max; i++) {
+ if (tribeId === $scope.assignment.tribes[i].id) {
+ $scope.assignment.tribes.splice(i, 1);
+ $scope.tribesId = -1;
+ return;
+ }
+ }
+ };
+
+ $scope.removeSelectedUser = function (username) {
+ for (var i = 0, max = $scope.assignment.users.length; i < max; i++) {
+ if (username === $scope.assignment.users[i].username) {
+ $scope.assignment.users.splice(i, 1);
+ $scope.username = -1;
+ return;
+ }
+ }
+ };
+ function getDisplayName(username) {
+ for (var i = 0, max = $scope.users.length; i < max; i++) {
+ if ($scope.users[i].username === username) {
+ return $scope.users[i].displayName;
+ }
+ }
+ return "oops";
+ }
+
+ function getTribeName(tribeID) {
+ for (var i = 0, max = $scope.tribes.length; i < max; i++) {
+ if ($scope.tribes[i].id === tribeID) {
+ return $scope.tribes[i].name;
+ }
+ }
+ return "oops";
+ }
+
+ function getPlatformName(platformID) {
+ for (var i = 0, max = $scope.platforms.length; i < max; i++) {
+ if ($scope.platforms[i].id === platformID) {
+ return $scope.platforms[i].name;
+ }
+ }
+ return "oops";
+ }
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {SpecificationsFct} specificationsHttp
+ * @param {AssignmentFct} assignmentHttp
+ * @param {UsersFct} usersHttp
+ * @param {LabelsFct} labelsHttp
+ * @param {PlatformsFct} platformsHttp description
+ * @param {TribesFct} tribesHttp description
+ * @returns {undefined} */
+ function AdminAssignmentCtrl($scope, $timeout, $routeParams, specificationsHttp, assignmentsHttp, usersHttp, labelsHttp, platformsHttp, tribesHttp, versionsHttp, assignmentHttp, runHttp, SynergyUtils, SynergyModels) {
+ $scope.testRunId = $routeParams.id;
+ $scope.assignments = [];
+ $scope.platforms = [];
+ $scope.users = [];
+ $scope.ready = 0; // if < 4, message is displayed
+ $scope.value = {};
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ loadPlatforms();
+ loadUsers();
+ loadLabels();
+ loadSpecifications();
+ };
+
+ function loadPlatforms() {
+ platformsHttp.get($scope, function (data) {
+ $scope.platforms = data;
+ $scope.ready++;
+ if (SynergyUtils.definedNotNull(data) && data.length > 0) {
+ $scope.value.platform = data[0];
+ }
+ }, $scope.generalHttpFactoryError, false);
+ }
+
+ function loadLabels() {
+ labelsHttp.getAll($scope, function (data) {
+ $scope.ready++;
+ data.push({"label": "None", "id": "-1"});
+ $scope.labels = data;
+ $scope.value.label = data[data.length - 1];
+ }, $scope.generalHttpFactoryError);
+ }
+
+ function loadUsers() {
+ usersHttp.getAll($scope, function (data) {
+ if (SynergyUtils.definedNotNull(data) && data.users) {
+ for (var i = 0, max = data.users.length; i < max; i++) {
+ data.users[i].displayName = data.users[i].firstName + " " + data.users[i].lastName + " (" + data.users[i].username + ")";
+ }
+ }
+ $scope.ready++;
+ $scope.users = data.users;
+ if (SynergyUtils.definedNotNull(data) && data.users.length > 0) {
+ $scope.value.user = $scope.users[0];
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ /**
+ * Adds assignment to the assignments array that will be sent to server
+ */
+ $scope.addAssignment = function () {
+ if (!$scope.value.specification) {
+ $scope.SYNERGY.logger.showMsg("Oops", "No specification selected (maybe tribe does not have any specification?)", "alert-error");
+ return;
+ }
+ var assignment = new SynergyModels.TestAssignment(parseInt($scope.value.platform.id, 10), $scope.value.user.username, parseInt($scope.value.label.id, 10));
+ assignment.specificationId = parseInt($scope.value.specification.id, 10);
+ assignment.testRunId = parseInt($scope.testRunId, 10);
+ assignment.display = {
+ user: $scope.value.user.firstName + " " + $scope.value.user.lastName,
+ label: $scope.value.label.label,
+ platform: $scope.value.platform.name,
+ duplicates: false,
+ specification: $scope.value.specification.value
+ };
+ $scope.assignments.push(assignment);
+ assignmentHttp.checkExists($scope, assignment).then(function (data) {
+ assignment.display.duplicates = true;
+ }, function (response) {
+ switch (response.status) {
+ case 404:
+ assignment.display.duplicates = false;
+ break;
+ default:
+ $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
+ break;
+ }
+ });
+ };
+
+ $scope.removeAssignment = function (index) {
+ $scope.assignments.splice(index, 1);
+ };
+
+ function loadSpecifications() {
+ runHttp.getSpecifications($scope, $scope.testRunId, function (data) {
+ $scope.specifications = [];
+ for (var i = 0, max = data.specifications.length; i < max; i += 1) {
+ $scope.specifications[i] = {
+ value: data.specifications[i].title + " (" + ((data.projectName === null || typeof data.projectName === "undefined") ? $scope.SYNERGY.product : data.projectName) + " " + data.specifications[i].version + ")",
+ info: "<a href='#/specification/" + data.specifications[i].id + "'>view</a>",
+ id: data.specifications[i].id,
+ type: "specification",
+ version: data.specifications[i].version
+ };
+ }
+ $scope.value.specification = $scope.specifications[0];
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Creates a new test assignment
+ * @returns {unresolved}
+ */
+ $scope.create = function () {
+
+ $scope.showWaitDialog();
+ assignmentsHttp.createForUsers($scope, $scope.assignments, function (data) {
+ $scope.SYNERGY.modal.update("Assignments created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, function (data) {
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.logger.log("Action failed ", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ };
+ }
+ /**
+ * @param {UserFct} userHttp
+ * @param {UsersFct} usersHttp
+ * @returns {undefined}
+ */
+ function AdminUsersCtrl($scope, $location, $routeParams, userHttp, usersHttp) {
+ $scope.users = [];
+ $scope.page = $routeParams.page || 1;
+ var currentUser = "";
+ $scope.importUsersUrl = "http://" + window.location.host + "/dashboard/web/a_netcatusers.php";
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ usersHttp.get($scope, $scope.page, function (data) {
+ $scope.users = data.users;
+ $scope.next = (data.nextUrl.length > 1) ? 1 : 0;
+ $scope.prev = (data.prevUrl.length > 1) ? 1 : 0;
+ $scope.nextPage = parseInt($scope.page, 10) + 1;
+ $scope.prevPage = parseInt($scope.page, 10) - 1;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.importUsers = function () {
+ usersHttp.importUsers($scope, $scope.importUsersUrl, function (data) {
+ $scope.SYNERGY.logger.log("Done", data + " users imported", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.retireUsers = function () {
+ usersHttp.retireUsersWithRole($scope, "tester", function () {
+ $scope.SYNERGY.logger.log("Done", "Removed users with role 'tester' retired", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.perform = function (action, username) {
+ if (action !== "delete") {
+ $location.path("administration/user/" + username + "/" + action);
+ } else {
+ switch (action) {
+ case "delete":
+ $("#deleteModal").modal("toggle");
+ currentUser = username;
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ $scope.newuser = function () {
+ $location.path("administration/user/-1/create");
+ };
+
+ /**
+ * Deletes user
+ */
+ $scope.deleteUser = function () {
+ $("#deleteModal").modal("toggle");
+ userHttp.remove($scope, currentUser, function (data) {
+ $scope.SYNERGY.logger.log("Done", "User removed", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {UserFct} userHttp
+ * @returns {undefined}
+ **/
+ function AdminUserCtrl($scope, $routeParams, userHttp, SynergyModels) {
+ $scope.user = {
+ };
+ $scope.username = $routeParams.username || "";
+ $scope.passwordChangeAllowed = !$scope.SYNERGY.useSSO;
+ $scope.updatePassword = false;
+ this.oldUsername = ""; // in case admin changes username, need to track of the old one
+ var self = this;
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ if ($scope.username === "-1") {
+ // new user
+ return;
+ }
+ userHttp.get($scope, $scope.username, function (data) {
+ self.oldUsername = $scope.username;
+ $scope.user = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+ /**
+ * Creates user
+ */
+ $scope.save = function () {
+ var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
+ if ($scope.myForm.$invalid || ($scope.updatePassword && invalidPassword)) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, $scope.user.role, -1);
+ u.email = $scope.user.email;
+ if ($scope.updatePassword) {
+ u.password = $scope.user.password;
+ }
+ u.emailNotifications = $scope.user.emailNotifications;
+ userHttp.create($scope, u, function (data) {
+ $scope.SYNERGY.modal.update("User created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ /**
+ * Updates user
+ * @returns {unresolved}
+ */
+ $scope.edit = function () {
+ var invalidPassword = typeof $scope.user.password === "undefined" || $scope.user.password === null || $scope.user.password.length < 1;
+ if ($scope.myForm.$invalid || ($scope.updatePassword && invalidPassword)) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var u = new SynergyModels.User($scope.user.firstName, $scope.user.lastName, $scope.user.username, $scope.user.role, -1, self.oldUsername);
+ u.email = $scope.user.email;
+ if ($scope.updatePassword) {
+ u.password = $scope.user.password;
+ }
+ u.emailNotifications = $scope.user.emailNotifications;
+ userHttp.edit($scope, u, function (data) {
+ $scope.SYNERGY.modal.update("User updated", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self2 = $scope;
+ $scope.init(function () {
+ self2.fetch();
+ });
+ }
+ /**
+ *
+ * @param {SettingsFct} settingsHttp
+ * @returns {undefined}
+ */
+ function AdminSettingCtrl($scope, settingsHttp) {
+ $scope.settings = [];
+ $scope.newValue = "";
+ $scope.newDesc = "";
+ $scope.newKey = "";
+
+ $scope.fetch = function () {
+ if (!$scope.SYNERGY.session.hasAdminRights()) {
+ return;
+ }
+ settingsHttp.get($scope, function (data) {
+ $scope.settings = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.save = function () {
+ for (var i = 0, limit = $scope.settings.length; i < limit; i += 1) {
+ if ($scope.settings[i].value.length < 1) {
+ $scope.SYNERGY.logger.log("Missing value: ", $scope.settings[i].key, "INFO", "alert-error");
+ return;
+ }
+ }
+ settingsHttp.edit($scope, $scope.settings, function (data) {
+ $scope.SYNERGY.logger.log("Done", "Settings updated", "INFO", "alert-success");
+ $scope.fetch();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.addSetting = function () {
+ $scope.settings.push({"value": $scope.newValue, "label": $scope.newDesc, "key": $scope.newKey});
+ $scope.newValue = $scope.newDesc = $scope.newKey = "";
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ /**
+ *
+ * @param {AboutFct} aboutHttp
+ * @returns {undefined}
+ */
+ function AboutCtrl($scope, aboutHttp) {
+ $scope.$emit("updateNavbar", {item: "nav_empty"});
+ $scope.statistics = [];
+
+ $scope.fetch = function () {
+ aboutHttp.get($scope, function (data) {
+ $scope.$emit("updateBreadcrumbs", {link: "about", title: "About"});
+ $scope.statistics = data;
+ }, $scope.generalHttpFactoryError);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ function TribesCtrl($scope, $location, $timeout, tribesHttp) {
+ $scope.$emit("updateNavbar", {item: "nav_empty"});
+ $scope.tribes = [];
+
+ $scope.fetch = function () {
+ tribesHttp.get($scope, function (data) {
+ $scope.tribes = data;
+ $scope.$emit("updateBreadcrumbs", {link: "tribes", title: "Tribes"});
+ }, $scope.generalHttpFactoryError, true);
+ };
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ function AdminLogCtrl($scope, logHttp, sessionRenewalHttp) {
+ $scope.logContent = "";
+ $scope.fetch = function () {
+ logHttp.get($scope, function (data) {
+ $scope.logContent = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.deleteLog = function () {
+ logHttp.remove($scope, function () {
+ $scope.logContent = "";
+ $scope.SYNERGY.logger.log("Deleted", "", "INFO", "alert-info");
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.sso = function () {
+ sessionRenewalHttp.test($scope, function (d) {
+ window.console.log("Done");
+ }, function (d) {
+ window.console.log("Opps, not done at all");
+ });
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ function AdminDatabaseCtrl($scope, databaseHttp) {
+ $scope.order = "ASC";
+ $scope.tables = [];
+ $scope.columns = [];
+ $scope.orderBy = "";
+ $scope.data = [];
+ $scope.limit = 10;
+ $scope.selectedTable = "";
+
+ $scope.fetch = function () {
+ databaseHttp.getTables($scope, function (data) {
+ $scope.tables = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.loadTable = function () {
+ databaseHttp.getColumns($scope, $scope.selectedTable, function (data) {
+ $scope.columns = data;
+ $scope.orderBy = data[0];
+ }, $scope.generalHttpFactoryError);
+ };
+
+ $scope.show = function () {
+ databaseHttp.listTable($scope, $scope.selectedTable, $scope.limit || 10, $scope.order, $scope.orderBy || "id", function (data) {
+ $scope.data = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ }
+ function AssignmentVolunteerCtrl($scope, $routeParams, specificationsHttp, assignmentHttp, labelsHttp, platformsHttp, versionsHttp, reviewHttp, runHttp, SynergyUtils, SynergyModels) {
+ $scope.suggestions = [];
+ $scope.specifications = [];
+ $scope.assignment = {
+ testRun: $routeParams.id,
+ username: "",
+ tribe: -1,
+ specification: "",
+ label: "",
+ platform: "",
+ specificationId: "",
+ labelId: "",
+ platformId: ""
+ };
+ $scope.platforms = [];
+ $scope.ready = 0;
+ $scope.assignmentType = "test";
+ $scope.reviewUrl;
+ $scope.availablePages = [];
+ $scope.reviewPage = null;
+ $scope.showOnlyNotUsedPages = true;
+ var deleteModalDisplayed = false;
+
+ $scope.initData = function () {
+ if ($scope.assignmentType === "review") {
+ $scope.changeAvailablePages();
+ }
+ };
+
+ $scope.fetch = function () {
+ loadPlatforms();
+ loadLabels();
+ loadVersions();
+ loadSpecifications();
+ };
+
+ $scope.changeAvailablePages = function () {
+ if ($scope.showOnlyNotUsedPages) {
+ reviewHttp.listNotStarted($scope, $scope.assignment.testRun, function (d) {
+ $scope.availablePages = d;
+ }, $scope.generalHttpFactoryError);
+ } else {
+ reviewHttp.list($scope, function (d) {
+ $scope.availablePages = d;
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ function loadPlatforms() {
+ platformsHttp.get($scope, function (data) {
+ $scope.platforms = data;
+ $scope.ready++;
+ if (SynergyUtils.definedNotNull(data) && data.length > 0) {
+ $scope.assignment.platform = data[0].id;
+ }
+ }, $scope.generalHttpFactoryError, true);
+ }
+
+ function loadVersions() {
+ versionsHttp.get($scope, true, function (data) {
+ $scope.versions = data;
+ $scope.ready++;
+ if (SynergyUtils.definedNotNull(data) && data.length > 0) {
+ $scope.selectedVersion = data[0].name;
+ $scope.filter();
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.filter = function () {
+ var matching = [];
+ for (var i = 0, max = $scope.specifications.length; i < max; i++) {
+ if ($scope.selectedVersion === $scope.specifications[i].version) {
+ matching.push($scope.specifications[i]);
+ }
+ }
+ $scope.suggestions = matching;
+ };
+
+
+ function loadLabels() {
+ labelsHttp.getAll($scope, function (data) {
+ $scope.ready++;
+ data.push({"label": "None", "id": "-1"});
+ $scope.labels = data;
+ $scope.assignment.label = data[data.length - 1].id;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ function loadSpecifications() {
+ runHttp.getSpecifications($scope, $routeParams.id, function (data) {
+ $scope.specifications = [];
+ for (var i = 0, max = data.specifications.length; i < max; i += 1) {
+ $scope.specifications[i] = {
+ value: data.specifications[i].title + " (" + ((data.projectName === null || typeof data.projectName === "undefined") ? $scope.SYNERGY.product : data.projectName) + " " + data.specifications[i].version + ")",
+ info: "<a href='#/specification/" + data.specifications[i].id + "'>view</a>",
+ id: data.specifications[i].id,
+ type: "specification",
+ version: data.specifications[i].version
+ };
+ }
+ $scope.filter();
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError);
+ }
+
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+
+
+ function checkAssignmentExists(assignment) {
+ assignmentHttp.checkExists($scope, assignment).then(function (data) {
+ $("#deleteModal").modal("toggle");
+ deleteModalDisplayed = true;
+ }, function (response) {
+ switch (response.status) {
+ case 404:
+ submitAssignmentData(assignment);
+ break;
+ default:
+ $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
+ break;
+ }
+ });
+ }
+
+ function submitAssignmentData(assignment) {
+ $scope.showWaitDialog();
+ assignmentHttp.createVolunteer($scope, assignment, function (data) {
+ $scope.SYNERGY.modal.update("Assignment created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, function (data) {
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ }
+
+
+ function submitReviewAssignment() {
+ if ($scope.reviewPage === null) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+ var reviewAssignment = new SynergyModels.ReviewAssignment($scope.SYNERGY.session.username, $scope.reviewPage.url);
+ reviewAssignment.owner = $scope.reviewPage.owner;
+ reviewAssignment.title = $scope.reviewPage.title;
+ reviewAssignment.testRunId = parseInt($scope.assignment.testRun, 10);
+
+ reviewHttp.createVolunteer($scope, reviewAssignment, function (data) {
+ $scope.SYNERGY.modal.update("Assignment created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, function (data) {
+ $scope.SYNERGY.modal.show();
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+
+ }
+
+ /**
+ * Creates a new test assignment
+ * @returns {unresolved}
+ */
+ $scope.create = function (skipDuplicateCheck) {
+
+ if ($scope.assignmentType === "review") {
+ submitReviewAssignment();
+ return;
+ }
+
+ var assignment = new SynergyModels.TestAssignment(parseInt($scope.assignment.platform, 10), $scope.SYNERGY.session.username, parseInt($scope.assignment.label, 10));
+ assignment.specificationId = parseInt($scope.assignment.specificationId, 10);
+ assignment.testRunId = parseInt($scope.assignment.testRun, 10);
+
+ if (!$scope.assignment.specificationId || assignment.platformId < 0) {
+ $scope.SYNERGY.modal.update("Missing required fields", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ if (($scope.assignedToUser && assignment.username.length < 1) || ($scope.assignedToTribe && $scope.assignment.tribe.id < 1)) {
+ $scope.SYNERGY.modal.update("Missing assignee", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ if (assignment.labelId < 0 && parseInt($scope.assignment.label, 10) > 0) {
+ $scope.SYNERGY.modal.update("Label does not exist", "");
+ $scope.SYNERGY.modal.show();
+ return;
+ }
+
+ if (!skipDuplicateCheck) {
+ checkAssignmentExists(assignment);
+ } else {
+ if (deleteModalDisplayed) {
+ $("#deleteModal").modal("toggle");
+ }
+ submitAssignmentData(assignment);
+ }
+ };
+ }
+ /**
+ *
+ * @param {type} $scope
+ * @param {type} $routeParams
+ * @param {RevisionsFct} revisionsHttp
+ * @returns {undefined}
+ */
+ function RevisionCtrl($scope, $routeParams, revisionsHttp) {
+
+ $scope.revisions = [];
+ $scope.specificationId = $routeParams.id;
+
+ $scope.fetch = function () {
+ revisionsHttp.listRevisions($scope, $scope.specificationId, function (data) {
+ $scope.revisions = data;
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+ /**
+ * Loads 2 revisions from server and calls diffRevisions() to print them
+ */
+ $scope.compareRevisions = function () {
+ revisionsHttp.getRevisions($scope, $scope.choiceA, $scope.choiceB, $scope.specificationId, function (data) {
+ diffRevisions(data[0], data[1]);
+ }, $scope.generalHttpFactoryError);
+ };
+
+ function diffRevisions(rev1, rev2) {
+ var base = difflib.stringAsLines(rev1.content);
+ var newtxt = difflib.stringAsLines(rev2.content);
+ var sm = new difflib.SequenceMatcher(base, newtxt);
+ var opcodes = sm.get_opcodes();
+ var diffoutputdiv = $("#diffoutput");
+ diffoutputdiv.html("");
+
+ diffoutputdiv.append(diffview.buildView({baseTextLines: base,
+ newTextLines: newtxt,
+ opcodes: opcodes,
+ baseTextName: $scope.getLocalTime(rev1.date, true) + "(" + rev1.author + ")",
+ newTextName: $scope.getLocalTime(rev2.date, true) + "(" + rev2.author + ")",
+ contextSize: null,
+ viewType: 1}));
+ }
+ }
+ function CalendarCtrl($scope, calendarHttp) {
+ $scope.$emit("updateBreadcrumbs", {link: "calendar", title: "Calendar"});
+
+ function loadCalendar() {
+ calendarHttp.getEvents($scope, function (data) {
+ var items = [];
+
+ for (var i = 0, max = data.length; i < max; i += 1) {
+ items.push({url: "#/run/" + data[i].id, title: data[i].title, start: new Date(data[i].start.substr(0, 4), parseInt(data[i].start.substr(4, 2), 10) - 1, data[i].start.substr(6, 2)), end: new Date(data[i].end.substr(0, 4), parseInt(data[i].end.substr(4, 2), 10) - 1, data[i].end.substr(6, 2))});
+ }
+
+ $("#calendar").fullCalendar({
+ header: {
+ left: "prev,next today",
+ center: "title",
+ right: "month,agendaWeek,agendaDay"
+ },
+ editable: true,
+ events: items,
+ eventMouseover: function (event, jsEvent, view) {
+ if (view.name !== "agendaDay") {
+ $(jsEvent.target).attr("title", event.title);
+ }
+ }
+ });
+ }, function () {
+ window.console.log("Failed to load calendar events");
+ });
+ }
+ loadCalendar();
+
+ }
+ function AssignmentTribeCtrl($scope, $routeParams, tribesHttp, labelsHttp, platformsHttp, assignmentsHttp, assignmentHttp, SynergyModels) {
+
+ $scope.tribes = [];
+ $scope.platforms = [];
+ $scope.labels = [];
+ $scope.ready = 0;
+ $scope.value = {};
+ $scope.assignments = [];
+ $scope.runId = $routeParams.id;
+
+ $scope.fetch = function () {
+ loadTribes();
+ loadLabels();
+ loadPlatforms();
+ };
+
+ function loadPlatforms() {
+ platformsHttp.get($scope, function (data) {
+ $scope.platforms = data;
+ $scope.value.platform = data[0];
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError, true);
+ }
+
+ function loadLabels() {
+ labelsHttp.getAll($scope, function (data) {
+ $scope.ready++;
+ data.push({"label": "None", "id": "-1"});
+ $scope.value.label = data[data.length - 1];
+ $scope.labels = data;
+ }, $scope.generalHttpFactoryError);
+ }
+ /**
+ * Adds assignment to the array of assignments to be sent to server
+ */
+ $scope.addAssignment = function () {
+ if (!$scope.value.specification) {
+ $scope.SYNERGY.logger.showMsg("Oops", "No specification selected (maybe tribe does not have any specification?)", "alert-error");
+ return;
+ }
+ var assignment = new SynergyModels.TestAssignment(parseInt($scope.value.platform.id, 10), $scope.value.user.username, parseInt($scope.value.label.id, 10));
+ assignment.specificationId = parseInt($scope.value.specification.id, 10);
+ assignment.testRunId = parseInt($scope.runId, 10);
+ assignment.tribeId = parseInt($scope.selectedTribe.id, 10);
+ assignment.display = {
+ tribe: $scope.selectedTribe.name,
+ user: $scope.value.user.firstName + " " + $scope.value.user.lastName,
+ label: $scope.value.label.label,
+ platform: $scope.value.platform.name,
+ specification: $scope.value.specification.title + " (" + $scope.SYNERGY.product + " " + $scope.value.specification.version + ")",
+ duplicates: false
+ };
+ $scope.assignments.push(assignment);
+ assignmentHttp.checkExists($scope, assignment).then(function (data) {
+ assignment.display.duplicates = true;
+ }, function (response) {
+ switch (response.status) {
+ case 404:
+ assignment.display.duplicates = false;
+ break;
+ default:
+ $scope.SYNERGY.logger.log("Action failed", "Unable to check for duplicates", "INFO", "alert-error");
+ break;
+ }
+ });
+ };
+ /**
+ * Respons to change of tribe, sets preselected specification and user
+ */
+ $scope.filterTribe = function () {
+ $scope.value.user = $scope.selectedTribe.members[0];
+ if ($scope.selectedTribe.ext.specifications) {
+ $scope.value.specification = $scope.selectedTribe.ext.specifications[0];
+ }
+ };
+
+ $scope.removeAssignment = function (index) {
+ $scope.assignments.splice(index, 1);
+ };
+
+ function setProject(data) {
+ for (var t = 0, max = data.length; t < max; t++) {
+ if (data[t].hasOwnProperty("ext") && data[t].ext.hasOwnProperty("specifications")) {
+ for (var s = 0, maxs = data[t].ext.specifications.length; s < maxs; s++) {
+ if (data[t].ext.specifications[s].hasOwnProperty("projects") && data[t].ext.specifications[s].projects.length > 0) {
+ data[t].ext.specifications[s]._project = data[t].ext.specifications[s].projects[0].name;
+ } else {
+ data[t].ext.specifications[s]._project = $scope.SYNERGY.product;
+ }
+ }
+ }
+ }
+ }
+
+ function loadTribes() {
+ tribesHttp.getTribesForRun($scope, $scope.SYNERGY.session.username, $scope.runId, function (data) {
+ setProject(data);
+ $scope.tribes = data;
+ $scope.selectedTribe = data[0];
+ $scope.value.user = data[0].members[0];
+ if (data[0].ext.specifications) {
+ $scope.value.specification = data[0].ext.specifications[0];
+ }
+ $scope.ready++;
+ }, $scope.generalHttpFactoryError);
+ }
+
+ $scope.cancel = function () {
+ window.history.back();
+ };
+ $scope.create = function () {
+ assignmentsHttp.createForTribes($scope, $scope.assignments, function (data) {
+ $scope.SYNERGY.modal.update("Assignments created", "");
+ $scope.SYNERGY.modal.show();
+ window.history.back();
+ }, $scope.generalHttpFactoryError);
+ };
+
+ var self = $scope;
+ $scope.init(function () {
+ self.fetch();
+ });
+
+ }
+ function StatisticsCtrl($scope, statisticsHttp, $routeParams, SynergyUtils, SynergyModels, SynergyHandlers, SynergyIssue) {
+ $scope.id = $routeParams.id;
+ $scope.timeView = "all";
+ $scope.inArchive = window.location.href.indexOf("archive") > 0 ? true : false;
+ var archivedData = {};
+ /**
+ * Order object for Tribes table
+ */
+ $scope.orderProp = {
+ prop: "time",
+ descending: true
+ };
+
+ try {
+ $("#start").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
+ $("#end").datetimepicker({dateFormat: "yy-mm-dd", timeFormat: "HH:mm:ss"});
+ } catch (e) {
+
+ }
+ $scope.P1Issues = [];
+ $scope.P2Issues = [];
+ $scope.P3Issues = [];
+ $scope.unresolvedIssues = [];
+ $scope.unknownIssues = [];
+ $scope.allIssues = [];
+ $scope.reviewTotal = {};
+
+ /**
+ * Order object for testers table
+ */
+ $scope.orderPropU = {
+ prop: "time",
+ descending: true
+ };
+ $scope.orderPropR = {
+ prop: "time",
+ descending: true
+ };
+
+ var cache = {};
+ $scope.tribes = [];
+ var tribesSpecs = [];
+ $scope.data = {};
+ $scope.computed = {};
+ var totalCounts = {};
+ var fallback = false;
+
+ $scope.fetch = function () {
+ if ($scope.inArchive) {
+ statisticsHttp.getArchive($scope, $scope.id, fallback, function (data) {
+ $scope.data = data;
+ cache = data;
+ tribesSpecs = data.tribes;
+ evaluateComputedData(data);
+ if (data.reviews) {
+ evaluateReviews(data.reviews);
+ }
+ }, function (data, status) {
+ if (status === 404 && !fallback) {
+ $scope.SYNERGY.logger.log("Opps", "Statistics not found, trying alternative way", "INFO", "alert-warning");
+ fallback = true;
+ self.fetch();
+ return;
+ }
+ $scope.SYNERGY.logger.log("Action failed", data, "INFO", "alert-error");
+ $scope.SYNERGY.logger.log("Action failed", data.toString(), "DEBUG", "alert-error");
+ });
+ } else {
+ statisticsHttp.get($scope, $scope.id, function (data) {
+ cache = data;
+ $scope.data = data;
+ tribesSpecs = data.tribes;
+ evaluateComputedData(data);
+ if (data.reviews) {
+ evaluateReviews(data.reviews);
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+ $scope.getAllTimeData = function () {
+ if ($scope.timeView === "all") {
+ $scope.data = cache;
+ tribesSpecs = cache.tribes;
+ evaluateComputedData(cache);
+ if ($scope.data.reviews) {
+ evaluateReviews($scope.data.reviews);
+ }
+ } else {
+ $("#start").val("");
+ $("#end").val("");
+ }
+ };
+
+ function evaluateReviews(reviews) {
+ var total = {
+ total: reviews.length,
+ completed: 0,
+ time: 0,
+ comments: 0,
+ reviewers: 0
+ };
+ var collector = new SynergyModels.UserReviewStats();
+ for (var i = 0, max = reviews.length; i < max; i++) {
+ if (reviews[i].isFinished) {
+ total.completed++;
+ }
+ total.time += reviews[i].timeTaken;
+ total.comments += reviews[i].numberOfComments;
+ collector.addReview(reviews[i]);
+ }
+ total.time = Math.floor(total.time / 60) + " hours and " + (total.time % 60) + " minutes";
+ $scope.reviews = collector.finish();
+ total.reviewers = $scope.reviews.length;
+ total.finishRate = (Math.round(1000 * total.completed / total.total) / 10) || 0;
+ $scope.reviewTotal = total;
+ }
+
+ /**
+ * Loads statistics for time period
+ */
+ $scope.filterStatistics = function () {
+ var start = $("#start").val();
+ var stop = $("#end").val();
+
+ if (start.length < 1 || stop.length < 1) {
+ $scope.SYNERGY.logger.log("Missing data", "Please specify both 'from' and 'to' time periods", "INFO", "alert-error");
+ return;
+ }
+
+ if ($scope.inArchive) {
+ filterArchivedData(start, stop);
+ } else {
+ statisticsHttp.getPeriod($scope, $scope.id, {"from": $scope.getUTCTime(start), "to": $scope.getUTCTime(stop)}, function (data) {
+ $scope.data = data;
+ tribesSpecs = data.tribes;
+ evaluateComputedData(data);
+ if ($scope.data.reviews) {
+ evaluateReviews($scope.data.reviews);
+ }
+ }, $scope.generalHttpFactoryError);
+ }
+ };
+
+
+ function filterArchivedData(start, stop) {
+ start = SynergyUtils.localToUTCTimestamp(start);
+ stop = SynergyUtils.localToUTCTimestamp(stop);
+ $scope.data = new SynergyHandlers.ArchiveDataFilter(SynergyUtils.shallowClone(cache)).getData(start, stop);
+ tribesSpecs = $scope.data.tribes;
+ evaluateComputedData($scope.data);
+ if ($scope.data.reviews) {
+ evaluateReviews($scope.data.reviews);
+ }
+ }
+
+ $scope.sortTribes = function (prop) {
+ if ($scope.orderProp.prop === prop) {
+ $scope.orderProp.descending = !$scope.orderProp.descending;
+ } else {
+ $scope.orderProp = {
+ prop: prop,
+ descending: true
+ };
+ }
+ };
+
+ $scope.sortUsers = function (prop) {
+ if ($scope.orderPropU.prop === prop) {
+ $scope.orderPropU.descending = !$scope.orderPropU.descending;
+ } else {
+ $scope.orderPropU = {
+ prop: prop,
+ descending: true
+ };
+ }
+ };
+ $scope.sortReviewers = function (prop) {
+ if ($scope.orderPropR.prop === prop) {
+ $scope.orderPropR.descending = !$scope.orderPropR.descending;
+ } else {
+ $scope.orderPropR = {
+ prop: prop,
+ descending: true
+ };
+ }
+ };
+
+ /**
+ * Goes through loaded data and count overall statistics, draw charts etc.
+ */
+ function evaluateComputedData(data) {
+ var computed = {};
+ computed.testers = getTesters(data);
+ computed.testers = computed.testers.filter(function (t) {
+ return t.completedCases > 0;
+ });
+ computed.testersCount = computed.testers.length;
+ computed.time = Math.floor(totalCounts.timeToComplete / 60) + " hours and " + (totalCounts.timeToComplete % 60) + " minutes";
+ computed.completedRelative = (Math.round(1000 * data.testRun.completed / data.testRun.total) / 10) || 0;
+ computed.passRate = (Math.round(1000 * totalCounts.passed / data.testRun.completed) / 10) || 0;
+ $scope.tribes = getTribesData(data);
+ $scope.issuesTotal = data.issues.length;
+ $scope.issuesUrl = $scope.SYNERGY.issues.viewLink(data.testRun.projectName, data.issues);
+ $scope.issueColor = getIssueColor(data.issues);
+ var issueCollector = new SynergyIssue.RunIssuesCollector();
+ issueCollector.addIssues(data.issues);
+ $scope.unresolvedIssues = issueCollector.issuesStats.unresolvedIssues;
+ $scope.P1Issues = issueCollector.issuesStats.P1Issues;
... 42685 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists