├── .venv
├── tests
├── __init__.py
└── changes
│ ├── __init__.py
│ ├── api
│ ├── __init__.py
│ ├── serializer
│ │ ├── __init__.py
│ │ └── models
│ │ │ ├── __init__.py
│ │ │ ├── test_cause.py
│ │ │ ├── test_project.py
│ │ │ ├── test_buildmessage.py
│ │ │ ├── test_bazeltargetmessage.py
│ │ │ ├── test_adminmessage.py
│ │ │ ├── test_change.py
│ │ │ └── test_logchunk.py
│ ├── validators
│ │ ├── __init__.py
│ │ ├── test_basic.py
│ │ └── test_datetime.py
│ ├── test_client.py
│ ├── test_node_details.py
│ ├── test_cluster_details.py
│ ├── test_change_details.py
│ ├── test_node_index.py
│ ├── test_cluster_index.py
│ ├── test_testcase_details.py
│ ├── test_project_plan_index.py
│ ├── test_cluster_nodes.py
│ ├── test_node_from_hostname.py
│ ├── test_build_mark_seen.py
│ ├── test_patch_details.py
│ ├── test_repository_project_index.py
│ ├── test_project_source_build_index.py
│ ├── test_build_message_index.py
│ ├── test_task_index.py
│ ├── test_job_artifact_index.py
│ └── test_build_target_message_index.py
│ ├── jobs
│ └── __init__.py
│ ├── lib
│ └── __init__.py
│ ├── models
│ └── __init__.py
│ ├── queue
│ └── __init__.py
│ ├── storage
│ └── __init__.py
│ ├── utils
│ ├── __init__.py
│ ├── test_trees.py
│ └── test_http.py
│ ├── vcs
│ └── __init__.py
│ ├── web
│ └── __init__.py
│ ├── artifacts
│ └── __init__.py
│ ├── backends
│ ├── __init__.py
│ └── jenkins
│ │ ├── __init__.py
│ │ ├── buildsteps
│ │ └── __init__.py
│ │ └── fixtures
│ │ └── GET
│ │ ├── queue_item_by_job_id.xml
│ │ ├── job_list_by_upstream.xml
│ │ ├── build_item_by_job_id.xml
│ │ └── node_config.xml
│ ├── listeners
│ └── __init__.py
│ └── test_config.py
├── changes
├── api
│ ├── __init__.py
│ ├── validators
│ │ ├── __init__.py
│ │ ├── basic.py
│ │ ├── datetime.py
│ │ └── author.py
│ ├── serializer
│ │ ├── vcs
│ │ │ ├── __init__.py
│ │ │ └── revisionresult.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── latest_green_build.py
│ │ │ ├── node.py
│ │ │ ├── cluster.py
│ │ │ ├── comment.py
│ │ │ ├── adminmessage.py
│ │ │ ├── buildmessage.py
│ │ │ ├── testartifact.py
│ │ │ ├── bazeltargetmessage.py
│ │ │ ├── artifact.py
│ │ │ ├── revisionresult.py
│ │ │ ├── snapshotimage.py
│ │ │ ├── logchunk.py
│ │ │ ├── author.py
│ │ │ ├── event.py
│ │ │ ├── patch.py
│ │ │ ├── project.py
│ │ │ ├── change.py
│ │ │ ├── jobphase.py
│ │ │ ├── command.py
│ │ │ └── task.py
│ │ └── __init__.py
│ ├── cluster_index.py
│ ├── cached_snapshot_cluster_details.py
│ ├── change_details.py
│ ├── node_details.py
│ ├── build_coverage.py
│ ├── patch_details.py
│ ├── build_message_index.py
│ ├── node_from_hostname.py
│ ├── repository_project_index.py
│ ├── cluster_details.py
│ ├── build_target_message_index.py
│ ├── auth_index.py
│ ├── job_artifact_index.py
│ ├── task_index.py
│ ├── build_mark_seen.py
│ ├── controller.py
│ ├── task_details.py
│ ├── build_test_index_counts.py
│ ├── node_job_index.py
│ └── snapshotimage_details.py
├── db
│ ├── __init__.py
│ ├── types
│ │ ├── __init__.py
│ │ └── enum.py
│ └── funcs
│ │ ├── __init__.py
│ │ └── coalesce.py
├── ext
│ └── __init__.py
├── vcs
│ └── __init__.py
├── web
│ └── __init__.py
├── backends
│ ├── __init__.py
│ ├── jenkins
│ │ ├── __init__.py
│ │ └── buildsteps
│ │ │ └── __init__.py
│ └── base.py
├── debug
│ ├── __init__.py
│ ├── mail
│ │ ├── __init__.py
│ │ └── build_result.py
│ └── reports
│ │ ├── __init__.py
│ │ └── build.py
├── jobs
│ ├── __init__.py
│ ├── generate_report.py
│ └── check_repos.py
├── models
│ ├── __init__.py
│ └── itemsequence.py
├── queue
│ └── __init__.py
├── reports
│ └── __init__.py
├── storage
│ ├── __init__.py
│ └── base.py
├── utils
│ ├── __init__.py
│ ├── times.py
│ ├── dirs.py
│ ├── cache.py
│ ├── slugs.py
│ └── locking.py
├── artifacts
│ ├── __init__.py
│ └── dummylogfile.py
├── buildsteps
│ ├── __init__.py
│ ├── dummy.py
│ └── lxc.py
├── expanders
│ └── __init__.py
├── listeners
│ ├── __init__.py
│ └── snapshot_build.py
├── lib
│ ├── __init__.py
│ ├── build_lib.py
│ └── project_lib.py
├── url_converters
│ ├── __init__.py
│ └── uuid.py
├── experimental
│ └── __init__.py
├── testutils
│ └── __init__.py
├── buildfailures
│ ├── base.py
│ ├── timeout.py
│ ├── malformedartifact.py
│ ├── duplicatetestname.py
│ ├── missingmanifestjson.py
│ ├── missingtests.py
│ ├── missingtargets.py
│ ├── malformedmanifestjson.py
│ ├── missingartifact.py
│ └── testfailure.py
└── app.py
├── linters
└── __init__.py
├── test_simple
├── .eslintignore
├── static
└── vendor
│ ├── requirejs-babel
│ ├── .gitignore
│ ├── bower.json
│ ├── README.md
│ └── .bower.json
│ ├── classnames
│ ├── .npmignore
│ ├── .travis.yml
│ └── bower.json
│ ├── react
│ ├── bower.json
│ ├── .bower.json
│ ├── react-dom.min.js
│ └── react-dom-server.min.js
│ ├── requirejs
│ ├── README.md
│ ├── bower.json
│ └── .bower.json
│ ├── uri.js
│ ├── bower.json
│ └── .bower.json
│ ├── underscore
│ ├── bower.json
│ └── .bower.json
│ ├── moment
│ ├── component.json
│ ├── lang
│ │ └── readme.md
│ └── .bower.json
│ └── react-bootstrap
│ ├── bower.json
│ ├── README.md
│ └── .bower.json
├── .bowerrc
├── migrations
├── README
├── versions
│ ├── 4134b4818694_remove_index_build_p.py
│ ├── fe743605e1a_remove_repositoryoption.py
│ ├── 5aad6f742b77_parent_revision_sha_.py
│ ├── 4f955feb6d07_rename_build_job.py
│ ├── f2c8d15416b_index_build_project_.py
│ ├── 105d4dd82a0a_date_indexes_on_test.py
│ ├── 2622a69cd25a_unique_node_label.py
│ ├── 4901f27ade8e_fill_target.py
│ ├── 5508859bed73_index_source_patch_i.py
│ ├── 34157be0e8a2_index_patch_project_.py
│ ├── 42e9d35a4098_rename_buildstep_job.py
│ ├── 545ba163595_rename_buildfamily_b.py
│ ├── 6483270c001_rename_buildplan_job.py
│ ├── 516f04a1a754_rename_buildphase_jo.py
│ ├── 97786e74292_add_task_data.py
│ ├── 4ae598236ef1_unused_indexes_on_jo.py
│ ├── 181adec926e2_add_status_index_to_task.py
│ ├── 21b7c3b2ce88_index_build_project_.py
│ ├── 36d7c98ddfee_drop_projectplan_table.py
│ ├── 21a9d1ebe15c_add_jobplan_data.py
│ ├── 26c0affcb18a_add_jobstep_data.py
│ ├── 315e787e94bf_add_source_data.py
│ ├── 4ba1dd8c3080_add_index_for_projec.py
│ ├── 4d235d421320_add_artifact_file.py
│ ├── bb141e41aab_add_rerun_count_to_t.py
│ ├── f8f72eecc7f_remove_idx_testgroup.py
│ ├── 26f665189ca0_remove_jobstep_jobph.py
│ ├── 2b4219a6cf46_jobphase_build_id_jo.py
│ ├── 35af40cebcde_jobplan_build_id_job.py
│ ├── f26b6cb3c9c_jobstep_build_id_job.py
│ ├── 10403d977f5f_add_plan_status.py
│ ├── 17d61d9621cb_add_command_type.py
│ ├── 4114cbbd0573_index_job_status_dat.py
│ ├── 4ffb7e1df217_unique_jobphase_job_.py
│ ├── 51775a13339d_patch_hash_column.py
│ ├── 545e104c5f5a_add_logsource_step_i.py
│ ├── 3a3366fb7822_index_testgroup_test.py
│ ├── 3c93047c66c8_add_index_to_jobstep_status.py
│ ├── 46a612aff7d3_add_snapshot_source_id.py
│ ├── 19168fe64c41_unique_constraint_on_command.py
│ ├── 256ab638deaa_filecoverage_build_i.py
│ ├── 2596f21c6f58_add_project_status.py
│ ├── 36cbde703cc0_add_build_priority.py
│ ├── 41c7ef24fd4c_add_build_date_decided.py
│ ├── 4c3f2a63b7a7_add_plan_avg_build_time.py
│ ├── 3d8177efcfe1_add_filecoverage_step_id.py
│ ├── 2b7153fe25af_add_repository_status.py
│ ├── 2d9df8a3103c_add_logchunk_unique_.py
│ ├── 2da2dd72d21c_add_snapshot_date_created.py
│ ├── 3961ccb5d884_increase_artifact_name_length.py
│ ├── 016f138b2da8_add_job_autogenerated.py
│ ├── 15bd4b7e6622_add_filecoverage_unique_constraint.py
│ ├── 208224023555_add_repository_backe.py
│ ├── 2fa8391277a0_add_jobstep_last_heartbeat.py
│ ├── 3f289637f530_remove_unused_models.py
│ ├── 520eba1ce36e_remove_author_name_u.py
│ ├── 554f414d4c46_set_on_delete_cascad.py
│ ├── f8ed1f99eb1_add_projectplan_avg_.py
│ ├── 57e4a0553903_add_snapshotimage_status.py
│ ├── 8550c7394f4_change_commands_env_to_text.py
│ ├── 18c150792a21_add_column_flakyteststat_first_run.py
│ ├── 30b9f619c3b2_remove_test_suite.py
│ ├── 17c884f6f57c_add_failurereason_column.py
│ ├── 1b229c83511d_add_logsource_in_artifact_store.py
│ ├── d324e8fc580_add_various_testsuit.py
│ ├── 291978a5d5e4_add_revision_date_created_signal.py
│ ├── 3aed22af8f4f_add_build_tag.py
│ ├── 1db7a1ab95db_fix_filecoverage_pro.py
│ ├── 3b22c1663664_change_logsource_constraint.py
│ ├── 3042d0ca43bf_index_job_project_id.py
│ ├── 4a12e7f0159d_remove_build_repository_id_revision_sha_.py
│ ├── 5844fbc9d9e4_add_flakyteststat_double_reruns.py
│ ├── 36becb086fcb_add_revision_branche.py
│ ├── 3d9067f21201_unique_step_order.py
│ ├── 2d82db02b3ef_fill_testgroup_num_l.py
│ ├── 3b94584c761e_create_index_in_flakyteststat_for_test_.py
│ ├── 382745ed4478_add_jobplan_snapshotimage.py
│ ├── 40429d3569cf_add_plan_project_id.py
│ ├── cb99fdfb903_add_build_family_id.py
│ ├── 460741ff1212_add_project_permissions_column_to_user.py
│ ├── 1d806848a73f_unique_buildplan_bui.py
│ ├── 45e1cfacfc7d_task_parent_id_is_op.py
│ ├── 15f131d88adf_remove_patch_project.py
│ ├── 1ad857c78a0d_family_id_build_id.py
│ ├── 3df65ebfa27e_remove_patch_label_and_message.py
│ ├── 1cb3db431e29_add_build_collection_id.py
│ ├── 290a71847cd7_add_jobstep_cluster.py
│ ├── 57e24a9f2290_log_build_id_job_id.py
│ ├── 3265d2120c82_add_testcase_step_id.py
│ ├── 3e85891e90bb_add_jobstep_replacement_id.py
│ ├── 12569fada93_require_plan_project_id.py
│ ├── 2f3ba1e84a6f_update_source_constr.py
│ ├── 3bef10ab5088_add_various_testgrou.py
│ ├── 52bcea82b482_remove_patch_url.py
│ ├── 3bf1066f4935_add_label_in_testmessage.py
│ ├── 47e23df5a7ed_add_testgroup_result.py
│ ├── 3cf79ad9bde_add_snapshot_plan_id_to_plan.py
│ ├── 14491da59392_add_build_revision_s.py
│ ├── fd1a52fe89f_add_various_test_ind.py
│ ├── 4640ecd97c82_add_testgroup_num_le.py
│ ├── 139e272152de_index_jobphase.py
│ ├── 4d5d239d53b4_set_on_delete_cascad.py
│ ├── 306fefe51dc6_set_on_delete_cascad.py
│ ├── 380d20771802_project_plan.py
│ ├── 3be107806e62_make_testartifact_cascade_on_test_delete.py
│ ├── 5677ef75c712_add_user.py
│ ├── 37e782a55ca6_set_on_delete_cascad.py
│ ├── 524b3c27203b_remove_job_author_id.py
│ ├── 4768ba7627ac_adjust_command_schema.py
│ ├── 5026dbcee21d_test_build_id_job_id.py
│ ├── 19910015c867_add_systemoption.py
│ ├── 11a7a7f2652f_add_itemstat.py
│ ├── 18c045b75331_add_various_build_in.py
│ ├── 1b53d33197bf_add_build_cause_and_.py
│ ├── 21c9439330f_add_cascade_to_filec.py
│ ├── 2d8acbec66df_add_build_target.py
│ ├── 4d302aa44bc8_add_additional_revis.py
│ ├── 2b1b01d74175_added_adminmessage_table.py
│ ├── 32274e97552_build_parent_build_p.py
│ ├── 346c011ca77a_optional_parent_revi.py
│ ├── 25d63e09ea3b_add_build_job_number.py
│ └── 215db24a630a_add_repository_last_.py
└── script.py.mako
├── .jshintignore
├── bin
├── worker
├── lint
└── shell
├── webapp
├── favicon.ico
├── css
│ ├── ABOUT_THE_CSS
│ ├── bundle_definition.less
│ ├── bundle_definition_custom.less
│ ├── bundle_definition_colorblind.less
│ ├── bundle_definition_custom_colorblind.less
│ ├── wiki_external_link.svg
│ └── bootstrap_config.json
├── custom
│ └── README
├── pages
│ ├── project_page
│ │ └── triage_tab.js
│ └── examples_page.js
└── display
│ └── section_header.js
├── docs
├── images
│ ├── example.png
│ ├── build-email.png
│ ├── console-log.png
│ └── build-details.png
├── _themes
│ └── kr
│ │ ├── theme.conf
│ │ ├── layout.html
│ │ └── relations.html
├── integrations
│ └── phabricator.rst
└── examples
│ └── changes.conf.py
├── docker
├── README
└── supervisor-run
├── .coveragerc
├── CONTRIBUTING.rst
├── .sublimelinterrc
├── ci
├── README.txt
├── run_tests.sh
└── mypy-run
├── .travis.yml
├── setup.cfg
├── .jshintrc
├── test_requirements.txt
├── bower.json
├── templates
└── debug
│ └── email.html
├── .gitignore
├── .arclint
└── README.rst
/.venv:
--------------------------------------------------------------------------------
1 | changes
2 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/ext/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/vcs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/linters/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test_simple:
--------------------------------------------------------------------------------
1 | testing
2 |
--------------------------------------------------------------------------------
/changes/backends/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/db/types/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/debug/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/jobs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/queue/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/reports/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/storage/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webapp/dist
2 |
--------------------------------------------------------------------------------
/changes/artifacts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/buildsteps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/debug/mail/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/debug/reports/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/expanders/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/jobs/generate_report.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/listeners/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/jobs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/queue/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/storage/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/vcs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/api/validators/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/backends/jenkins/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/lib/__init__.py:
--------------------------------------------------------------------------------
1 | # Woo!
2 |
--------------------------------------------------------------------------------
/changes/url_converters/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/artifacts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/backends/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/listeners/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/api/validators/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/changes/backends/jenkins/buildsteps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/vendor/requirejs-babel/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/buildsteps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "static/vendor"
3 | }
4 |
--------------------------------------------------------------------------------
/changes/experimental/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'ar'
2 |
--------------------------------------------------------------------------------
/migrations/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/changes/db/funcs/__init__.py:
--------------------------------------------------------------------------------
1 | from .coalesce import * # NOQA
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | static/vendor/
2 | static/js/lib/
3 | static/js/modules/scalyr.js
4 |
--------------------------------------------------------------------------------
/bin/worker:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eux
2 |
3 | celery -A changes.app:celery worker $@
4 |
--------------------------------------------------------------------------------
/static/vendor/classnames/.npmignore:
--------------------------------------------------------------------------------
1 | /.*/
2 | /.*
3 | /benchmarks/
4 | /tests/
5 |
--------------------------------------------------------------------------------
/webapp/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/changes/master/webapp/favicon.ico
--------------------------------------------------------------------------------
/docs/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/changes/master/docs/images/example.png
--------------------------------------------------------------------------------
/docker/README:
--------------------------------------------------------------------------------
1 | The files in this directory are (mostly) copied into the container and used
2 | there.
3 |
--------------------------------------------------------------------------------
/docs/images/build-email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/changes/master/docs/images/build-email.png
--------------------------------------------------------------------------------
/docs/images/console-log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/changes/master/docs/images/console-log.png
--------------------------------------------------------------------------------
/docs/images/build-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dropbox/changes/master/docs/images/build-details.png
--------------------------------------------------------------------------------
/bin/lint:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | PYTHONPATH="$(dirname "$0")/.." exec python -m linters.main "$@" --arc-out
4 |
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/fixtures/GET/queue_item_by_job_id.xml:
--------------------------------------------------------------------------------
1 |
2 | 13
3 | 14
4 |
5 |
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/fixtures/GET/job_list_by_upstream.xml:
--------------------------------------------------------------------------------
1 | 172171
2 |
--------------------------------------------------------------------------------
/static/vendor/react/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "main": ["react.js", "react-dom.js"],
4 | "ignore": []
5 | }
6 |
--------------------------------------------------------------------------------
/static/vendor/requirejs/README.md:
--------------------------------------------------------------------------------
1 | # requirejs-bower
2 |
3 | Bower packaging for [RequireJS](http://requirejs.org).
4 |
5 |
--------------------------------------------------------------------------------
/changes/testutils/__init__.py:
--------------------------------------------------------------------------------
1 | from .cases import * # NOQA
2 | from .helpers import * # NOQA
3 | from .fixtures import * # NOQA
4 |
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/fixtures/GET/build_item_by_job_id.xml:
--------------------------------------------------------------------------------
1 |
2 | 1
3 | 0
4 |
5 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source =
3 | changes
4 | tests
5 | omit =
6 | changes/mock.py
7 |
8 | [xml]
9 | output=coverage.xml
10 |
--------------------------------------------------------------------------------
/changes/api/serializer/vcs/__init__.py:
--------------------------------------------------------------------------------
1 | from changes.utils.imports import import_submodules
2 |
3 | import_submodules(globals(), __name__, __path__)
4 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/__init__.py:
--------------------------------------------------------------------------------
1 | from changes.utils.imports import import_submodules
2 |
3 | import_submodules(globals(), __name__, __path__)
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | Contributing to Changes
2 | -----------------------
3 |
4 | To get started, you'll need to sign Dropbox's CLA:
5 |
6 | https://opensource.dropbox.com/cla/
7 |
--------------------------------------------------------------------------------
/docs/_themes/kr/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = basic
3 | stylesheet = flasky.css
4 | pygments_style = flask_theme_support.FlaskyStyle
5 |
6 | [options]
7 | touch_icon =
8 |
--------------------------------------------------------------------------------
/changes/api/serializer/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import * # NOQA
2 |
3 | # register models
4 | from . import models # NOQA
5 |
6 | # register VCS
7 | from . import vcs # NOQA
8 |
--------------------------------------------------------------------------------
/changes/buildfailures/base.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 |
4 | class BuildFailure(object):
5 | def get_html_label(self, build):
6 | raise NotImplementedError
7 |
--------------------------------------------------------------------------------
/webapp/css/ABOUT_THE_CSS:
--------------------------------------------------------------------------------
1 | The default version of changes is not that pretty. We suggest writing some
2 | custom CSS to style it to look more like your own organization's product /
3 | tooling
4 |
--------------------------------------------------------------------------------
/webapp/css/bundle_definition.less:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | @import "color_classes";
4 | @import "layout_helpers";
5 | @import "basics";
6 | @import "display";
7 | @import "page_specific";
8 |
--------------------------------------------------------------------------------
/.sublimelinterrc:
--------------------------------------------------------------------------------
1 | {
2 | "@python": "2.7",
3 | "linters": {
4 | "pep8": {
5 | "ignore": "E501",
6 | "max-line-length": "100"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ci/README.txt:
--------------------------------------------------------------------------------
1 | These are the scripts used by Changes to setup and run the tests for Changes.
2 |
3 | The intention is that 'setup.sh' is run first, followed by 'run_tests.sh', and
4 | they should be safe to rerun.
5 |
--------------------------------------------------------------------------------
/webapp/custom/README:
--------------------------------------------------------------------------------
1 | This directory is for custom css or a js file containing custom content hooks.
2 | Ideally, just put your stuff in version control and check it out here, then
3 | change the config parameters in changes
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | services:
3 | - postgresql
4 | - redis-server
5 | python:
6 | - "2.7"
7 | install:
8 | - make develop
9 | script:
10 | - hg --version
11 | - git --version
12 | - make test
13 |
--------------------------------------------------------------------------------
/webapp/css/bundle_definition_custom.less:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "@{custom_css}";
3 |
4 | @import "color_classes";
5 | @import "layout_helpers";
6 | @import "basics";
7 | @import "display";
8 | @import "page_specific";
9 |
--------------------------------------------------------------------------------
/static/vendor/classnames/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | before_install:
3 | - "npm install npm -g"
4 | node_js:
5 | - "0.10"
6 | - "0.12"
7 | - "iojs"
8 | env:
9 | - TEST_SUITE=unit
10 | script: "npm run-script $TEST_SUITE"
11 |
--------------------------------------------------------------------------------
/webapp/pages/project_page/triage_tab.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | var TriageTab = React.createClass({
4 |
5 | render: function() {
6 | return
Coming soon!
;
7 | },
8 | })
9 |
10 | export default TriageTab;
11 |
--------------------------------------------------------------------------------
/changes/app.py:
--------------------------------------------------------------------------------
1 | """
2 | This file acts as a default entry point for app creation.
3 | """
4 |
5 | from changes.config import create_app, queue
6 |
7 | app = create_app()
8 |
9 | # HACK(dcramer): this allows Celery to detect itself -.-
10 | celery = queue.celery
11 |
--------------------------------------------------------------------------------
/webapp/css/bundle_definition_colorblind.less:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | /* red-green colorblind support */
4 | @green: @blue;
5 |
6 | @import "color_classes";
7 | @import "layout_helpers";
8 | @import "basics";
9 | @import "display";
10 | @import "page_specific";
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts=--tb=short
3 | norecursedirs=partials static static-built htmlcov docs node_modules .* *.egg-info{args} env
4 |
5 | [flake8]
6 | ignore = F999,E501,E128,E124,E126,F841,E123
7 | max-line-length = 100
8 | exclude = env,.svn,CVS,.bzr,.hg,.git,__pycache
9 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_cause.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import serialize
2 | from changes.constants import Cause
3 |
4 |
5 | def test_simple():
6 | result = serialize(Cause.retry)
7 | assert result['id'] == 'retry'
8 | assert result['name'] == 'Retry'
9 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "strict": true,
3 | "browser": true,
4 | "jquery": true,
5 | "predef": [
6 | "G_vmlCanvasManager",
7 | "EventSource",
8 | "angular",
9 | "console",
10 | "define",
11 | "require",
12 | "nv",
13 | "d3"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/changes/buildfailures/timeout.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class Timeout(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('The build timed out.')
11 |
--------------------------------------------------------------------------------
/webapp/css/bundle_definition_custom_colorblind.less:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "@{custom_css}";
3 |
4 | /* red-green colorblind support */
5 | @green: @blue;
6 |
7 | @import "color_classes";
8 | @import "layout_helpers";
9 | @import "basics";
10 | @import "display";
11 | @import "page_specific";
12 |
--------------------------------------------------------------------------------
/bin/shell:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import absolute_import
3 |
4 | from changes.config import create_app
5 | from werkzeug import script
6 |
7 | app = create_app()
8 | app_context = app.app_context()
9 | app_context.push()
10 |
11 | script.make_shell(lambda: {"app": app}, use_ipython=True)()
12 |
--------------------------------------------------------------------------------
/static/vendor/uri.js/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "URIjs",
3 | "version": "1.15.2",
4 | "main": "src/URI.js",
5 | "ignore": [
6 | ".*",
7 | "*.css",
8 | "/*.js",
9 | "/*.html",
10 | "/*.json",
11 | "utils",
12 | "test",
13 | "prettify"
14 | ],
15 | "license": "MIT"
16 | }
17 |
--------------------------------------------------------------------------------
/test_requirements.txt:
--------------------------------------------------------------------------------
1 | exam>=0.10.2,<0.11.0
2 | flake8>=2.4.0,<2.5.0
3 | loremipsum==1.0.2
4 | mercurial>=2.4
5 | mock>=1.0.1,<1.1.0
6 | pytest>=2.5.0,<2.6.0
7 | pytest-cov>=1.6,<1.7
8 | pytest-timeout>=0.3,<0.4
9 | pytest-xdist>=1.9,<1.10
10 | responses>=0.5.0,<0.6.0
11 | unittest2>=0.5.1,<0.6.0
12 | moto>=0.3.0,<0.4.0
13 |
--------------------------------------------------------------------------------
/changes/buildfailures/malformedartifact.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MalformedArtifact(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('An artifact could not be parsed.')
11 |
--------------------------------------------------------------------------------
/docker/supervisor-run:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
3 | DAEMON=/usr/bin/supervisord
4 | if [ -f /etc/default/supervisor ] ; then
5 | . /etc/default/supervisor
6 | fi
7 | DAEMON_OPTS="-c /etc/supervisor/supervisord.conf $DAEMON_OPTS"
8 |
9 | exec "$DAEMON" $DAEMON_OPTS --nodaemon
10 |
--------------------------------------------------------------------------------
/tests/changes/api/test_client.py:
--------------------------------------------------------------------------------
1 | from changes.api.client import api_client
2 | from changes.testutils import TestCase
3 |
4 |
5 | class APIClientTest(TestCase):
6 | def test_simple(self):
7 | # HACK: relies on existing endpoint
8 | result = api_client.get('/projects/')
9 | assert type(result) == list
10 |
--------------------------------------------------------------------------------
/changes/buildfailures/duplicatetestname.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class DuplicateTestName(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('A duplicate test name has been found.')
11 |
--------------------------------------------------------------------------------
/static/vendor/underscore/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "underscore",
3 | "version": "1.8.3",
4 | "main": "underscore.js",
5 | "keywords": ["util", "functional", "server", "client", "browser"],
6 | "ignore" : ["docs", "test", "*.yml", "CNAME", "index.html", "favicon.ico", "CONTRIBUTING.md", ".*", "component.json", "package.json", "karma.*"]
7 | }
8 |
--------------------------------------------------------------------------------
/changes/api/cluster_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView
4 | from changes.models.node import Cluster
5 |
6 |
7 | class ClusterIndexAPIView(APIView):
8 | def get(self):
9 | queryset = Cluster.query.order_by(Cluster.label.asc())
10 |
11 | return self.paginate(queryset)
12 |
--------------------------------------------------------------------------------
/changes/buildfailures/missingmanifestjson.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MissingManifestJson(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('Infrastructure failure (missing manifest file)')
11 |
--------------------------------------------------------------------------------
/changes/buildfailures/missingtests.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MissingTests(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('Tests were expected for all results, but some or all were missing.')
11 |
--------------------------------------------------------------------------------
/changes/db/funcs/coalesce.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.compiler import compiles
2 | from sqlalchemy.sql.expression import FunctionElement
3 |
4 |
5 | class coalesce(FunctionElement):
6 | name = 'coalesce'
7 |
8 |
9 | @compiles(coalesce, 'postgresql')
10 | def compile(element, compiler, **kw):
11 | return "coalesce(%s)" % compiler.process(element.clauses)
12 |
--------------------------------------------------------------------------------
/ci/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -ex
2 |
3 | source ~/env/bin/activate
4 | # webapp/entry.js must be ES5-compatible; we don't currently preprocess it.
5 | node_modules/.bin/eslint --no-eslintrc --parser-options=ecmaVersion:5 -f junit webapp/entry.js > entry_eslint.junit.xml
6 | node_modules/.bin/eslint --plugin react --quiet -f junit webapp/ > webapp_eslint.junit.xml
7 | make test-full
8 |
--------------------------------------------------------------------------------
/docs/integrations/phabricator.rst:
--------------------------------------------------------------------------------
1 | Phabricator Integration
2 | =======================
3 |
4 | While Changes provides no direct integration with Phabricator, a build step is available as an external library:
5 |
6 | https://github.com/dropbox/phabricator-changes
7 |
8 | The build step allows you to wire up your Phabricator install to submit commits and diffs to Changes as builds.
9 |
--------------------------------------------------------------------------------
/changes/buildfailures/missingtargets.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MissingTargets(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('Some Bazel targets did not produce a test.xml artifact, most likely due to a build failure.')
11 |
--------------------------------------------------------------------------------
/changes/buildfailures/malformedmanifestjson.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MalformedManifestJson(BuildFailure):
9 | def get_html_label(self, build):
10 | return Markup('Infrastructure failure (manifest.json file was malformed or had incorrect JobStep id)')
11 |
--------------------------------------------------------------------------------
/changes/api/cached_snapshot_cluster_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView
4 | import changes.lib.snapshot_garbage_collection as gc
5 |
6 |
7 | class CachedSnapshotClusterDetailsAPIView(APIView):
8 | def get(self, cluster):
9 | images = gc.get_cached_snapshot_images(cluster)
10 | return self.respond([image.id for image in images])
11 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/latest_green_build.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.latest_green_build import LatestGreenBuild
3 |
4 |
5 | @register(LatestGreenBuild)
6 | class LatestGreenBuildCrumbler(Crumbler):
7 | def crumble(self, item, attrs):
8 | return {
9 | 'branch': item.branch,
10 | 'build': item.build,
11 | }
12 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/node.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.node import Node
3 |
4 |
5 | @register(Node)
6 | class NodeCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'name': instance.label,
11 | 'dateCreated': instance.date_created,
12 | }
13 |
--------------------------------------------------------------------------------
/static/vendor/requirejs/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "requirejs",
3 | "version": "2.1.22",
4 | "ignore": [],
5 | "homepage": "http://requirejs.org",
6 | "authors": [
7 | "jrburke.com"
8 | ],
9 | "description": "A file and module loader for JavaScript",
10 | "main": "require.js",
11 | "keywords": [
12 | "AMD"
13 | ],
14 | "license": [
15 | "BSD-3-Clause",
16 | "MIT"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/cluster.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.node import Cluster
3 |
4 |
5 | @register(Cluster)
6 | class ClusterCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'name': instance.label,
11 | 'dateCreated': instance.date_created,
12 | }
13 |
--------------------------------------------------------------------------------
/changes/utils/times.py:
--------------------------------------------------------------------------------
1 | def duration(value):
2 | ONE_SECOND = 1000
3 | ONE_MINUTE = ONE_SECOND * 60
4 |
5 | if not value:
6 | return '0 s'
7 |
8 | abs_value = abs(value)
9 |
10 | if abs_value < 3 * ONE_SECOND:
11 | return '%d ms' % (value,)
12 | elif abs_value < 5 * ONE_MINUTE:
13 | return '%d s' % (value / ONE_SECOND,)
14 | else:
15 | return '%d m' % (value / ONE_MINUTE,)
16 |
--------------------------------------------------------------------------------
/tests/changes/api/test_node_details.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class NodeDetailsTest(APITestCase):
5 | def test_simple(self):
6 | node = self.create_node()
7 | path = '/api/0/nodes/{0}/'.format(node.id.hex)
8 |
9 | resp = self.client.get(path)
10 | assert resp.status_code == 200
11 | data = self.unserialize(resp)
12 | assert data['id'] == node.id.hex
13 |
--------------------------------------------------------------------------------
/changes/api/change_details.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import joinedload
2 |
3 | from changes.api.base import APIView
4 | from changes.models.change import Change
5 |
6 |
7 | class ChangeDetailsAPIView(APIView):
8 | def get(self, change_id):
9 | change = Change.query.options(
10 | joinedload(Change.project),
11 | joinedload(Change.author),
12 | ).get(change_id)
13 |
14 | return self.respond(change)
15 |
--------------------------------------------------------------------------------
/changes/utils/dirs.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, division
2 |
3 | import os
4 |
5 | from changes.constants import PROJECT_ROOT
6 |
7 |
8 | def enforce_is_subdir(path, root=PROJECT_ROOT):
9 | """
10 | Ensure that a particular path is within a subdirectory of changes.
11 | Prevents directory traversal attacks
12 | """
13 | if not os.path.abspath(path).startswith(root):
14 | raise RuntimeError('this path is not safe!')
15 |
--------------------------------------------------------------------------------
/changes/api/validators/basic.py:
--------------------------------------------------------------------------------
1 | from typing import Callable # NOQA
2 |
3 |
4 | def bounded_integer(lower, upper):
5 | """Accepts an integer in [lower, upper]."""
6 | # type: (int, int) -> Callable[str, int]
7 | def parse(s):
8 | # type: (str) -> int
9 | iv = int(s)
10 | if iv < lower or iv > upper:
11 | raise ValueError("{} is not in [{}, {}]".format(iv, lower, upper))
12 | return iv
13 | return parse
14 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "changes",
3 | "version": "0.1.0",
4 | "ignore": [
5 | "**/.*",
6 | "node_modules",
7 | "static/vendor",
8 | "tests"
9 | ],
10 | "devDependencies": {
11 | "classnames": "~2.2.0",
12 | "moment": "~2.1.0",
13 | "react": "~15.0",
14 | "react-bootstrap": "~0.29",
15 | "requirejs": "~2.1.11",
16 | "requirejs-babel": "0.0.6",
17 | "underscore": "~1.8.3",
18 | "uri.js": "~1.15.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/changes/buildfailures/missingartifact.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 |
7 |
8 | class MissingArtifact(BuildFailure):
9 | def get_html_label(self, build):
10 | # TODO(dcramer): we need arbitrary data with build failures so this can
11 | # say *what* artifact
12 | return Markup('A critical artifact was expected, but was not collected.')
13 |
--------------------------------------------------------------------------------
/tests/changes/api/test_cluster_details.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ClusterIndexTest(APITestCase):
5 | def test_simple(self):
6 | cluster_1 = self.create_cluster(label='bar')
7 | path = '/api/0/clusters/{0}/'.format(cluster_1.id.hex)
8 |
9 | resp = self.client.get(path)
10 | assert resp.status_code == 200
11 | data = self.unserialize(resp)
12 | assert data['id'] == cluster_1.id.hex
13 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/comment.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.comment import Comment
3 |
4 |
5 | @register(Comment)
6 | class CommentCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'user': instance.user,
11 | 'text': instance.text,
12 | 'dateCreated': instance.date_created.isoformat(),
13 | }
14 |
--------------------------------------------------------------------------------
/static/vendor/requirejs-babel/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "requirejs-babel",
3 | "version": "0.0.6",
4 | "authors": [
5 | "Michael "
6 | ],
7 | "description": "An AMD loader plugin for Babel",
8 | "main": "es6.js",
9 | "moduleType": [
10 | "amd"
11 | ],
12 | "keywords": [
13 | "es6",
14 | "esnext",
15 | "babel"
16 | ],
17 | "license": "MIT",
18 | "ignore": [
19 | "demo",
20 | "package.json"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/migrations/versions/4134b4818694_remove_index_build_p.py:
--------------------------------------------------------------------------------
1 | """Remove index Build.project_id
2 |
3 | Revision ID: 4134b4818694
4 | Revises: f2c8d15416b
5 | Create Date: 2013-12-03 16:18:03.550867
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4134b4818694'
11 | down_revision = 'f2c8d15416b'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_index('idx_build_project_id', 'build')
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/static/vendor/moment/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moment",
3 | "version": "2.1.0",
4 | "scripts": ["moment.js"],
5 | "main": "moment.js",
6 | "description": "Parse, validate, manipulate, and display dates in javascript.",
7 | "ignore": [
8 | ".gitignore",
9 | ".travis.yml",
10 | "composer.json",
11 | "CONTRIBUTING.md",
12 | "ender.js",
13 | "Gruntfile.js",
14 | "package.js",
15 | "package.json",
16 | "test",
17 | "tasks"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/static/vendor/moment/lang/readme.md:
--------------------------------------------------------------------------------
1 | # Using language files in the browser
2 |
3 | The files in this directory are optimized for use with Node.js, hence the `require('../moment')` calls at the beginning of each file.
4 |
5 | If you are looking to use these files in the browser, the minified version of these files in `/min/lang/` are a better fit. We do some trickery to make each language file work with or without requirejs. If you are interested in the details, check out `/tasks/minify-lang.js`.
6 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/adminmessage.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.adminmessage import AdminMessage
3 |
4 |
5 | @register(AdminMessage)
6 | class AdminMessageCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'user': instance.user,
11 | 'message': instance.message,
12 | 'dateCreated': instance.date_created,
13 | }
14 |
--------------------------------------------------------------------------------
/migrations/versions/fe743605e1a_remove_repositoryoption.py:
--------------------------------------------------------------------------------
1 | """Remove RepositoryOption
2 |
3 | Revision ID: fe743605e1a
4 | Revises: 10403d977f5f
5 | Create Date: 2014-09-17 15:17:09.925681
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'fe743605e1a'
11 | down_revision = '10403d977f5f'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_table('repositoryoption')
18 |
19 |
20 | def downgrade():
21 | raise NotImplementedError
22 |
--------------------------------------------------------------------------------
/changes/backends/base.py:
--------------------------------------------------------------------------------
1 | class UnrecoverableException(Exception):
2 | pass
3 |
4 |
5 | class BaseBackend(object):
6 | def __init__(self, app):
7 | self.app = app
8 |
9 | def create_job(self, job):
10 | raise NotImplementedError
11 |
12 | def sync_job(self, job):
13 | raise NotImplementedError
14 |
15 | def sync_step(self, step):
16 | raise NotImplementedError
17 |
18 | def cancel_step(self, step):
19 | raise NotImplementedError
20 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/buildmessage.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.buildmessage import BuildMessage
3 |
4 |
5 | @register(BuildMessage)
6 | class BuildMessageCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'build': {'id': instance.build_id.hex},
11 | 'text': instance.text,
12 | 'dateCreated': instance.date_created,
13 | }
14 |
--------------------------------------------------------------------------------
/tests/changes/api/test_change_details.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ChangeDetailsTest(APITestCase):
5 | def test_simple(self):
6 | project = self.create_project()
7 | change = self.create_change(project)
8 |
9 | path = '/api/0/changes/{0}/'.format(change.id.hex)
10 |
11 | resp = self.client.get(path)
12 | assert resp.status_code == 200
13 | data = self.unserialize(resp)
14 | assert data['id'] == change.id.hex
15 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/testartifact.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.testartifact import TestArtifact
3 |
4 |
5 | @register(TestArtifact)
6 | class TestArtifactCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'name': instance.name,
11 | 'url': instance.file.url_for() if instance.file else None,
12 | 'type': instance.type
13 | }
14 |
--------------------------------------------------------------------------------
/templates/debug/email.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Subject: {{ subject }}
7 | Recipients: {{ recipients }}
8 |
9 | Html email:
10 |
11 | {{ html_content }}
12 |
13 |
14 | Text email:
15 |
16 |
{{ text_content }}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/migrations/versions/5aad6f742b77_parent_revision_sha_.py:
--------------------------------------------------------------------------------
1 | """parent_revision_sha => revision_sha
2 |
3 | Revision ID: 5aad6f742b77
4 | Revises: 2d82db02b3ef
5 | Create Date: 2013-11-11 17:05:31.671178
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5aad6f742b77'
11 | down_revision = '2d82db02b3ef'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute("update build set revision_sha = parent_revision_sha")
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/migrations/versions/4f955feb6d07_rename_build_job.py:
--------------------------------------------------------------------------------
1 | """Rename Build -> Job
2 |
3 | Revision ID: 4f955feb6d07
4 | Revises: 554f414d4c46
5 | Create Date: 2013-12-25 23:17:26.666301
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4f955feb6d07'
11 | down_revision = '554f414d4c46'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE build RENAME TO job')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE job RENAME TO build')
22 |
--------------------------------------------------------------------------------
/changes/api/node_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView
4 | from changes.models.node import Node
5 |
6 |
7 | class NodeDetailsAPIView(APIView):
8 | def get(self, node_id):
9 | node = Node.query.get(node_id)
10 | if node is None:
11 | return '', 404
12 |
13 | context = self.serialize(node)
14 | context['clusters'] = self.serialize(list(node.clusters))
15 |
16 | return self.respond(context, serialize=False)
17 |
--------------------------------------------------------------------------------
/changes/url_converters/uuid.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from uuid import UUID
4 | from werkzeug.routing import BaseConverter, ValidationError
5 |
6 |
7 | class UUIDConverter(BaseConverter):
8 | """
9 | UUID converter for the Werkzeug routing system.
10 | """
11 | def to_python(self, value):
12 | try:
13 | return UUID(value)
14 | except ValueError:
15 | raise ValidationError
16 |
17 | def to_url(self, value):
18 | return str(value)
19 |
--------------------------------------------------------------------------------
/migrations/versions/f2c8d15416b_index_build_project_.py:
--------------------------------------------------------------------------------
1 | """Index Build.project_id,date_created
2 |
3 | Revision ID: f2c8d15416b
4 | Revises: 152c9c780e
5 | Create Date: 2013-12-03 15:54:09.488066
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'f2c8d15416b'
11 | down_revision = '152c9c780e'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_build_project_date', 'build', ['project_id', 'date_created'])
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/migrations/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision}
5 | Create Date: ${create_date}
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = ${repr(up_revision)}
11 | down_revision = ${repr(down_revision)}
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | ${imports if imports else ""}
16 |
17 | def upgrade():
18 | ${upgrades if upgrades else "pass"}
19 |
20 |
21 | def downgrade():
22 | ${downgrades if downgrades else "pass"}
23 |
--------------------------------------------------------------------------------
/migrations/versions/105d4dd82a0a_date_indexes_on_test.py:
--------------------------------------------------------------------------------
1 | """Date indexes on TestGroup
2 |
3 | Revision ID: 105d4dd82a0a
4 | Revises: 2d9df8a3103c
5 | Create Date: 2013-11-19 12:24:02.607191
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '105d4dd82a0a'
11 | down_revision = '2d9df8a3103c'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_testgroup_project_date', 'testgroup', ['project_id', 'date_created'])
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/changes/api/build_coverage.py:
--------------------------------------------------------------------------------
1 | from changes.api.base import APIView
2 |
3 | from changes.lib.coverage import get_coverage_by_build_id, merged_coverage_data
4 |
5 | from changes.models.build import Build
6 |
7 |
8 | class BuildTestCoverageAPIView(APIView):
9 | def get(self, build_id):
10 | build = Build.query.get(build_id)
11 | if build is None:
12 | return '', 404
13 |
14 | coverage = merged_coverage_data(get_coverage_by_build_id(build.id))
15 |
16 | return self.respond(coverage)
17 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/bazeltargetmessage.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.bazeltargetmessage import BazelTargetMessage
3 |
4 |
5 | @register(BazelTargetMessage)
6 | class BazelTargetMessageCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'target': {'id': instance.target_id.hex},
11 | 'text': instance.text,
12 | 'dateCreated': instance.date_created,
13 | }
14 |
--------------------------------------------------------------------------------
/migrations/versions/2622a69cd25a_unique_node_label.py:
--------------------------------------------------------------------------------
1 | """Unique Node.label
2 |
3 | Revision ID: 2622a69cd25a
4 | Revises: 2c7cbd9b7e54
5 | Create Date: 2013-12-18 12:42:30.583085
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2622a69cd25a'
11 | down_revision = '2c7cbd9b7e54'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_node_label', 'node', ['label'])
18 |
19 |
20 | def downgrade():
21 | op.drop_constraint('unq_node_label', 'node')
22 |
--------------------------------------------------------------------------------
/migrations/versions/4901f27ade8e_fill_target.py:
--------------------------------------------------------------------------------
1 | """Fill target
2 |
3 | Revision ID: 4901f27ade8e
4 | Revises: 52bcea82b482
5 | Create Date: 2013-11-11 17:25:38.930307
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4901f27ade8e'
11 | down_revision = '52bcea82b482'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute("update build set target = substr(revision_sha, 0, 12) where target is null and revision_sha is not null")
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/static/vendor/react-bootstrap/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-bootstrap",
3 | "version": "0.29.3",
4 | "homepage": "http://react-bootstrap.github.io/",
5 | "author": "Stephen J. Collings ",
6 | "license": "MIT",
7 | "main": [
8 | "react-bootstrap.js"
9 | ],
10 | "keywords": [
11 | "react",
12 | "ecosystem-react",
13 | "react-component",
14 | "bootstrap"
15 | ],
16 | "ignore": [
17 | "**/.*"
18 | ],
19 | "dependencies": {
20 | "react": ">=0.14.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/static/vendor/react/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "main": [
4 | "react.js",
5 | "react-dom.js"
6 | ],
7 | "ignore": [],
8 | "homepage": "https://github.com/facebook/react-bower",
9 | "version": "15.0.2",
10 | "_release": "15.0.2",
11 | "_resolution": {
12 | "type": "version",
13 | "tag": "v15.0.2",
14 | "commit": "624b4f21e8bf11b7a102cd0855553d1a4279da3f"
15 | },
16 | "_source": "https://github.com/facebook/react-bower.git",
17 | "_target": "~15.0",
18 | "_originalSource": "react"
19 | }
--------------------------------------------------------------------------------
/docs/examples/changes.conf.py:
--------------------------------------------------------------------------------
1 | # ~/.changes/changes.conf.py
2 | WEB_BASE_URI = 'http://localhost:5000'
3 | INTERNAL_BASE_URI = 'http://localhost:5000'
4 | SERVER_NAME = 'localhost:5000'
5 |
6 | REPO_ROOT = '/tmp'
7 |
8 | # You can obtain these values via the Google Developers Console:
9 | # https://console.developers.google.com/
10 | # Example 'Authorized JavaScript Origins': http://localhost:5000
11 | # Example 'Authorized Redirect URIs': http://localhost:5000/auth/complete/
12 | GOOGLE_CLIENT_ID = None
13 | GOOGLE_CLIENT_SECRET = None
14 |
--------------------------------------------------------------------------------
/migrations/versions/5508859bed73_index_source_patch_i.py:
--------------------------------------------------------------------------------
1 | """Index Source.patch_id
2 |
3 | Revision ID: 5508859bed73
4 | Revises: 26f665189ca0
5 | Create Date: 2014-01-06 11:14:19.109932
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5508859bed73'
11 | down_revision = '26f665189ca0'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_source_patch_id', 'source', ['patch_id'])
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_source_patch_id', 'source')
22 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/artifact.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.artifact import Artifact
3 |
4 |
5 | @register(Artifact)
6 | class ArtifactCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'jobstep': instance.step,
11 | 'name': instance.name,
12 | 'url': instance.file.url_for() if instance.file else None,
13 | 'dateCreated': instance.date_created,
14 | }
15 |
--------------------------------------------------------------------------------
/migrations/versions/34157be0e8a2_index_patch_project_.py:
--------------------------------------------------------------------------------
1 | """Index Patch.project_id
2 |
3 | Revision ID: 34157be0e8a2
4 | Revises: f8ed1f99eb1
5 | Create Date: 2014-01-02 21:59:37.076886
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '34157be0e8a2'
11 | down_revision = 'f8ed1f99eb1'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_patch_project_id', 'patch', ['project_id'])
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_patch_project_id', 'patch')
22 |
--------------------------------------------------------------------------------
/migrations/versions/42e9d35a4098_rename_buildstep_job.py:
--------------------------------------------------------------------------------
1 | """Rename BuildStep -> JobStep
2 |
3 | Revision ID: 42e9d35a4098
4 | Revises: 516f04a1a754
5 | Create Date: 2013-12-25 23:44:30.776610
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '42e9d35a4098'
11 | down_revision = '516f04a1a754'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE buildstep RENAME TO jobstep')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobstep RENAME TO buildstep')
22 |
--------------------------------------------------------------------------------
/migrations/versions/545ba163595_rename_buildfamily_b.py:
--------------------------------------------------------------------------------
1 | """Rename BuildFamily => Build
2 |
3 | Revision ID: 545ba163595
4 | Revises: 256ab638deaa
5 | Create Date: 2013-12-26 01:51:58.807080
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '545ba163595'
11 | down_revision = '256ab638deaa'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE buildfamily RENAME TO build')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE build RENAME TO buildfamily')
22 |
--------------------------------------------------------------------------------
/migrations/versions/6483270c001_rename_buildplan_job.py:
--------------------------------------------------------------------------------
1 | """Rename BuildPlan -> JobPlan
2 |
3 | Revision ID: 6483270c001
4 | Revises: 4f955feb6d07
5 | Create Date: 2013-12-25 23:37:07.896471
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '6483270c001'
11 | down_revision = '4f955feb6d07'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE buildplan RENAME TO jobplan')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobplan RENAME TO buildplan')
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .arcconfig
2 | .arc_lib
3 | .coverage
4 | .DS_Store
5 | .idea
6 | *.iml
7 | *.db
8 | *.egg-info
9 | *.pyc
10 | *.swp
11 | *.swo
12 | npm-debug.log
13 | /env
14 | /*.xml
15 | /docs/_build/
16 | /htmlcov
17 | /dist
18 | /node_modules/babel-eslint/*
19 | /node_modules/eslint/*
20 | /node_modules/eslint-plugin-react/*
21 | /static/js/generated
22 | /static-built
23 | /webapp/custom/*
24 | !/webapp/custom/README
25 | /webapp/dist
26 | /webapp/css/bundled.css
27 | /webapp/css/bundled_with_custom.css
28 | webapp/.webassets-cache
29 | .vagrant
30 |
--------------------------------------------------------------------------------
/migrations/versions/516f04a1a754_rename_buildphase_jo.py:
--------------------------------------------------------------------------------
1 | """Rename BuildPhase -> JobPhase
2 |
3 | Revision ID: 516f04a1a754
4 | Revises: 6483270c001
5 | Create Date: 2013-12-25 23:40:42.892745
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '516f04a1a754'
11 | down_revision = '6483270c001'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE buildphase RENAME TO jobphase')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobphase RENAME TO buildphase')
22 |
--------------------------------------------------------------------------------
/migrations/versions/97786e74292_add_task_data.py:
--------------------------------------------------------------------------------
1 | """Add Task.data
2 |
3 | Revision ID: 97786e74292
4 | Revises: 4ae598236ef1
5 | Create Date: 2014-01-21 17:40:42.013002
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '97786e74292'
11 | down_revision = '4ae598236ef1'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('task', sa.Column('data', sa.JSONEncodedDict(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('task', 'data')
23 |
--------------------------------------------------------------------------------
/changes/api/patch_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from flask import request, Response
4 |
5 | from changes.api.base import APIView
6 | from changes.models.patch import Patch
7 |
8 |
9 | class PatchDetailsAPIView(APIView):
10 | def get(self, patch_id):
11 | patch = Patch.query.get(patch_id)
12 | if patch is None:
13 | return '', 404
14 |
15 | if request.args.get('raw'):
16 | return Response(patch.diff, mimetype='text/plain')
17 |
18 | return self.respond(patch)
19 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/revisionresult.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.serializer import Crumbler, register
4 | from changes.models.revisionresult import RevisionResult
5 |
6 |
7 | @register(RevisionResult)
8 | class RevisionResultCrumbler(Crumbler):
9 | def crumble(self, instance, attrs):
10 | return {
11 | 'id': instance.id.hex,
12 | 'revisionSha': instance.revision_sha,
13 | 'build': instance.build,
14 | 'result': instance.result,
15 | }
16 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/snapshotimage.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.snapshot import SnapshotImage
3 |
4 |
5 | @register(SnapshotImage)
6 | class SnapshotImageCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'status': instance.status,
11 | 'dateCreated': instance.date_created,
12 | 'plan': instance.plan,
13 | 'snapshot': {'id': instance.snapshot.id.hex},
14 | }
15 |
--------------------------------------------------------------------------------
/migrations/versions/4ae598236ef1_unused_indexes_on_jo.py:
--------------------------------------------------------------------------------
1 | """Unused indexes on Job
2 |
3 | Revision ID: 4ae598236ef1
4 | Revises: 4ffb7e1df217
5 | Create Date: 2014-01-21 14:19:27.134472
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4ae598236ef1'
11 | down_revision = '4ffb7e1df217'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_index('idx_build_source_id', 'job')
18 | op.drop_index('idx_build_family_id', 'job')
19 |
20 |
21 | def downgrade():
22 | raise NotImplementedError
23 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/logchunk.py:
--------------------------------------------------------------------------------
1 |
2 | from changes.api.serializer import Crumbler, register
3 | from changes.models.log import LogChunk
4 |
5 |
6 | @register(LogChunk)
7 | class LogChunkCrumbler(Crumbler):
8 | def crumble(self, instance, attrs):
9 | return {
10 | 'id': instance.id.hex,
11 | 'source': {
12 | 'id': instance.source.id.hex,
13 | },
14 | 'text': instance.text,
15 | 'offset': instance.offset,
16 | 'size': instance.size,
17 | }
18 |
--------------------------------------------------------------------------------
/migrations/versions/181adec926e2_add_status_index_to_task.py:
--------------------------------------------------------------------------------
1 | """Add status index to task
2 |
3 | Revision ID: 181adec926e2
4 | Revises: 43397e521791
5 | Create Date: 2016-10-03 17:41:44.038137
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '181adec926e2'
11 | down_revision = '43397e521791'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_task_status', 'task', ['status'], unique=False)
18 |
19 |
20 | def downgrade():
21 | op.drop_index('id_task_status', table_name='task')
22 |
--------------------------------------------------------------------------------
/migrations/versions/21b7c3b2ce88_index_build_project_.py:
--------------------------------------------------------------------------------
1 | """Index Build.project_id,patch_id,date_created
2 |
3 | Revision ID: 21b7c3b2ce88
4 | Revises: 4134b4818694
5 | Create Date: 2013-12-03 16:19:11.794912
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '21b7c3b2ce88'
11 | down_revision = '4134b4818694'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_build_project_patch_date', 'build', ['project_id', 'patch_id', 'date_created'])
18 |
19 |
20 | def downgrade():
21 | pass
22 |
--------------------------------------------------------------------------------
/migrations/versions/36d7c98ddfee_drop_projectplan_table.py:
--------------------------------------------------------------------------------
1 | """Drop ProjectPlan table
2 |
3 | Revision ID: 36d7c98ddfee
4 | Revises: 12569fada93
5 | Create Date: 2014-10-14 11:25:48.151275
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '36d7c98ddfee'
11 | down_revision = '12569fada93'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | ### commands auto generated by Alembic - please adjust! ###
18 | op.drop_table('project_plan')
19 |
20 |
21 | def downgrade():
22 | raise NotImplementedError
23 |
--------------------------------------------------------------------------------
/changes/storage/base.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 |
4 | class FileStorage(object):
5 | def __init__(self, path=''):
6 | self.path = path
7 |
8 | def save(self, filename, fp, content_type=None, path=None):
9 | raise NotImplementedError
10 |
11 | def url_for(self, filename, expire=300):
12 | raise NotImplementedError
13 |
14 | def get_file(self, filename, offset=None, length=None):
15 | raise NotImplementedError
16 |
17 | def get_size(self, filename):
18 | raise NotImplementedError
19 |
--------------------------------------------------------------------------------
/migrations/versions/21a9d1ebe15c_add_jobplan_data.py:
--------------------------------------------------------------------------------
1 | """Add JobPlan.data
2 |
3 | Revision ID: 21a9d1ebe15c
4 | Revises: 19b8969073ab
5 | Create Date: 2014-07-15 17:30:50.899914
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '21a9d1ebe15c'
11 | down_revision = '19b8969073ab'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobplan', sa.Column('data', sa.JSONEncodedDict(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('jobplan', 'data')
23 |
--------------------------------------------------------------------------------
/migrations/versions/26c0affcb18a_add_jobstep_data.py:
--------------------------------------------------------------------------------
1 | """Add JobStep.data
2 |
3 | Revision ID: 26c0affcb18a
4 | Revises: 4114cbbd0573
5 | Create Date: 2014-01-07 16:44:48.524027
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '26c0affcb18a'
11 | down_revision = '4114cbbd0573'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobstep', sa.Column('data', sa.JSONEncodedDict(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('jobstep', 'data')
23 |
--------------------------------------------------------------------------------
/migrations/versions/315e787e94bf_add_source_data.py:
--------------------------------------------------------------------------------
1 | """Add Source.data
2 |
3 | Revision ID: 315e787e94bf
4 | Revises: 3f289637f530
5 | Create Date: 2014-05-06 18:25:11.223951
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '315e787e94bf'
11 | down_revision = '3f289637f530'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('source', sa.Column('data', sa.JSONEncodedDict(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('source', 'data')
23 |
--------------------------------------------------------------------------------
/migrations/versions/4ba1dd8c3080_add_index_for_projec.py:
--------------------------------------------------------------------------------
1 | """Add index for project test list
2 |
3 | Revision ID: 4ba1dd8c3080
4 | Revises: 2c380be0a31e
5 | Create Date: 2014-04-14 11:08:05.598439
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4ba1dd8c3080'
11 | down_revision = '2c380be0a31e'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_test_project_key', 'test', ['project_id', 'label_sha'])
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_test_project_key', 'test')
22 |
--------------------------------------------------------------------------------
/migrations/versions/4d235d421320_add_artifact_file.py:
--------------------------------------------------------------------------------
1 | """Add Artifact.file
2 |
3 | Revision ID: 4d235d421320
4 | Revises: 19910015c867
5 | Create Date: 2014-07-14 17:24:54.764351
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4d235d421320'
11 | down_revision = '19910015c867'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('artifact', sa.Column('file', sa.FileStorage(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('artifact', 'file')
23 |
--------------------------------------------------------------------------------
/migrations/versions/bb141e41aab_add_rerun_count_to_t.py:
--------------------------------------------------------------------------------
1 | """add rerun count to tests
2 |
3 | Revision ID: bb141e41aab
4 | Revises: f8f72eecc7f
5 | Create Date: 2014-03-28 13:57:17.364930
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'bb141e41aab'
11 | down_revision = 'f8f72eecc7f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column(u'test', sa.Column('reruns', sa.INTEGER(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column(u'test', 'reruns')
23 |
--------------------------------------------------------------------------------
/migrations/versions/f8f72eecc7f_remove_idx_testgroup.py:
--------------------------------------------------------------------------------
1 | """Remove idx_testgroup_project_id
2 |
3 | Revision ID: f8f72eecc7f
4 | Revises: 1db7a1ab95db
5 | Create Date: 2014-03-27 12:12:15.548413
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'f8f72eecc7f'
11 | down_revision = '1db7a1ab95db'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_index('idx_testgroup_project_id', 'testgroup')
18 |
19 |
20 | def downgrade():
21 | op.create_index('idx_testgroup_project_id', 'testgroup', ['project_id'])
22 |
--------------------------------------------------------------------------------
/tests/changes/api/test_node_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class NodeIndexTest(APITestCase):
5 | def test_simple(self):
6 | node_1 = self.create_node(label='bar')
7 | node_2 = self.create_node(label='foo')
8 | path = '/api/0/nodes/'
9 |
10 | resp = self.client.get(path)
11 | assert resp.status_code == 200
12 | data = self.unserialize(resp)
13 | assert len(data) == 2
14 | assert data[0]['id'] == node_1.id.hex
15 | assert data[1]['id'] == node_2.id.hex
16 |
--------------------------------------------------------------------------------
/migrations/versions/26f665189ca0_remove_jobstep_jobph.py:
--------------------------------------------------------------------------------
1 | """Remove JobStep/JobPhase repository_id
2 |
3 | Revision ID: 26f665189ca0
4 | Revises: 524b3c27203b
5 | Create Date: 2014-01-05 22:01:02.648719
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '26f665189ca0'
11 | down_revision = '524b3c27203b'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_column('jobstep', 'repository_id')
18 | op.drop_column('jobphase', 'repository_id')
19 |
20 |
21 | def downgrade():
22 | raise NotImplementedError
23 |
--------------------------------------------------------------------------------
/migrations/versions/2b4219a6cf46_jobphase_build_id_jo.py:
--------------------------------------------------------------------------------
1 | """JobPhase.build_id => job_id
2 |
3 | Revision ID: 2b4219a6cf46
4 | Revises: f26b6cb3c9c
5 | Create Date: 2013-12-26 00:16:20.974336
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2b4219a6cf46'
11 | down_revision = 'f26b6cb3c9c'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE jobphase RENAME COLUMN build_id TO job_id')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobphase RENAME COLUMN job_id TO build_id')
22 |
--------------------------------------------------------------------------------
/migrations/versions/35af40cebcde_jobplan_build_id_job.py:
--------------------------------------------------------------------------------
1 | """JobPlan.build_id => job_id
2 |
3 | Revision ID: 35af40cebcde
4 | Revises: 2b4219a6cf46
5 | Create Date: 2013-12-26 00:18:58.945167
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '35af40cebcde'
11 | down_revision = '2b4219a6cf46'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE jobplan RENAME COLUMN build_id TO job_id')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobplan RENAME COLUMN job_id TO build_id')
22 |
--------------------------------------------------------------------------------
/migrations/versions/f26b6cb3c9c_jobstep_build_id_job.py:
--------------------------------------------------------------------------------
1 | """JobStep.build_id => job_id
2 |
3 | Revision ID: f26b6cb3c9c
4 | Revises: 5026dbcee21d
5 | Create Date: 2013-12-26 00:11:38.414155
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'f26b6cb3c9c'
11 | down_revision = '5026dbcee21d'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE jobstep RENAME COLUMN build_id TO job_id')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE jobstep RENAME COLUMN job_id TO build_id')
22 |
--------------------------------------------------------------------------------
/migrations/versions/10403d977f5f_add_plan_status.py:
--------------------------------------------------------------------------------
1 | """Add Plan.status
2 |
3 | Revision ID: 10403d977f5f
4 | Revises: 57e4a0553903
5 | Create Date: 2014-08-27 11:56:07.902348
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '10403d977f5f'
11 | down_revision = '57e4a0553903'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('plan', sa.Column('status', sa.Enum(), server_default='1', nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('plan', 'status')
23 |
--------------------------------------------------------------------------------
/migrations/versions/17d61d9621cb_add_command_type.py:
--------------------------------------------------------------------------------
1 | """Add Command.type
2 |
3 | Revision ID: 17d61d9621cb
4 | Revises: 46a612aff7d3
5 | Create Date: 2014-08-18 13:25:39.609575
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '17d61d9621cb'
11 | down_revision = '46a612aff7d3'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('command', sa.Column('type', sa.Enum(), server_default='0', nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('command', 'type')
23 |
--------------------------------------------------------------------------------
/migrations/versions/4114cbbd0573_index_job_status_dat.py:
--------------------------------------------------------------------------------
1 | """Index Job.{status,date_created}
2 |
3 | Revision ID: 4114cbbd0573
4 | Revises: 5508859bed73
5 | Create Date: 2014-01-06 11:28:15.691391
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4114cbbd0573'
11 | down_revision = '5508859bed73'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_job_status_date_created', 'job', ['status', 'date_created'])
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_job_status_date_created', 'job')
22 |
--------------------------------------------------------------------------------
/migrations/versions/4ffb7e1df217_unique_jobphase_job_.py:
--------------------------------------------------------------------------------
1 | """Unique JobPhase.{job_id,label}
2 |
3 | Revision ID: 4ffb7e1df217
4 | Revises: 545e104c5f5a
5 | Create Date: 2014-01-21 11:12:10.408310
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4ffb7e1df217'
11 | down_revision = '545e104c5f5a'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_jobphase_key', 'jobphase', ['job_id', 'label'])
18 |
19 |
20 | def downgrade():
21 | op.drop_constraint('unq_jobphase_key', 'jobphase')
22 |
--------------------------------------------------------------------------------
/migrations/versions/51775a13339d_patch_hash_column.py:
--------------------------------------------------------------------------------
1 | """patch hash column
2 |
3 | Revision ID: 51775a13339d
4 | Revises: 187eade64ef0
5 | Create Date: 2016-06-17 13:46:10.921685
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '51775a13339d'
11 | down_revision = '187eade64ef0'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('revision', sa.Column('patch_hash', sa.String(40), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('revision', 'patch_hash')
23 |
--------------------------------------------------------------------------------
/migrations/versions/545e104c5f5a_add_logsource_step_i.py:
--------------------------------------------------------------------------------
1 | """Add LogSource.step_id
2 |
3 | Revision ID: 545e104c5f5a
4 | Revises: 5677ef75c712
5 | Create Date: 2014-01-15 17:18:53.402012
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '545e104c5f5a'
11 | down_revision = '5677ef75c712'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('logsource', sa.Column('step_id', sa.GUID(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('logsource', 'step_id')
23 |
--------------------------------------------------------------------------------
/migrations/versions/3a3366fb7822_index_testgroup_test.py:
--------------------------------------------------------------------------------
1 | """Index testgroup_test.test_id
2 |
3 | Revision ID: 3a3366fb7822
4 | Revises: 139e272152de
5 | Create Date: 2014-01-02 22:20:55.132222
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3a3366fb7822'
11 | down_revision = '139e272152de'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_testgroup_test_test_id', 'testgroup_test', ['test_id'])
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_testgroup_test_test_id', 'testgroup_test')
22 |
--------------------------------------------------------------------------------
/migrations/versions/3c93047c66c8_add_index_to_jobstep_status.py:
--------------------------------------------------------------------------------
1 | """Add index to JobStep.status
2 |
3 | Revision ID: 3c93047c66c8
4 | Revises: 4ab789ea6b6f
5 | Create Date: 2015-04-28 22:55:23.139295
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3c93047c66c8'
11 | down_revision = '4ab789ea6b6f'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_jobstep_status', 'jobstep', ['status'], unique=False)
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_jobstep_status', table_name='jobstep')
22 |
--------------------------------------------------------------------------------
/migrations/versions/46a612aff7d3_add_snapshot_source_id.py:
--------------------------------------------------------------------------------
1 | """Add Snapshot.source_id
2 |
3 | Revision ID: 46a612aff7d3
4 | Revises: 2da2dd72d21c
5 | Create Date: 2014-08-12 15:52:28.488096
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '46a612aff7d3'
11 | down_revision = '2da2dd72d21c'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('snapshot', sa.Column('source_id', sa.GUID(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('snapshot', 'source_id')
23 |
--------------------------------------------------------------------------------
/migrations/versions/19168fe64c41_unique_constraint_on_command.py:
--------------------------------------------------------------------------------
1 | """Unique constraint on Command
2 |
3 | Revision ID: 19168fe64c41
4 | Revises: 4768ba7627ac
5 | Create Date: 2014-07-17 14:42:59.309732
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '19168fe64c41'
11 | down_revision = '4768ba7627ac'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_command_order', 'command', ['jobstep_id', 'order'])
18 |
19 |
20 | def downgrade():
21 | op.drop_constraint('unq_command_order', 'command')
22 |
--------------------------------------------------------------------------------
/migrations/versions/256ab638deaa_filecoverage_build_i.py:
--------------------------------------------------------------------------------
1 | """FileCoverage.build_id => job_id
2 |
3 | Revision ID: 256ab638deaa
4 | Revises: 37244bf4e3f5
5 | Create Date: 2013-12-26 01:47:50.855439
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '256ab638deaa'
11 | down_revision = '37244bf4e3f5'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE filecoverage RENAME COLUMN build_id TO job_id')
18 |
19 |
20 | def downgrade():
21 | op.execute('ALTER TABLE filecoverage RENAME COLUMN job_id TO build_id')
22 |
--------------------------------------------------------------------------------
/migrations/versions/2596f21c6f58_add_project_status.py:
--------------------------------------------------------------------------------
1 | """Add Project.status
2 |
3 | Revision ID: 2596f21c6f58
4 | Revises: 4e68c2a3d269
5 | Create Date: 2014-02-18 17:34:59.432346
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2596f21c6f58'
11 | down_revision = '4e68c2a3d269'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('project', sa.Column('status', sa.Enum(), server_default='1', nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('project', 'status')
23 |
--------------------------------------------------------------------------------
/migrations/versions/36cbde703cc0_add_build_priority.py:
--------------------------------------------------------------------------------
1 | """Add Build.priority
2 |
3 | Revision ID: 36cbde703cc0
4 | Revises: fe743605e1a
5 | Create Date: 2014-10-06 10:10:14.729720
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '36cbde703cc0'
11 | down_revision = '2c6662281b66'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('build', sa.Column('priority', sa.Enum(), server_default='0', nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('build', 'priority')
23 |
--------------------------------------------------------------------------------
/migrations/versions/41c7ef24fd4c_add_build_date_decided.py:
--------------------------------------------------------------------------------
1 | """add Build.date_decided
2 |
3 | Revision ID: 41c7ef24fd4c
4 | Revises: 3961ccb5d884
5 | Create Date: 2015-12-16 13:27:24.838841
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '41c7ef24fd4c'
11 | down_revision = '3961ccb5d884'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('build', sa.Column('date_decided', sa.DateTime(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('build', 'date_decided')
23 |
--------------------------------------------------------------------------------
/migrations/versions/4c3f2a63b7a7_add_plan_avg_build_time.py:
--------------------------------------------------------------------------------
1 | """Add Plan.avg_build_time
2 |
3 | Revision ID: 4c3f2a63b7a7
4 | Revises: 4cd8cf6a0894
5 | Create Date: 2014-10-09 15:21:09.463279
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4c3f2a63b7a7'
11 | down_revision = '4cd8cf6a0894'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('plan', sa.Column('avg_build_time', sa.Integer(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('plan', 'avg_build_time')
23 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/author.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.api.serializer.models.user import get_gravatar_url
3 | from changes.models.author import Author
4 |
5 |
6 | @register(Author)
7 | class AuthorCrumbler(Crumbler):
8 | def crumble(self, instance, attrs):
9 | return {
10 | 'id': instance.id.hex,
11 | 'name': instance.name,
12 | 'email': instance.email,
13 | 'avatar': get_gravatar_url(instance.email),
14 | 'dateCreated': instance.date_created,
15 | }
16 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/event.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.event import Event
3 |
4 |
5 | @register(Event)
6 | class EventCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'type': instance.type,
11 | 'itemId': instance.item_id.hex,
12 | 'data': dict(instance.data),
13 | 'dateCreated': instance.date_created.isoformat(),
14 | 'dateModified': instance.date_modified.isoformat(),
15 | }
16 |
--------------------------------------------------------------------------------
/migrations/versions/3d8177efcfe1_add_filecoverage_step_id.py:
--------------------------------------------------------------------------------
1 | """Add FileCoverage.step_id
2 |
3 | Revision ID: 3d8177efcfe1
4 | Revises: 3df65ebfa27e
5 | Create Date: 2014-05-08 11:19:04.251803
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3d8177efcfe1'
11 | down_revision = '3df65ebfa27e'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('filecoverage', sa.Column('step_id', sa.GUID(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('filecoverage', 'step_id')
23 |
--------------------------------------------------------------------------------
/tests/changes/api/test_cluster_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ClusterIndexTest(APITestCase):
5 | def test_simple(self):
6 | cluster_1 = self.create_cluster(label='bar')
7 | cluster_2 = self.create_cluster(label='foo')
8 | path = '/api/0/clusters/'
9 |
10 | resp = self.client.get(path)
11 | assert resp.status_code == 200
12 | data = self.unserialize(resp)
13 | assert len(data) == 2
14 | assert data[0]['id'] == cluster_1.id.hex
15 | assert data[1]['id'] == cluster_2.id.hex
16 |
--------------------------------------------------------------------------------
/migrations/versions/2b7153fe25af_add_repository_status.py:
--------------------------------------------------------------------------------
1 | """Add Repository.status
2 |
3 | Revision ID: 2b7153fe25af
4 | Revises: 3b22c1663664
5 | Create Date: 2014-07-03 12:18:06.616440
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2b7153fe25af'
11 | down_revision = '3b22c1663664'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('repository', sa.Column('status', sa.Enum(), server_default='1', nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('repository', 'status')
23 |
--------------------------------------------------------------------------------
/migrations/versions/2d9df8a3103c_add_logchunk_unique_.py:
--------------------------------------------------------------------------------
1 | """Add LogChunk unique constraint
2 |
3 | Revision ID: 2d9df8a3103c
4 | Revises: 393be9b08e4c
5 | Create Date: 2013-11-13 17:12:48.662009
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2d9df8a3103c'
11 | down_revision = '393be9b08e4c'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_logchunk_source_offset', 'logchunk', ['source_id', 'offset'])
18 |
19 |
20 | def downgrade():
21 | op.drop_constraint('unq_logchunk_source_offset', 'logchunk')
22 |
--------------------------------------------------------------------------------
/migrations/versions/2da2dd72d21c_add_snapshot_date_created.py:
--------------------------------------------------------------------------------
1 | """Add Snapshot.date_created
2 |
3 | Revision ID: 2da2dd72d21c
4 | Revises: 2191c871434
5 | Create Date: 2014-08-08 14:29:38.740485
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2da2dd72d21c'
11 | down_revision = '2191c871434'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('snapshot', sa.Column('date_created', sa.DateTime(), nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('snapshot', 'date_created')
23 |
--------------------------------------------------------------------------------
/migrations/versions/3961ccb5d884_increase_artifact_name_length.py:
--------------------------------------------------------------------------------
1 | """increase artifact name length
2 |
3 | Revision ID: 3961ccb5d884
4 | Revises: 1b229c83511d
5 | Create Date: 2015-11-05 15:34:28.189700
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3961ccb5d884'
11 | down_revision = '1b229c83511d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.alter_column('artifact', 'name', type_=sa.VARCHAR(1024))
19 |
20 |
21 | def downgrade():
22 | op.alter_column('artifact', 'name', type_=sa.VARCHAR(128))
23 |
--------------------------------------------------------------------------------
/webapp/css/wiki_external_link.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/migrations/versions/016f138b2da8_add_job_autogenerated.py:
--------------------------------------------------------------------------------
1 | """add job autogenerated
2 |
3 | Revision ID: 016f138b2da8
4 | Revises: 58c2302ec362
5 | Create Date: 2016-06-09 17:32:41.079003
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '016f138b2da8'
11 | down_revision = '58c2302ec362'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('job', sa.Column('autogenerated', sa.Boolean(), nullable=False, server_default="0"))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('job', 'autogenerated')
23 |
--------------------------------------------------------------------------------
/migrations/versions/15bd4b7e6622_add_filecoverage_unique_constraint.py:
--------------------------------------------------------------------------------
1 | """Add FileCoverage unique constraint
2 |
3 | Revision ID: 15bd4b7e6622
4 | Revises: 3d8177efcfe1
5 | Create Date: 2014-05-09 11:06:50.845168
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '15bd4b7e6622'
11 | down_revision = '3d8177efcfe1'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_job_filname', 'filecoverage', ['job_id', 'filename'])
18 |
19 |
20 | def downgrade():
21 | op.drop_constraint('unq_job_filname', 'filecoverage')
22 |
--------------------------------------------------------------------------------
/migrations/versions/208224023555_add_repository_backe.py:
--------------------------------------------------------------------------------
1 | """Add Repository.backend
2 |
3 | Revision ID: 208224023555
4 | Revises: 1d1f467bdf3d
5 | Create Date: 2013-11-25 15:12:30.867388
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '208224023555'
11 | down_revision = '1d1f467bdf3d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('repository', sa.Column('backend', sa.Enum(), nullable=False, server_default='0'))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('repository', 'backend')
23 |
--------------------------------------------------------------------------------
/migrations/versions/2fa8391277a0_add_jobstep_last_heartbeat.py:
--------------------------------------------------------------------------------
1 | """Add JobStep.last_heartbeat
2 |
3 | Revision ID: 2fa8391277a0
4 | Revises: 3768db8af6ea
5 | Create Date: 2014-11-04 16:14:32.624975
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2fa8391277a0'
11 | down_revision = '3768db8af6ea'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobstep', sa.Column('last_heartbeat', sa.DateTime(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('jobstep', 'last_heartbeat')
23 |
--------------------------------------------------------------------------------
/migrations/versions/3f289637f530_remove_unused_models.py:
--------------------------------------------------------------------------------
1 | """Remove unused models
2 |
3 | Revision ID: 3f289637f530
4 | Revises: 4ba1dd8c3080
5 | Create Date: 2014-04-17 11:08:50.963964
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3f289637f530'
11 | down_revision = '4ba1dd8c3080'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_table('aggtestgroup')
18 | op.drop_table('testgroup_test')
19 | op.drop_table('testgroup')
20 | op.drop_table('aggtestsuite')
21 |
22 |
23 | def downgrade():
24 | raise NotImplementedError
25 |
--------------------------------------------------------------------------------
/migrations/versions/520eba1ce36e_remove_author_name_u.py:
--------------------------------------------------------------------------------
1 | """Remove Author.name unique constraint
2 |
3 | Revision ID: 520eba1ce36e
4 | Revises: 18c045b75331
5 | Create Date: 2013-11-04 19:20:04.277883
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '520eba1ce36e'
11 | down_revision = '18c045b75331'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_constraint('author_name_key', 'author')
18 |
19 |
20 | def downgrade():
21 | ### commands auto generated by Alembic - please adjust! ###
22 | pass
23 | ### end Alembic commands ###
24 |
--------------------------------------------------------------------------------
/migrations/versions/554f414d4c46_set_on_delete_cascad.py:
--------------------------------------------------------------------------------
1 | """Set ON DELETE CASCADE on Step.*
2 |
3 | Revision ID: 554f414d4c46
4 | Revises: 306fefe51dc6
5 | Create Date: 2013-12-23 16:46:05.137414
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '554f414d4c46'
11 | down_revision = '306fefe51dc6'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_constraint('step_plan_id_fkey', 'step')
18 | op.create_foreign_key('step_plan_id_fkey', 'step', 'plan', ['plan_id'], ['id'], ondelete='CASCADE')
19 |
20 |
21 | def downgrade():
22 | pass
23 |
--------------------------------------------------------------------------------
/migrations/versions/f8ed1f99eb1_add_projectplan_avg_.py:
--------------------------------------------------------------------------------
1 | """Add ProjectPlan.avg_build_time
2 |
3 | Revision ID: f8ed1f99eb1
4 | Revises: 25d63e09ea3b
5 | Create Date: 2014-01-02 16:12:43.339060
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'f8ed1f99eb1'
11 | down_revision = '25d63e09ea3b'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('project_plan', sa.Column('avg_build_time', sa.Integer(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('project_plan', 'avg_build_time')
23 |
--------------------------------------------------------------------------------
/changes/api/build_message_index.py:
--------------------------------------------------------------------------------
1 | from changes.api.base import APIView, error
2 | from changes.models.build import Build
3 | from changes.models.buildmessage import BuildMessage
4 |
5 |
6 | class BuildMessageIndexAPIView(APIView):
7 | def get(self, build_id):
8 | build = Build.query.get(build_id)
9 | if not build:
10 | return error('build not found', http_code=404)
11 | queryset = BuildMessage.query.filter(
12 | BuildMessage.build_id == build.id,
13 | ).order_by(BuildMessage.date_created.asc())
14 |
15 | return self.paginate(queryset)
16 |
--------------------------------------------------------------------------------
/changes/api/node_from_hostname.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView, error
4 | from changes.models.node import Node
5 |
6 |
7 | class NodeFromHostnameAPIView(APIView):
8 | def get(self, node_hostname):
9 | node = Node.query.filter(Node.label == node_hostname).first()
10 | if node is None:
11 | return error("Node not found", http_code=404)
12 |
13 | context = self.serialize(node)
14 | context['clusters'] = self.serialize(list(node.clusters))
15 |
16 | return self.respond(context, serialize=False)
17 |
--------------------------------------------------------------------------------
/migrations/versions/57e4a0553903_add_snapshotimage_status.py:
--------------------------------------------------------------------------------
1 | """Add SnapshotImage.status
2 |
3 | Revision ID: 57e4a0553903
4 | Revises: 30c86cc72856
5 | Create Date: 2014-08-21 23:03:23.553069
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '57e4a0553903'
11 | down_revision = '30c86cc72856'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('snapshot_image', sa.Column('status', sa.Enum(), server_default='0', nullable=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('snapshot_image', 'status')
23 |
--------------------------------------------------------------------------------
/migrations/versions/8550c7394f4_change_commands_env_to_text.py:
--------------------------------------------------------------------------------
1 | """change commands.env to text
2 |
3 | Revision ID: 8550c7394f4
4 | Revises: 5844fbc9d9e4
5 | Create Date: 2015-06-15 13:42:13.990095
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '8550c7394f4'
11 | down_revision = '5844fbc9d9e4'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.alter_column('command', 'env', type_ = sa.types.Text())
19 |
20 |
21 | def downgrade():
22 | op.alter_column('command', 'env', type_ = sa.types.String(length=2048))
23 |
24 |
--------------------------------------------------------------------------------
/migrations/versions/18c150792a21_add_column_flakyteststat_first_run.py:
--------------------------------------------------------------------------------
1 | """Add column flakyteststat.first_run
2 |
3 | Revision ID: 18c150792a21
4 | Revises: 3bc4c6853367
5 | Create Date: 2015-06-03 18:29:05.131373
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '18c150792a21'
11 | down_revision = '3bc4c6853367'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('flakyteststat', sa.Column('first_run', sa.Date(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('flakyteststat', 'first_run')
23 |
--------------------------------------------------------------------------------
/migrations/versions/30b9f619c3b2_remove_test_suite.py:
--------------------------------------------------------------------------------
1 | """Remove test suite
2 |
3 | Revision ID: 30b9f619c3b2
4 | Revises: 3b55ff8856f5
5 | Create Date: 2014-05-09 14:36:41.548924
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '30b9f619c3b2'
11 | down_revision = '3b55ff8856f5'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_test_name', 'test', ['job_id', 'label_sha'])
18 | op.drop_column('test', 'suite_id')
19 |
20 | op.drop_table('testsuite')
21 |
22 |
23 | def downgrade():
24 | raise NotImplementedError
25 |
--------------------------------------------------------------------------------
/migrations/versions/17c884f6f57c_add_failurereason_column.py:
--------------------------------------------------------------------------------
1 | """add failurereason column
2 |
3 | Revision ID: 17c884f6f57c
4 | Revises: 3cf79ad9bde
5 | Create Date: 2015-07-29 08:22:59.755597
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '17c884f6f57c'
11 | down_revision = '3cf79ad9bde'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column(
19 | 'failurereason',
20 | sa.Column('data', sa.JSONEncodedDict(), nullable=True))
21 |
22 |
23 | def downgrade():
24 | op.drop_column('failurereason', 'data')
25 |
--------------------------------------------------------------------------------
/migrations/versions/1b229c83511d_add_logsource_in_artifact_store.py:
--------------------------------------------------------------------------------
1 | """add_logsource_in_artifact_store
2 |
3 | Revision ID: 1b229c83511d
4 | Revises: 382745ed4478
5 | Create Date: 2015-10-07 14:55:58.423046
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1b229c83511d'
11 | down_revision = '382745ed4478'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('logsource', sa.Column('in_artifact_store', sa.Boolean(), default=False))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('logsource', 'in_artifact_store')
23 |
--------------------------------------------------------------------------------
/migrations/versions/d324e8fc580_add_various_testsuit.py:
--------------------------------------------------------------------------------
1 | """Add various TestSuite indexes
2 |
3 | Revision ID: d324e8fc580
4 | Revises: 3bef10ab5088
5 | Create Date: 2013-11-04 17:12:09.353301
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'd324e8fc580'
11 | down_revision = '3bef10ab5088'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_testsuite_project_id', 'testsuite', ['project_id'])
18 |
19 |
20 | def downgrade():
21 | ### commands auto generated by Alembic - please adjust! ###
22 | pass
23 | ### end Alembic commands ###
24 |
--------------------------------------------------------------------------------
/changes/jobs/check_repos.py:
--------------------------------------------------------------------------------
1 | from changes.jobs.sync_repo import sync_repo
2 | from changes.models.repository import Repository, RepositoryBackend
3 |
4 |
5 | def check_repos():
6 | """
7 | Looks for any repositories which haven't checked in within several minutes
8 | and creates `sync_repo` tasks for them.
9 | """
10 | repo_list = list(Repository.query.filter(
11 | Repository.backend != RepositoryBackend.unknown,
12 | ))
13 |
14 | for repo in repo_list:
15 | sync_repo.delay_if_needed(
16 | task_id=repo.id.hex,
17 | repo_id=repo.id.hex,
18 | )
19 |
--------------------------------------------------------------------------------
/migrations/versions/291978a5d5e4_add_revision_date_created_signal.py:
--------------------------------------------------------------------------------
1 | """add revision date created signal
2 |
3 | Revision ID: 291978a5d5e4
4 | Revises: 17c884f6f57c
5 | Create Date: 2015-07-31 14:17:01.337179
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '291978a5d5e4'
11 | down_revision = '17c884f6f57c'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('revision', sa.Column('date_created_signal', sa.DateTime(), nullable=True))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('revision', 'date_created_signal')
23 |
--------------------------------------------------------------------------------
/migrations/versions/3aed22af8f4f_add_build_tag.py:
--------------------------------------------------------------------------------
1 | """Add Build.tags
2 |
3 | Revision ID: 3aed22af8f4f
4 | Revises: 4e2d942b58b0
5 | Create Date: 2014-12-01 12:38:25.867513
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3aed22af8f4f'
11 | down_revision = '4e2d942b58b0'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy.dialects import postgresql
16 |
17 |
18 | def upgrade():
19 | op.add_column('build', sa.Column('tags', postgresql.ARRAY(sa.String(length=16)), nullable=True))
20 |
21 |
22 | def downgrade():
23 | op.drop_column('build', 'tags')
24 |
--------------------------------------------------------------------------------
/migrations/versions/1db7a1ab95db_fix_filecoverage_pro.py:
--------------------------------------------------------------------------------
1 | """Fix FileCoverage.project_id
2 |
3 | Revision ID: 1db7a1ab95db
4 | Revises: 36becb086fcb
5 | Create Date: 2014-03-21 15:47:41.430619
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1db7a1ab95db'
11 | down_revision = '36becb086fcb'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.drop_column('filecoverage', 'project_id')
19 | op.add_column('filecoverage', sa.Column('project_id', sa.GUID(), nullable=False))
20 |
21 |
22 | def downgrade():
23 | raise NotImplementedError
24 |
--------------------------------------------------------------------------------
/migrations/versions/3b22c1663664_change_logsource_constraint.py:
--------------------------------------------------------------------------------
1 | """Change LogSource constraint
2 |
3 | Revision ID: 3b22c1663664
4 | Revises: 15f131d88adf
5 | Create Date: 2014-06-25 01:07:42.217354
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3b22c1663664'
11 | down_revision = '15f131d88adf'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint(
18 | 'unq_logsource_key2', 'logsource', ['step_id', 'name'])
19 | op.drop_constraint('unq_logsource_key', 'logsource')
20 |
21 |
22 | def downgrade():
23 | raise NotImplementedError
24 |
--------------------------------------------------------------------------------
/webapp/display/section_header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /*
4 | * Very simple text component designed to be the title above grid elements
5 | */
6 | var SectionHeader = React.createClass({
7 |
8 | propTypes: {
9 | // ...
10 | // transfers all properties to rendered
11 | },
12 |
13 | render: function() {
14 | var { className, ...others } = this.props;
15 | className = (className || "") + " sectionHeader nonFixedClass";
16 | return
17 | {this.props.children}
18 |
;
19 | }
20 | });
21 |
22 | export default SectionHeader;
23 |
--------------------------------------------------------------------------------
/changes/api/repository_project_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, division, unicode_literals
2 |
3 | from changes.api.base import APIView
4 | from changes.models.project import Project
5 | from changes.models.repository import Repository
6 |
7 |
8 | class RepositoryProjectIndexAPIView(APIView):
9 | def get(self, repository_id):
10 | repo = Repository.query.get(repository_id)
11 | if repo is None:
12 | return '', 404
13 |
14 | queryset = Project.query.filter(
15 | Project.repository_id == repo.id,
16 | )
17 |
18 | return self.paginate(queryset)
19 |
--------------------------------------------------------------------------------
/migrations/versions/3042d0ca43bf_index_job_project_id.py:
--------------------------------------------------------------------------------
1 | """Index Job(project_id, status, date_created) where patch_id IS NULL
2 |
3 | Revision ID: 3042d0ca43bf
4 | Revises: 3a3366fb7822
5 | Create Date: 2014-01-03 15:24:39.947813
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3042d0ca43bf'
11 | down_revision = '3a3366fb7822'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('CREATE INDEX idx_job_previous_runs ON job (project_id, status, date_created) WHERE patch_id IS NULL')
18 |
19 |
20 | def downgrade():
21 | op.drop_index('idx_job_previous_runs', 'job')
22 |
--------------------------------------------------------------------------------
/migrations/versions/4a12e7f0159d_remove_build_repository_id_revision_sha_.py:
--------------------------------------------------------------------------------
1 | """Remove Build.{repository_id,revision_sha,patch_id}
2 |
3 | Revision ID: 4a12e7f0159d
4 | Revises: 1b2fa9c97090
5 | Create Date: 2014-05-15 12:12:19.106724
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4a12e7f0159d'
11 | down_revision = '1b2fa9c97090'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_column('build', 'revision_sha')
18 | op.drop_column('build', 'repository_id')
19 | op.drop_column('build', 'patch_id')
20 |
21 |
22 | def downgrade():
23 | raise NotImplementedError
24 |
--------------------------------------------------------------------------------
/migrations/versions/5844fbc9d9e4_add_flakyteststat_double_reruns.py:
--------------------------------------------------------------------------------
1 | """Add flakyteststat.double_reruns
2 |
3 | Revision ID: 5844fbc9d9e4
4 | Revises: 18c150792a21
5 | Create Date: 2015-06-04 14:20:02.697048
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5844fbc9d9e4'
11 | down_revision = '18c150792a21'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('flakyteststat', sa.Column('double_reruns', sa.Integer(), nullable=False, server_default='0'))
19 |
20 |
21 | def downgrade():
22 | op.drop_column('flakyteststat', 'double_reruns')
23 |
--------------------------------------------------------------------------------
/tests/changes/api/test_testcase_details.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class TestCaseDetailsTest(APITestCase):
5 | def test_simple(self):
6 | project = self.create_project()
7 | build = self.create_build(project=project)
8 | job = self.create_job(build=build)
9 |
10 | testcase = self.create_test(job=job)
11 |
12 | path = '/api/0/tests/{0}/'.format(
13 | testcase.id.hex)
14 |
15 | resp = self.client.get(path)
16 | assert resp.status_code == 200
17 | data = self.unserialize(resp)
18 | assert data['id'] == testcase.id.hex
19 |
--------------------------------------------------------------------------------
/changes/db/types/enum.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.types import TypeDecorator, INT
2 |
3 |
4 | class Enum(TypeDecorator):
5 | impl = INT
6 |
7 | def __init__(self, enum=None, *args, **kwargs):
8 | self.enum = enum
9 | super(Enum, self).__init__(*args, **kwargs)
10 |
11 | def process_bind_param(self, value, dialect):
12 | if value is None:
13 | return value
14 | return value.value
15 |
16 | def process_result_value(self, value, dialect):
17 | if value is None:
18 | return value
19 | elif self.enum:
20 | return self.enum(value)
21 | return value
22 |
--------------------------------------------------------------------------------
/changes/api/cluster_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView
4 | from changes.models.node import Cluster, Node
5 |
6 |
7 | class ClusterDetailsAPIView(APIView):
8 | def get(self, cluster_id):
9 | cluster = Cluster.query.get(cluster_id)
10 | if cluster is None:
11 | return '', 404
12 |
13 | node_count = Node.query.filter(
14 | Node.clusters.contains(cluster),
15 | ).count()
16 |
17 | context = self.serialize(cluster)
18 | context['numNodes'] = node_count
19 |
20 | return self.respond(context, serialize=False)
21 |
--------------------------------------------------------------------------------
/migrations/versions/36becb086fcb_add_revision_branche.py:
--------------------------------------------------------------------------------
1 | """Add Revision.branches
2 |
3 | Revision ID: 36becb086fcb
4 | Revises: 11a7a7f2652f
5 | Create Date: 2014-03-18 17:48:37.484301
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '36becb086fcb'
11 | down_revision = '11a7a7f2652f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy.dialects import postgresql
16 |
17 |
18 | def upgrade():
19 | op.add_column('revision', sa.Column('branches', postgresql.ARRAY(sa.String(length=128)), nullable=True))
20 |
21 |
22 | def downgrade():
23 | op.drop_column('revision', 'branches')
24 |
--------------------------------------------------------------------------------
/tests/changes/utils/test_trees.py:
--------------------------------------------------------------------------------
1 | from changes.utils.trees import build_tree
2 |
3 |
4 | def test_build_tree():
5 | test_names = [
6 | 'foo.bar.bar',
7 | 'foo.bar.biz',
8 | 'foo.biz',
9 | 'blah.brah',
10 | 'blah.blah.blah',
11 | ]
12 |
13 | result = build_tree(test_names, min_children=2)
14 |
15 | assert sorted(result) == ['blah', 'foo']
16 |
17 | result = build_tree(test_names, min_children=2, parent='foo')
18 |
19 | assert sorted(result) == ['foo.bar', 'foo.biz']
20 |
21 | result = build_tree(test_names, min_children=2, parent='foo.biz')
22 |
23 | assert result == set()
24 |
--------------------------------------------------------------------------------
/migrations/versions/3d9067f21201_unique_step_order.py:
--------------------------------------------------------------------------------
1 | """Unique Step.order
2 |
3 | Revision ID: 3d9067f21201
4 | Revises: 2622a69cd25a
5 | Create Date: 2013-12-18 15:06:47.035804
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3d9067f21201'
11 | down_revision = '2622a69cd25a'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_step_key', 'step', ['plan_id', 'order'])
18 | op.drop_index('idx_step_plan_id', 'step')
19 |
20 |
21 | def downgrade():
22 | op.create_index('idx_step_plan_id', 'step', ['plan_id'])
23 | op.drop_constraint('unq_step_key', 'step')
24 |
--------------------------------------------------------------------------------
/tests/changes/api/test_project_plan_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ProjectPlanListTest(APITestCase):
5 | def test_retrieve(self):
6 | project = self.create_project()
7 | project2 = self.create_project()
8 | path = '/api/0/projects/{0}/plans/'.format(
9 | project.id.hex)
10 |
11 | plan = self.create_plan(project)
12 | self.create_plan(project2)
13 |
14 | resp = self.client.get(path)
15 | assert resp.status_code == 200
16 | data = self.unserialize(resp)
17 | assert len(data) == 1
18 | assert data[0]['id'] == plan.id.hex
19 |
--------------------------------------------------------------------------------
/migrations/versions/2d82db02b3ef_fill_testgroup_num_l.py:
--------------------------------------------------------------------------------
1 | """Fill TestGroup.num_leaves
2 |
3 | Revision ID: 2d82db02b3ef
4 | Revises: 4640ecd97c82
5 | Create Date: 2013-11-08 13:14:05.257558
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2d82db02b3ef'
11 | down_revision = '4640ecd97c82'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute("""
18 | update testgroup as a
19 | set num_leaves = (
20 | select count(*)
21 | from testgroup as b
22 | where a.id = b.parent_id
23 | )
24 | """)
25 |
26 |
27 | def downgrade():
28 | pass
29 |
--------------------------------------------------------------------------------
/migrations/versions/3b94584c761e_create_index_in_flakyteststat_for_test_.py:
--------------------------------------------------------------------------------
1 | """Create index in flakyteststat for test_id
2 |
3 | Revision ID: 3b94584c761e
4 | Revises: 3be107806e62
5 | Create Date: 2016-07-13 14:03:31.967630
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3b94584c761e'
11 | down_revision = '3be107806e62'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_index('idx_flakyteststat_last_flaky_run_id', 'flakyteststat', ['last_flaky_run_id'])
19 |
20 |
21 | def downgrade():
22 | op.drop_index('idx_flakyteststat_last_flaky_run_id', 'flakyteststat')
23 |
--------------------------------------------------------------------------------
/migrations/versions/382745ed4478_add_jobplan_snapshotimage.py:
--------------------------------------------------------------------------------
1 | """add jobplan snapshotimage
2 |
3 | Revision ID: 382745ed4478
4 | Revises: 3e85891e90bb
5 | Create Date: 2015-09-25 10:20:05.963135
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '382745ed4478'
11 | down_revision = '3e85891e90bb'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobplan', sa.Column('snapshot_image_id', sa.GUID(),
19 | sa.ForeignKey('snapshot_image.id', ondelete="RESTRICT"), nullable=True))
20 |
21 |
22 | def downgrade():
23 | op.drop_column('jobplan', 'snapshot_image_id')
24 |
--------------------------------------------------------------------------------
/static/vendor/uri.js/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "URIjs",
3 | "version": "1.15.2",
4 | "main": "src/URI.js",
5 | "ignore": [
6 | ".*",
7 | "*.css",
8 | "/*.js",
9 | "/*.html",
10 | "/*.json",
11 | "utils",
12 | "test",
13 | "prettify"
14 | ],
15 | "license": "MIT",
16 | "homepage": "https://github.com/medialize/URI.js",
17 | "_release": "1.15.2",
18 | "_resolution": {
19 | "type": "version",
20 | "tag": "v1.15.2",
21 | "commit": "c51da7e8c0bdf46ae926d2388d3032e2347c61db"
22 | },
23 | "_source": "https://github.com/medialize/URI.js.git",
24 | "_target": "~1.15.2",
25 | "_originalSource": "uri.js"
26 | }
--------------------------------------------------------------------------------
/changes/api/build_target_message_index.py:
--------------------------------------------------------------------------------
1 | from changes.api.base import APIView, error
2 | from changes.models.bazeltarget import BazelTarget
3 | from changes.models.bazeltargetmessage import BazelTargetMessage
4 |
5 |
6 | class BuildTargetMessageIndex(APIView):
7 | def get(self, build_id, target_id):
8 | target = BazelTarget.query.get(target_id)
9 | if not target:
10 | return error('target not found', http_code=404)
11 | queryset = BazelTargetMessage.query.filter(
12 | BazelTargetMessage.target_id == target.id,
13 | ).order_by(BazelTargetMessage.date_created.asc())
14 |
15 | return self.paginate(queryset)
16 |
--------------------------------------------------------------------------------
/changes/api/serializer/vcs/revisionresult.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.vcs.base import RevisionResult
3 |
4 |
5 | @register(RevisionResult)
6 | class RevisionCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id,
10 | 'sha': instance.id, # Having both id and sha is a bit distasteful. We should try to fix this.
11 | 'message': instance.message,
12 | 'author': None, # We don't return author information
13 | 'dateCreated': instance.author_date,
14 | 'dateCommitted': instance.committer_date,
15 | }
16 |
--------------------------------------------------------------------------------
/static/vendor/react-bootstrap/README.md:
--------------------------------------------------------------------------------
1 | # react-bootstrap-bower
2 |
3 | [Bootstrap 3](http://getbootstrap.com) components built with [React](http://facebook.github.io/react/)
4 |
5 | This repo contains built AMD modules and standalone browser globals.
6 |
7 | There is a separate [source repo](https://github.com/react-bootstrap/react-bootstrap).
8 |
9 | A [docs site](http://react-bootstrap.github.io) with live editable examples.
10 |
11 | [](https://travis-ci.org/react-bootstrap/react-bootstrap) [](http://badge.fury.io/bo/react-bootstrap)
12 |
--------------------------------------------------------------------------------
/migrations/versions/40429d3569cf_add_plan_project_id.py:
--------------------------------------------------------------------------------
1 | """Add Plan.project_id
2 |
3 | Revision ID: 40429d3569cf
4 | Revises: 36cbde703cc0
5 | Create Date: 2014-10-09 14:38:51.043652
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '40429d3569cf'
11 | down_revision = '36cbde703cc0'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('plan', sa.Column('project_id', sa.GUID(), nullable=True))
19 | op.create_foreign_key('plan_project_id_fkey', 'plan', 'project', ['project_id'], ['id'], ondelete='CASCADE')
20 |
21 |
22 | def downgrade():
23 | op.drop_column('plan', 'project_id')
24 |
--------------------------------------------------------------------------------
/migrations/versions/cb99fdfb903_add_build_family_id.py:
--------------------------------------------------------------------------------
1 | """Add Build.family_id
2 |
3 | Revision ID: cb99fdfb903
4 | Revises: 1109e724859f
5 | Create Date: 2013-12-23 11:32:17.060863
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'cb99fdfb903'
11 | down_revision = '1109e724859f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('build', sa.Column('family_id', sa.GUID(), nullable=True))
19 | op.create_index('idx_build_family_id', 'build', ['family_id'])
20 |
21 |
22 | def downgrade():
23 | op.drop_index('idx_build_family_id', 'build')
24 | op.drop_column('build', 'family_id')
25 |
--------------------------------------------------------------------------------
/static/vendor/requirejs-babel/README.md:
--------------------------------------------------------------------------------
1 | Babel (6to5) Plugin
2 | ===
3 |
4 | A [Babel](https://babeljs.io/) loader plugin for [RequireJS](http://requirejs.org).
5 |
6 | Installation
7 | ---
8 |
9 | ```
10 | $ npm install -g bower
11 | $ bower install requirejs-babel
12 | ```
13 |
14 | Usage
15 | ---
16 |
17 | Add the paths to configuration:
18 |
19 | ```javascript
20 | paths: {
21 | es6: '...path_to_bower/requirejs-babel/es6',
22 | babel: '...path_to_bower/requirejs-babel/babel-4.6.6.min'
23 | }
24 | ```
25 |
26 | Reference files via the es6! plugin name:
27 | ```javascript
28 | define(['es6!your-es6-module'], function(module) {
29 | // ...
30 | });
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/_themes/kr/layout.html:
--------------------------------------------------------------------------------
1 | {%- extends "basic/layout.html" %}
2 | {%- block extrahead %}
3 | {{ super() }}
4 | {% if theme_touch_icon %}
5 |
6 | {% endif %}
7 |
9 |
10 | {% endblock %}
11 | {%- block relbar2 %}{% endblock %}
12 | {%- block footer %}
13 |
16 | {%- endblock %}
17 |
--------------------------------------------------------------------------------
/docs/_themes/kr/relations.html:
--------------------------------------------------------------------------------
1 | Related Topics
2 |
20 |
--------------------------------------------------------------------------------
/migrations/versions/460741ff1212_add_project_permissions_column_to_user.py:
--------------------------------------------------------------------------------
1 | """add project_permissions column to user
2 |
3 | Revision ID: 460741ff1212
4 | Revises: 3b94584c761e
5 | Create Date: 2016-08-19 09:26:10.731482
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '460741ff1212'
11 | down_revision = '3b94584c761e'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy.dialects import postgresql
16 |
17 |
18 | def upgrade():
19 | op.add_column('user', sa.Column('project_permissions', postgresql.ARRAY(sa.String(length=256)), nullable=True))
20 |
21 |
22 | def downgrade():
23 | op.drop_column('user', 'project_permissions')
24 |
--------------------------------------------------------------------------------
/migrations/versions/1d806848a73f_unique_buildplan_bui.py:
--------------------------------------------------------------------------------
1 | """Unique BuildPlan.build
2 |
3 | Revision ID: 1d806848a73f
4 | Revises: ff220d76c11
5 | Create Date: 2013-12-13 13:37:37.833620
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1d806848a73f'
11 | down_revision = 'ff220d76c11'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_unique_constraint('unq_buildplan_build', 'buildplan', ['build_id'])
18 | op.drop_index('idx_buildplan_build_id', 'buildplan')
19 |
20 |
21 | def downgrade():
22 | op.create_index('idx_buildplan_build_id', 'buildplan', ['build_id'])
23 | op.drop_constraint('unq_buildplan_build', 'buildplan')
24 |
--------------------------------------------------------------------------------
/migrations/versions/45e1cfacfc7d_task_parent_id_is_op.py:
--------------------------------------------------------------------------------
1 | """Task.parent_id is optional
2 |
3 | Revision ID: 45e1cfacfc7d
4 | Revises: 26dbd6ff063c
5 | Create Date: 2014-01-13 16:23:03.890537
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '45e1cfacfc7d'
11 | down_revision = '26dbd6ff063c'
12 |
13 | from alembic import op
14 | from sqlalchemy.dialects import postgresql
15 |
16 |
17 | def upgrade():
18 | op.alter_column('task', 'parent_id', existing_type=postgresql.UUID(),
19 | nullable=True)
20 |
21 |
22 | def downgrade():
23 | op.alter_column('task', 'parent_id', existing_type=postgresql.UUID(),
24 | nullable=False)
25 |
--------------------------------------------------------------------------------
/tests/changes/api/validators/test_basic.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from changes.api.validators.basic import bounded_integer
3 |
4 |
5 | def test_bounded_integer():
6 | validator = bounded_integer(0, 10)
7 | with pytest.raises(ValueError):
8 | validator("11")
9 |
10 | with pytest.raises(ValueError):
11 | validator("4.5")
12 |
13 | with pytest.raises(ValueError):
14 | validator("-3")
15 |
16 | with pytest.raises(ValueError):
17 | validator("")
18 |
19 | with pytest.raises(ValueError):
20 | validator("10000")
21 |
22 | # No error expeceted
23 | validator("0")
24 | validator("2")
25 | validator("9")
26 | validator("10")
27 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/patch.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.serializer import Crumbler, register
4 | from changes.models.patch import Patch
5 | from changes.utils.http import build_web_uri
6 |
7 |
8 | @register(Patch)
9 | class PatchCrumbler(Crumbler):
10 | def crumble(self, instance, attrs):
11 | return {
12 | 'id': instance.id.hex,
13 | 'diff': instance.diff,
14 | 'link': build_web_uri('/patches/{0}/'.format(instance.id.hex)),
15 | 'parentRevision': {
16 | 'sha': instance.parent_revision_sha,
17 | },
18 | 'dateCreated': instance.date_created,
19 | }
20 |
--------------------------------------------------------------------------------
/tests/changes/api/test_cluster_nodes.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ClusterNodesTest(APITestCase):
5 | def test_simple(self):
6 | cluster_1 = self.create_cluster(label='bar')
7 | node_1 = self.create_node(cluster=cluster_1, label='foo')
8 | node_2 = self.create_node(cluster=cluster_1, label='test')
9 |
10 | path = '/api/0/clusters/{0}/nodes/'.format(cluster_1.id.hex)
11 |
12 | resp = self.client.get(path)
13 | assert resp.status_code == 200
14 | data = self.unserialize(resp)
15 | assert len(data) == 2
16 | assert data[0]['id'] == node_1.id.hex
17 | assert data[1]['id'] == node_2.id.hex
18 |
--------------------------------------------------------------------------------
/changes/utils/cache.py:
--------------------------------------------------------------------------------
1 | class memoize(object):
2 | """
3 | Memoize the result of a property call.
4 |
5 | >>> class A(object):
6 | >>> @memoize
7 | >>> def func(self):
8 | >>> return 'foo'
9 | """
10 |
11 | def __init__(self, func):
12 | self.__name__ = func.__name__
13 | self.__module__ = func.__module__
14 | self.__doc__ = func.__doc__
15 | self.func = func
16 |
17 | def __get__(self, obj, type=None):
18 | if obj is None:
19 | return self
20 | d, n = vars(obj), self.__name__
21 | if n not in d:
22 | value = self.func(obj)
23 | d[n] = value
24 | return value
25 |
--------------------------------------------------------------------------------
/migrations/versions/15f131d88adf_remove_patch_project.py:
--------------------------------------------------------------------------------
1 | """Remove patch.project
2 |
3 | Revision ID: 15f131d88adf
4 | Revises: 1c5907e309f1
5 | Create Date: 2014-06-10 14:36:35.068328
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '15f131d88adf'
11 | down_revision = '1c5907e309f1'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy.dialects import postgresql
16 |
17 |
18 | def upgrade():
19 | op.drop_column('patch', 'project_id')
20 |
21 |
22 | def downgrade():
23 | op.create_index('idx_patch_project_id', 'patch', ['project_id'], unique=False)
24 | op.add_column('patch', sa.Column('project_id', postgresql.UUID(), nullable=False))
25 |
--------------------------------------------------------------------------------
/changes/api/auth_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, division, unicode_literals
2 |
3 | from changes.api.auth import get_current_user
4 | from changes.api.base import APIView
5 |
6 |
7 | class AuthIndexAPIView(APIView):
8 | def get(self):
9 | """
10 | Return information on the currently authenticated user.
11 | """
12 | user = get_current_user()
13 |
14 | if user is None:
15 | context = {
16 | 'authenticated': False,
17 | }
18 | else:
19 | context = {
20 | 'authenticated': True,
21 | 'user': user,
22 | }
23 |
24 | return self.respond(context)
25 |
--------------------------------------------------------------------------------
/migrations/versions/1ad857c78a0d_family_id_build_id.py:
--------------------------------------------------------------------------------
1 | """*.family_id => build_id
2 |
3 | Revision ID: 1ad857c78a0d
4 | Revises: 545ba163595
5 | Create Date: 2013-12-26 01:57:32.211057
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1ad857c78a0d'
11 | down_revision = '545ba163595'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE job RENAME COLUMN family_id TO build_id')
18 | op.execute('ALTER TABLE jobplan RENAME COLUMN family_id TO build_id')
19 |
20 |
21 | def downgrade():
22 | op.execute('ALTER TABLE job RENAME COLUMN build_id TO family_id')
23 | op.execute('ALTER TABLE jobplan RENAME COLUMN build_id TO family_id')
24 |
--------------------------------------------------------------------------------
/migrations/versions/3df65ebfa27e_remove_patch_label_and_message.py:
--------------------------------------------------------------------------------
1 | """Remove Patch label and message
2 |
3 | Revision ID: 3df65ebfa27e
4 | Revises: 315e787e94bf
5 | Create Date: 2014-05-06 18:41:16.245897
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3df65ebfa27e'
11 | down_revision = '315e787e94bf'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.drop_column('patch', 'message')
19 | op.drop_column('patch', 'label')
20 |
21 |
22 | def downgrade():
23 | op.add_column('patch', sa.Column('label', sa.VARCHAR(length=64), nullable=False))
24 | op.add_column('patch', sa.Column('message', sa.TEXT(), nullable=True))
25 |
--------------------------------------------------------------------------------
/changes/api/job_artifact_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from sqlalchemy.orm import joinedload
4 |
5 | from changes.api.base import APIView
6 | from changes.models.artifact import Artifact
7 | from changes.models.job import Job
8 |
9 |
10 | class JobArtifactIndexAPIView(APIView):
11 | def get(self, job_id):
12 | job = Job.query.get(job_id)
13 | if job is None:
14 | return '', 404
15 |
16 | queryset = Artifact.query.options(
17 | joinedload('step')
18 | ).filter(
19 | Artifact.job_id == job.id,
20 | ).order_by(
21 | Artifact.name.asc(),
22 | )
23 |
24 | return self.paginate(queryset)
25 |
--------------------------------------------------------------------------------
/migrations/versions/1cb3db431e29_add_build_collection_id.py:
--------------------------------------------------------------------------------
1 | """Add Build.collection_id
2 |
3 | Revision ID: 1cb3db431e29
4 | Revises: 3aed22af8f4f
5 | Create Date: 2014-12-10 22:00:33.463247
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1cb3db431e29'
11 | down_revision = '3aed22af8f4f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('build', sa.Column('collection_id', sa.GUID(), nullable=True))
19 | op.create_index('idx_build_collection_id', 'build', ['collection_id'])
20 |
21 |
22 | def downgrade():
23 | op.drop_index('idx_build_collection_id', 'build')
24 | op.drop_column('build', 'collection_id')
25 |
--------------------------------------------------------------------------------
/migrations/versions/290a71847cd7_add_jobstep_cluster.py:
--------------------------------------------------------------------------------
1 | """add jobstep cluster
2 |
3 | Revision ID: 290a71847cd7
4 | Revises: 41c7ef24fd4c
5 | Create Date: 2016-01-11 14:07:49.879954
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '290a71847cd7'
11 | down_revision = '41c7ef24fd4c'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobstep', sa.Column('cluster', sa.String(length=128),
19 | nullable=True))
20 | op.create_index('idx_jobstep_cluster', 'jobstep', ['cluster'])
21 |
22 |
23 | def downgrade():
24 | # also drops the index
25 | op.drop_column('jobstep', 'cluster')
26 |
--------------------------------------------------------------------------------
/migrations/versions/57e24a9f2290_log_build_id_job_id.py:
--------------------------------------------------------------------------------
1 | """Log*.build_id => job_id
2 |
3 | Revision ID: 57e24a9f2290
4 | Revises: 35af40cebcde
5 | Create Date: 2013-12-26 01:03:06.812123
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '57e24a9f2290'
11 | down_revision = '35af40cebcde'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE logsource RENAME COLUMN build_id TO job_id')
18 | op.execute('ALTER TABLE logchunk RENAME COLUMN build_id TO job_id')
19 |
20 |
21 | def downgrade():
22 | op.execute('ALTER TABLE logsource RENAME COLUMN job_id TO build_id')
23 | op.execute('ALTER TABLE logchunk RENAME COLUMN job_id TO build_id')
24 |
--------------------------------------------------------------------------------
/static/vendor/requirejs/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "requirejs",
3 | "version": "2.1.22",
4 | "ignore": [],
5 | "homepage": "http://requirejs.org",
6 | "authors": [
7 | "jrburke.com"
8 | ],
9 | "description": "A file and module loader for JavaScript",
10 | "main": "require.js",
11 | "keywords": [
12 | "AMD"
13 | ],
14 | "license": [
15 | "BSD-3-Clause",
16 | "MIT"
17 | ],
18 | "_release": "2.1.22",
19 | "_resolution": {
20 | "type": "version",
21 | "tag": "2.1.22",
22 | "commit": "9cd0b99417eac61e890d11c5119f2e45e752c999"
23 | },
24 | "_source": "https://github.com/jrburke/requirejs-bower.git",
25 | "_target": "~2.1.11",
26 | "_originalSource": "requirejs"
27 | }
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_project.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from uuid import UUID
3 |
4 | from changes.api.serializer import serialize
5 | from changes.models.project import Project
6 |
7 |
8 | def test_simple():
9 | project = Project(
10 | id=UUID(hex='33846695b2774b29a71795a009e8168a'),
11 | slug='hello-world',
12 | name='Hello world',
13 | date_created=datetime(2013, 9, 19, 22, 15, 22),
14 | )
15 | result = serialize(project)
16 | assert result['name'] == 'Hello world'
17 | assert result['id'] == '33846695b2774b29a71795a009e8168a'
18 | assert result['slug'] == 'hello-world'
19 | assert result['dateCreated'] == '2013-09-19T22:15:22'
20 |
--------------------------------------------------------------------------------
/tests/changes/api/test_node_from_hostname.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class NodeFromHostnameTest(APITestCase):
5 | def test_simple(self):
6 | node = self.create_node(label='ip-127-0-0-1')
7 | path = '/api/0/nodes/hostname/{0}/'.format(node.label)
8 |
9 | resp = self.client.get(path)
10 | assert resp.status_code == 200
11 | data = self.unserialize(resp)
12 | assert data['id'] == node.id.hex
13 |
14 | def test_not_found(self):
15 | self.create_node(label='ip-127-0-0-1')
16 | path = '/api/0/nodes/hostname/{0}/'.format('ip-0-0-0-0')
17 |
18 | resp = self.client.get(path)
19 | assert resp.status_code == 404
20 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/project.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.project import Project
3 | from changes.utils.http import build_web_uri
4 |
5 |
6 | @register(Project)
7 | class ProjectCrumbler(Crumbler):
8 | def crumble(self, instance, attrs):
9 | return {
10 | 'id': instance.id.hex,
11 | 'slug': instance.slug,
12 | 'name': instance.name,
13 | 'repository': {
14 | 'id': instance.repository_id,
15 | },
16 | 'status': instance.status,
17 | 'dateCreated': instance.date_created,
18 | 'link': build_web_uri('/projects/{0}/'.format(instance.slug)),
19 | }
20 |
--------------------------------------------------------------------------------
/changes/api/validators/datetime.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from datetime import datetime
4 |
5 | import logging
6 |
7 | # We appear to be hitting https://bugs.python.org/issue7980, but by using
8 | # strptime early on, the race should be avoided.
9 | datetime.strptime("", "")
10 |
11 |
12 | class ISODatetime(object):
13 | def __call__(self, value):
14 | # type: (str) -> datetime
15 | try:
16 | return datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
17 | except Exception:
18 | logging.exception("Failed to parse datetime: %s", value)
19 | raise ValueError('Datetime was not parseable. Expected ISO 8601 with timezone: YYYY-MM-DDTHH:MM:SS.mmmmmmZ')
20 |
--------------------------------------------------------------------------------
/changes/utils/slugs.py:
--------------------------------------------------------------------------------
1 | import re
2 | import unicodedata
3 |
4 | # Extra characters outside of alphanumerics that we'll allow.
5 | SLUG_OK = '-_~'
6 |
7 |
8 | def slugify(s, ok=SLUG_OK, lower=True, spaces=False):
9 | # L and N signify letter/number.
10 | # http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
11 | rv = []
12 | for c in unicodedata.normalize('NFKC', unicode(s)):
13 | cat = unicodedata.category(c)[0]
14 | if cat in 'LN' or c in ok:
15 | rv.append(c)
16 | if cat == 'Z': # space
17 | rv.append(' ')
18 | new = ''.join(rv).strip()
19 | if not spaces:
20 | new = re.sub('[-\s]+', '-', new)
21 | return new.lower() if lower else new
22 |
--------------------------------------------------------------------------------
/migrations/versions/3265d2120c82_add_testcase_step_id.py:
--------------------------------------------------------------------------------
1 | """Add TestCase.step_id
2 |
3 | Revision ID: 3265d2120c82
4 | Revises: 21c9439330f
5 | Create Date: 2014-04-02 15:26:58.967387
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3265d2120c82'
11 | down_revision = '21c9439330f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('test', sa.Column('step_id', sa.GUID(), nullable=True))
19 | op.create_foreign_key('test_step_id_fkey', 'test', 'jobstep', ['step_id'], ['id'], ondelete='CASCADE')
20 | op.create_index('idx_test_step_id', 'test', ['step_id'])
21 |
22 |
23 | def downgrade():
24 | op.drop_column('test', 'step_id')
25 |
--------------------------------------------------------------------------------
/migrations/versions/3e85891e90bb_add_jobstep_replacement_id.py:
--------------------------------------------------------------------------------
1 | """Add jobstep replacement_id
2 |
3 | Revision ID: 3e85891e90bb
4 | Revises: 291978a5d5e4
5 | Create Date: 2015-08-31 16:19:46.669260
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3e85891e90bb'
11 | down_revision = '291978a5d5e4'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('jobstep', sa.Column('replacement_id', sa.GUID(),
19 | sa.ForeignKey('jobstep.id', ondelete="CASCADE"),
20 | unique=True, nullable=True))
21 |
22 |
23 | def downgrade():
24 | op.drop_column('jobstep', 'replacement_id')
25 |
--------------------------------------------------------------------------------
/tests/changes/api/test_build_mark_seen.py:
--------------------------------------------------------------------------------
1 | from changes.models.buildseen import BuildSeen
2 | from changes.testutils import APITestCase
3 |
4 |
5 | class BuildMarkSeenTest(APITestCase):
6 | def test_simple(self):
7 | project = self.create_project()
8 | build = self.create_build(project=project)
9 |
10 | self.login_default()
11 |
12 | path = '/api/0/builds/{0}/mark_seen/'.format(build.id.hex)
13 | resp = self.client.post(path)
14 |
15 | assert resp.status_code == 200
16 |
17 | buildseen = BuildSeen.query.filter(
18 | BuildSeen.user_id == self.default_user.id,
19 | BuildSeen.build_id == build.id,
20 | ).first()
21 |
22 | assert buildseen
23 |
--------------------------------------------------------------------------------
/tests/changes/api/test_patch_details.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class PatchDetailsTest(APITestCase):
5 | def test_simple(self):
6 | patch = self.create_patch()
7 |
8 | path = '/api/0/patches/{0}/'.format(patch.id.hex)
9 |
10 | resp = self.client.get(path)
11 | assert resp.status_code == 200
12 | data = self.unserialize(resp)
13 | assert data['id'] == patch.id.hex
14 |
15 | def test_raw(self):
16 | patch = self.create_patch()
17 |
18 | path = '/api/0/patches/{0}/?raw=1'.format(patch.id.hex)
19 |
20 | resp = self.client.get(path)
21 | assert resp.status_code == 200
22 | assert resp.data.decode('utf-8') == patch.diff
23 |
--------------------------------------------------------------------------------
/tests/changes/api/test_repository_project_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class RepositoryProjectListTest(APITestCase):
5 | def test_simple(self):
6 | repo = self.create_repo(url='https://example.com/bar')
7 | project = self.create_project(repository=repo, slug='foo')
8 | repo_2 = self.create_repo(url='https://example.com/foo')
9 | self.create_project(repository=repo_2, slug='bar')
10 |
11 | path = '/api/0/repositories/{0}/projects/'.format(repo.id)
12 |
13 | resp = self.client.get(path)
14 | assert resp.status_code == 200
15 | data = self.unserialize(resp)
16 | assert len(data) == 1
17 | assert data[0]['id'] == project.id.hex
18 |
--------------------------------------------------------------------------------
/migrations/versions/12569fada93_require_plan_project_id.py:
--------------------------------------------------------------------------------
1 | """Require Plan.project_id
2 |
3 | Revision ID: 12569fada93
4 | Revises: 4c3f2a63b7a7
5 | Create Date: 2014-10-14 11:23:49.892957
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '12569fada93'
11 | down_revision = '4c3f2a63b7a7'
12 |
13 | from alembic import op
14 | from sqlalchemy.dialects import postgresql
15 |
16 |
17 | def upgrade():
18 | op.alter_column('plan', 'project_id',
19 | existing_type=postgresql.UUID(),
20 | nullable=False)
21 |
22 |
23 | def downgrade():
24 | op.alter_column('plan', 'project_id',
25 | existing_type=postgresql.UUID(),
26 | nullable=True)
27 |
--------------------------------------------------------------------------------
/changes/models/itemsequence.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer
2 |
3 | from changes.config import db
4 | from changes.db.types.guid import GUID
5 |
6 |
7 | class ItemSequence(db.Model):
8 | """
9 | Used to hold counters for autoincrement-style sequence number generation.
10 | In each row, value is the last sequence number returned for the
11 | corresponding parent.
12 |
13 | The table is used via the next_item_value database function and not used in
14 | the python codebase.
15 | """
16 | __tablename__ = 'itemsequence'
17 |
18 | parent_id = Column(GUID, nullable=False, primary_key=True)
19 | value = Column(Integer, default=0, server_default='0', nullable=False,
20 | primary_key=True)
21 |
--------------------------------------------------------------------------------
/tests/changes/test_config.py:
--------------------------------------------------------------------------------
1 | from mock import patch
2 |
3 | from changes.config import db
4 | from changes.models.user import User
5 | from changes.testutils import TestCase
6 |
7 |
8 | class TestDBTransactionTracking(TestCase):
9 |
10 | @patch('logging.warning')
11 | def test_txn_timing(self, mock_warning):
12 | with patch('time.time', return_value=10):
13 | user_emails = list(db.session.query(User.email))
14 |
15 | assert 10 == db.session.info['txn_start_time']
16 |
17 | with patch('time.time', return_value=15):
18 | db.session.commit()
19 |
20 | assert 'txn_start_time' not in db.session.info
21 | call_args = mock_warning.call_args[0]
22 | assert call_args[2] == 5000
23 |
--------------------------------------------------------------------------------
/webapp/css/bootstrap_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "vars": {},
3 | "css": [
4 | "navs.less",
5 | "breadcrumbs.less",
6 | "pagination.less",
7 | "pager.less",
8 | "labels.less",
9 | "badges.less",
10 | "jumbotron.less",
11 | "thumbnails.less",
12 | "alerts.less",
13 | "progress-bars.less",
14 | "media.less",
15 | "list-group.less",
16 | "panels.less",
17 | "responsive-embed.less",
18 | "wells.less",
19 | "close.less",
20 | "component-animations.less",
21 | "dropdowns.less",
22 | "tooltip.less",
23 | "popovers.less",
24 | "modals.less",
25 | "carousel.less"
26 | ],
27 | "js": [],
28 | "customizerUrl": "http://getbootstrap.com/customize/?id=750b967675321de6e54c"
29 | }
30 |
--------------------------------------------------------------------------------
/changes/debug/reports/build.py:
--------------------------------------------------------------------------------
1 | import toronado
2 |
3 | from flask import render_template, request
4 | from flask.views import MethodView
5 | from jinja2 import Markup
6 |
7 | from changes.models.project import Project
8 | from changes.reports.build import BuildReport
9 |
10 |
11 | class BuildReportMailView(MethodView):
12 | def get(self, path=''):
13 | projects = Project.query.all()
14 |
15 | report = BuildReport(projects)
16 |
17 | context = report.generate(days=int(request.args.get('days', 7)))
18 |
19 | html_content = Markup(toronado.from_string(
20 | render_template('email/build_report.html', **context)
21 | ))
22 |
23 | return render_template('debug/email.html', html_content=html_content)
24 |
--------------------------------------------------------------------------------
/migrations/versions/2f3ba1e84a6f_update_source_constr.py:
--------------------------------------------------------------------------------
1 | """Update Source constraints
2 |
3 | Revision ID: 2f3ba1e84a6f
4 | Revises: cb99fdfb903
5 | Create Date: 2013-12-23 14:46:38.140915
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2f3ba1e84a6f'
11 | down_revision = 'cb99fdfb903'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('CREATE UNIQUE INDEX unq_source_revision ON source (repository_id, revision_sha) WHERE patch_id IS NULL')
18 | op.execute('CREATE UNIQUE INDEX unq_source_patch_id ON source (patch_id) WHERE patch_id IS NOT NULL')
19 |
20 |
21 | def downgrade():
22 | op.drop_constraint('unq_source_revision', 'source')
23 | op.drop_constraint('unq_source_patch_id', 'source')
24 |
--------------------------------------------------------------------------------
/migrations/versions/3bef10ab5088_add_various_testgrou.py:
--------------------------------------------------------------------------------
1 | """Add various TestGroup indexes
2 |
3 | Revision ID: 3bef10ab5088
4 | Revises: fd1a52fe89f
5 | Create Date: 2013-11-04 17:10:52.057285
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3bef10ab5088'
11 | down_revision = 'fd1a52fe89f'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_testgroup_project_id', 'testgroup', ['project_id'])
18 | op.create_index('idx_testgroup_suite_id', 'testgroup', ['suite_id'])
19 | op.create_index('idx_testgroup_parent_id', 'testgroup', ['parent_id'])
20 |
21 |
22 | def downgrade():
23 | ### commands auto generated by Alembic - please adjust! ###
24 | pass
25 | ### end Alembic commands ###
26 |
--------------------------------------------------------------------------------
/migrations/versions/52bcea82b482_remove_patch_url.py:
--------------------------------------------------------------------------------
1 | """Remove Patch.url
2 |
3 | Revision ID: 52bcea82b482
4 | Revises: 346c011ca77a
5 | Create Date: 2013-11-11 17:17:49.008228
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '52bcea82b482'
11 | down_revision = '346c011ca77a'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.drop_column('patch', u'url')
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.add_column('patch', sa.Column(u'url', sa.VARCHAR(length=200), nullable=True))
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/tests/changes/backends/jenkins/fixtures/GET/node_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | server-ubuntu-10.04 (ami-746cf244) (i-836023b7)
4 | Swarm slave from 127.0.0.1 : null
5 | /home/ubuntu
6 | 1
7 | NORMAL
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | jenkins-slave
17 |
18 |
--------------------------------------------------------------------------------
/migrations/versions/3bf1066f4935_add_label_in_testmessage.py:
--------------------------------------------------------------------------------
1 | """Add label in testmessage
2 |
3 | Revision ID: 3bf1066f4935
4 | Revises: 51775a13339d
5 | Create Date: 2016-06-30 14:12:07.405213
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3bf1066f4935'
11 | down_revision = '51775a13339d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('testmessage', sa.Column('label', sa.Text(), nullable=True))
19 | testmessage = sa.table('testmessage', sa.column('label'))
20 | op.execute(testmessage.update().values(label='test-message'))
21 | op.alter_column('testmessage', 'label', nullable=False)
22 |
23 |
24 | def downgrade():
25 | op.drop_column('testmessage', 'label')
26 |
--------------------------------------------------------------------------------
/migrations/versions/47e23df5a7ed_add_testgroup_result.py:
--------------------------------------------------------------------------------
1 | """Add TestGroup.result
2 |
3 | Revision ID: 47e23df5a7ed
4 | Revises: 520eba1ce36e
5 | Create Date: 2013-11-05 14:01:08.310862
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '47e23df5a7ed'
11 | down_revision = '520eba1ce36e'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('testgroup', sa.Column('result', sa.Enum(), nullable=True))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('testgroup', 'result')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/static/vendor/react/react-dom.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ReactDOM v15.0.2
3 | *
4 | * Copyright 2013-present, Facebook, Inc.
5 | * All rights reserved.
6 | *
7 | * This source code is licensed under the BSD-style license found in the
8 | * LICENSE file in the root directory of this source tree. An additional grant
9 | * of patent rights can be found in the PATENTS file in the same directory.
10 | *
11 | */
12 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e(require("react"));else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;f="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,f.ReactDOM=e(f.React)}}(function(e){return e.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED});
--------------------------------------------------------------------------------
/migrations/versions/3cf79ad9bde_add_snapshot_plan_id_to_plan.py:
--------------------------------------------------------------------------------
1 | """add_snapshot_plan_id_to_plan
2 |
3 | Revision ID: 3cf79ad9bde
4 | Revises: 26fa678dcc64
5 | Create Date: 2015-07-08 10:24:29.696113
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3cf79ad9bde'
11 | down_revision = '26fa678dcc64'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('plan', sa.Column('snapshot_plan_id', sa.GUID(), nullable=True))
19 | op.create_foreign_key('plan_snapshot_plan_id_fkey', 'plan', 'plan', ['snapshot_plan_id'], ['id'], ondelete='SET NULL')
20 |
21 |
22 | def downgrade():
23 | op.drop_constraint('plan_snapshot_plan_id_fkey', 'plan')
24 | op.drop_column('plan', 'snapshot_plan_id')
25 |
--------------------------------------------------------------------------------
/tests/changes/api/test_project_source_build_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class ProjectSourceBuildIndexTest(APITestCase):
5 | def test_simple(self):
6 | project = self.create_project()
7 | source = self.create_source(project)
8 | build1 = self.create_build(project, source=source)
9 | build2 = self.create_build(project, source=source)
10 | path = '/api/0/projects/{0}/sources/{1}/builds/'.format(
11 | project.id.hex, source.id.hex)
12 |
13 | resp = self.client.get(path)
14 | assert resp.status_code == 200
15 | data = self.unserialize(resp)
16 | assert len(data) == 2
17 | assert data[0]['id'] == build2.id.hex
18 | assert data[1]['id'] == build1.id.hex
19 |
--------------------------------------------------------------------------------
/migrations/versions/14491da59392_add_build_revision_s.py:
--------------------------------------------------------------------------------
1 | """Add Build.revision-sha
2 |
3 | Revision ID: 14491da59392
4 | Revises: 32274e97552
5 | Create Date: 2013-10-29 11:43:47.367799
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '14491da59392'
11 | down_revision = '32274e97552'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('build', sa.Column('revision_sha', sa.String(length=40), nullable=True))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('build', 'revision_sha')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/migrations/versions/fd1a52fe89f_add_various_test_ind.py:
--------------------------------------------------------------------------------
1 | """Add various TestCase indexes
2 |
3 | Revision ID: fd1a52fe89f
4 | Revises: 5177cfff57d7
5 | Create Date: 2013-11-04 17:03:03.005904
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = 'fd1a52fe89f'
11 | down_revision = '5177cfff57d7'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | # TestCase
18 | op.create_index('idx_test_project_id', 'test', ['project_id'])
19 | op.create_index('idx_test_suite_id', 'test', ['suite_id'])
20 | op.create_unique_constraint('unq_test_key', 'test', ['build_id', 'suite_id', 'label_sha'])
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | pass
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/changes/api/task_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from flask_restful.reqparse import RequestParser
4 |
5 | from changes.api.base import APIView
6 | from changes.models.task import Task
7 |
8 | import uuid
9 |
10 |
11 | class TaskIndexAPIView(APIView):
12 | get_parser = RequestParser()
13 |
14 | # If specified, the results will be limited to Tasks associated with this object id.
15 | get_parser.add_argument('object_id', type=uuid.UUID, default=None)
16 |
17 | def get(self):
18 | args = self.get_parser.parse_args()
19 |
20 | queryset = Task.query.order_by(Task.date_created.desc())
21 | if args.object_id:
22 | queryset = queryset.filter(Task.task_id == args.object_id)
23 |
24 | return self.paginate(queryset)
25 |
--------------------------------------------------------------------------------
/tests/changes/api/test_build_message_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class BuildMessageIndexTestCase(APITestCase):
5 | path = '/api/0/builds/{build_id}/messages/'
6 |
7 | def test_correct(self):
8 | build = self.create_build(self.create_project())
9 | message1 = self.create_build_message(build, text="Test message")
10 | message2 = self.create_build_message(build, text="Test message")
11 | message3 = self.create_build_message(build, text="Test message")
12 |
13 | resp = self.client.get(self.path.format(build_id=build.id.hex))
14 | assert resp.status_code == 200
15 | data = self.unserialize(resp)
16 |
17 | assert [m['id'] for m in data] == [message1.id.hex, message2.id.hex, message3.id.hex]
18 |
--------------------------------------------------------------------------------
/changes/api/build_mark_seen.py:
--------------------------------------------------------------------------------
1 | from changes.api.auth import get_current_user
2 | from changes.api.base import APIView
3 | from changes.db.utils import try_create
4 | from changes.models.build import Build
5 | from changes.models.buildseen import BuildSeen
6 |
7 |
8 | class BuildMarkSeenAPIView(APIView):
9 | def post(self, build_id):
10 | build = Build.query.get(build_id)
11 | if build is None:
12 | return '', 404
13 |
14 | user = get_current_user()
15 | if user is None:
16 | # don't do anything if they aren't logged in
17 | return self.respond({})
18 |
19 | try_create(BuildSeen, where={
20 | 'build_id': build.id,
21 | 'user_id': user.id,
22 | })
23 |
24 | return self.respond({})
25 |
--------------------------------------------------------------------------------
/static/vendor/react/react-dom-server.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ReactDOMServer v15.0.2
3 | *
4 | * Copyright 2013-present, Facebook, Inc.
5 | * All rights reserved.
6 | *
7 | * This source code is licensed under the BSD-style license found in the
8 | * LICENSE file in the root directory of this source tree. An additional grant
9 | * of patent rights can be found in the PATENTS file in the same directory.
10 | *
11 | */
12 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e(require("react"));else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;f="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,f.ReactDOMServer=e(f.React)}}(function(e){return e.__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED});
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_buildmessage.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from changes.api.serializer import serialize
4 | from changes.testutils.cases import TestCase
5 |
6 |
7 | class BuildMessageTestCase(TestCase):
8 |
9 | def test_correct(self):
10 | build = self.create_build(self.create_project())
11 | message = self.create_build_message(
12 | build,
13 | text="Test message",
14 | date_created=datetime(2013, 9, 19, 22, 15, 22),
15 | )
16 |
17 | result = serialize(message)
18 | assert result['id'] == message.id.hex
19 | assert result['build']['id'] == message.build_id.hex
20 | assert result['text'] == message.text
21 | assert result['dateCreated'] == '2013-09-19T22:15:22'
22 |
--------------------------------------------------------------------------------
/migrations/versions/4640ecd97c82_add_testgroup_num_le.py:
--------------------------------------------------------------------------------
1 | """Add TestGroup.num_leaves
2 |
3 | Revision ID: 4640ecd97c82
4 | Revises: 48a922151dd4
5 | Create Date: 2013-11-08 13:11:18.802332
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4640ecd97c82'
11 | down_revision = '48a922151dd4'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('testgroup', sa.Column('num_leaves', sa.Integer(), nullable=False, server_default='0'))
20 | ### end Alembic commands ###
21 |
22 |
23 | def downgrade():
24 | ### commands auto generated by Alembic - please adjust! ###
25 | op.drop_column('testgroup', 'num_leaves')
26 | ### end Alembic commands ###
27 |
--------------------------------------------------------------------------------
/tests/changes/api/test_task_index.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from changes.testutils import APITestCase
4 |
5 |
6 | class TaskIndexTest(APITestCase):
7 | def test_simple(self):
8 | task_1 = self.create_task(
9 | task_name='example',
10 | date_created=datetime(2013, 9, 19, 22, 15, 24),
11 | )
12 | task_2 = self.create_task(
13 | task_name='example',
14 | date_created=datetime(2013, 9, 20, 22, 15, 24),
15 | )
16 |
17 | path = '/api/0/tasks/'
18 |
19 | resp = self.client.get(path)
20 | assert resp.status_code == 200
21 | data = self.unserialize(resp)
22 | assert len(data) == 2
23 | assert data[0]['id'] == task_2.id.hex
24 | assert data[1]['id'] == task_1.id.hex
25 |
--------------------------------------------------------------------------------
/migrations/versions/139e272152de_index_jobphase.py:
--------------------------------------------------------------------------------
1 | """Index JobPhase
2 |
3 | Revision ID: 139e272152de
4 | Revises: 2f4637448764
5 | Create Date: 2014-01-02 22:03:22.957636
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '139e272152de'
11 | down_revision = '2f4637448764'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_jobphase_job_id', 'jobphase', ['job_id'])
18 | op.create_index('idx_jobphase_project_id', 'jobphase', ['project_id'])
19 | op.create_index('idx_jobphase_repository_id', 'jobphase', ['repository_id'])
20 |
21 |
22 | def downgrade():
23 | op.drop_index('idx_jobphase_job_id', 'jobphase')
24 | op.drop_index('idx_jobphase_project_id', 'jobphase')
25 | op.drop_index('idx_jobphase_repository_id', 'jobphase')
26 |
--------------------------------------------------------------------------------
/static/vendor/classnames/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "classnames",
3 | "version": "2.2.3",
4 | "description": "A simple utility for conditionally joining classNames together",
5 | "main": [
6 | "index.js",
7 | "bind.js",
8 | "dedupe.js"
9 | ],
10 | "homepage": "https://github.com/JedWatson/classnames",
11 | "authors": [
12 | "Jed Watson"
13 | ],
14 | "moduleType": [
15 | "amd",
16 | "globals",
17 | "node"
18 | ],
19 | "keywords": [
20 | "react",
21 | "css",
22 | "classes",
23 | "classname",
24 | "classnames",
25 | "util",
26 | "utility"
27 | ],
28 | "license": "MIT",
29 | "ignore": [
30 | ".editorconfig",
31 | ".gitignore",
32 | "gulpfile.js",
33 | "package.json",
34 | "node_modules",
35 | "tests.js"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/migrations/versions/4d5d239d53b4_set_on_delete_cascad.py:
--------------------------------------------------------------------------------
1 | """Set ON DELETE CASCADE on TestSuite.*
2 |
3 | Revision ID: 4d5d239d53b4
4 | Revises: 501983249c94
5 | Create Date: 2013-12-23 16:14:08.812850
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4d5d239d53b4'
11 | down_revision = '501983249c94'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_constraint('testsuite_project_id_fkey', 'testsuite')
18 | op.create_foreign_key('testsuite_project_id_fkey', 'testsuite', 'project', ['project_id'], ['id'], ondelete='CASCADE')
19 |
20 | op.drop_constraint('testsuite_build_id_fkey', 'testsuite')
21 | op.create_foreign_key('testsuite_build_id_fkey', 'testsuite', 'build', ['build_id'], ['id'], ondelete='CASCADE')
22 |
23 |
24 | def downgrade():
25 | pass
26 |
--------------------------------------------------------------------------------
/changes/lib/build_lib.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import literal
2 | from changes.config import db
3 | from changes.models.build import Build # NOQA
4 | from changes.models.jobplan import JobPlan
5 | from changes.models.option import ItemOption
6 |
7 |
8 | def contains_autogenerated_plan(build):
9 | """Returns true if any of the jobs in this build was created with an
10 | autogenerated jobplan.
11 | """
12 | # type: (Build)->bool
13 | contains = db.session.query(literal(True)).filter(
14 | ItemOption.query.join(
15 | JobPlan, JobPlan.plan_id == ItemOption.item_id,
16 | ).filter(
17 | ItemOption.name == 'bazel.autogenerate',
18 | ItemOption.value == '1',
19 | JobPlan.build_id == build.id,
20 | ).exists()
21 | ).scalar()
22 | return bool(contains)
23 |
--------------------------------------------------------------------------------
/static/vendor/requirejs-babel/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "requirejs-babel",
3 | "version": "0.0.6",
4 | "authors": [
5 | "Michael "
6 | ],
7 | "description": "An AMD loader plugin for Babel",
8 | "main": "es6.js",
9 | "moduleType": [
10 | "amd"
11 | ],
12 | "keywords": [
13 | "es6",
14 | "esnext",
15 | "babel"
16 | ],
17 | "license": "MIT",
18 | "ignore": [
19 | "demo",
20 | "package.json"
21 | ],
22 | "homepage": "https://github.com/mikach/requirejs-babel",
23 | "_release": "0.0.6",
24 | "_resolution": {
25 | "type": "version",
26 | "tag": "0.0.6",
27 | "commit": "f7b24d4aea0c9003ecef3560da771133e6c1755b"
28 | },
29 | "_source": "https://github.com/mikach/requirejs-babel.git",
30 | "_target": "0.0.6",
31 | "_originalSource": "requirejs-babel"
32 | }
--------------------------------------------------------------------------------
/.arclint:
--------------------------------------------------------------------------------
1 | {
2 | "linters": {
3 | "json": {
4 | "type": "json",
5 | "include": [
6 | "(^\\.arcconfig$)",
7 | "(^\\.arclint$)",
8 | "(\\.json$)"
9 | ]
10 | },
11 | "spelling": {
12 | "type": "spelling"
13 | },
14 | "text": {
15 | "type": "text",
16 | "text.max-line-length": 1000
17 | },
18 | "python": {
19 | "type": "flake8",
20 | "include": [
21 | "(^tests/.*\\.py$)",
22 | "(^changes/.*\\.py$)"
23 | ]
24 | },
25 | "python-linters": {
26 | "type": "script-and-regex",
27 | "script-and-regex.script": "bin/lint",
28 | "script-and-regex.regex": "/^(?Padvice|warning|error):(?P\\d+) (?P.*)$/m",
29 | "include": [
30 | "(^.*\\.py$)"
31 | ]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ci/mypy-run:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eux
2 |
3 | # Allow the path to mypy to be specified in the MYPY environment variable, but default to "mypy".
4 | : ${MYPY=mypy}
5 |
6 | # Any paths we need to include in typechecking that are not automatically found (that is, that
7 | # have no '# type:' annotation)
8 | EXTRA_FILES=""
9 |
10 | # Any files with type annotations that should be excluded from typechecking. This is a regular
11 | # expression matched against the filenames.
12 | EXCLUDE=""
13 |
14 | # Find all Python files that are not in the exclude list and which have a '# type:' annotation.
15 | FILES=`find . -type f -name \*.py -print0 \
16 | | xargs -0 grep -ls '# type:'`
17 |
18 | if [ -n "$EXCLUDE" ]; then
19 | FILES=`echo "$FILES" | egrep -v "$EXCLUDE"`
20 | fi
21 |
22 | ci/run_mypy.py $MYPY --silent-imports --py2 $FILES $EXTRA_FILES
23 |
--------------------------------------------------------------------------------
/static/vendor/moment/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moment",
3 | "version": "2.1.0",
4 | "scripts": [
5 | "moment.js"
6 | ],
7 | "main": "moment.js",
8 | "description": "Parse, validate, manipulate, and display dates in javascript.",
9 | "ignore": [
10 | ".gitignore",
11 | ".travis.yml",
12 | "composer.json",
13 | "CONTRIBUTING.md",
14 | "ender.js",
15 | "Gruntfile.js",
16 | "package.js",
17 | "package.json",
18 | "test",
19 | "tasks"
20 | ],
21 | "homepage": "https://github.com/moment/moment",
22 | "_release": "2.1.0",
23 | "_resolution": {
24 | "type": "version",
25 | "tag": "2.1.0",
26 | "commit": "05028e20e526b5f38dde3748833e98d7c8e846e8"
27 | },
28 | "_source": "https://github.com/moment/moment.git",
29 | "_target": "~2.1.0",
30 | "_originalSource": "moment"
31 | }
--------------------------------------------------------------------------------
/migrations/versions/306fefe51dc6_set_on_delete_cascad.py:
--------------------------------------------------------------------------------
1 | """Set ON DELETE CASCADE on Project Plan m2m
2 |
3 | Revision ID: 306fefe51dc6
4 | Revises: 37e782a55ca6
5 | Create Date: 2013-12-23 16:44:37.119363
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '306fefe51dc6'
11 | down_revision = '37e782a55ca6'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_constraint('project_plan_plan_id_fkey', 'project_plan')
18 | op.create_foreign_key('project_plan_plan_id_fkey', 'project_plan', 'plan', ['plan_id'], ['id'], ondelete='CASCADE')
19 |
20 | op.drop_constraint('project_plan_project_id_fkey', 'project_plan')
21 | op.create_foreign_key('project_plan_project_id_fkey', 'project_plan', 'project', ['project_id'], ['id'], ondelete='CASCADE')
22 |
23 |
24 | def downgrade():
25 | pass
26 |
--------------------------------------------------------------------------------
/migrations/versions/380d20771802_project_plan.py:
--------------------------------------------------------------------------------
1 | """Project <=> Plan
2 |
3 | Revision ID: 380d20771802
4 | Revises: 1d806848a73f
5 | Create Date: 2013-12-16 14:38:39.941404
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '380d20771802'
11 | down_revision = '1d806848a73f'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_table(
19 | 'project_plan',
20 | sa.Column('project_id', sa.GUID(), nullable=False),
21 | sa.Column('plan_id', sa.GUID(), nullable=False),
22 | sa.ForeignKeyConstraint(['plan_id'], ['plan.id'], ),
23 | sa.ForeignKeyConstraint(['project_id'], ['project.id'], ),
24 | sa.PrimaryKeyConstraint('project_id', 'plan_id')
25 | )
26 |
27 |
28 | def downgrade():
29 | op.drop_table('project_plan')
30 |
--------------------------------------------------------------------------------
/migrations/versions/3be107806e62_make_testartifact_cascade_on_test_delete.py:
--------------------------------------------------------------------------------
1 | """Make testartifact cascade on test delete
2 |
3 | Revision ID: 3be107806e62
4 | Revises: 3bf1066f4935
5 | Create Date: 2016-07-06 18:42:33.893405
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '3be107806e62'
11 | down_revision = '3bf1066f4935'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.drop_constraint('testartifact_test_id_fkey', 'testartifact')
19 | op.create_foreign_key('testartifact_test_id_fkey', 'testartifact', 'test', ['test_id'], ['id'], ondelete='CASCADE')
20 |
21 |
22 | def downgrade():
23 | op.drop_constraint('testartifact_test_id_fkey', 'testartifact')
24 | op.create_foreign_key('testartifact_test_id_fkey', 'testartifact', 'test', ['test_id'], ['id'])
25 |
--------------------------------------------------------------------------------
/migrations/versions/5677ef75c712_add_user.py:
--------------------------------------------------------------------------------
1 | """Add User
2 |
3 | Revision ID: 5677ef75c712
4 | Revises: 45e1cfacfc7d
5 | Create Date: 2014-01-15 11:06:25.217408
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5677ef75c712'
11 | down_revision = '45e1cfacfc7d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_table(
19 | 'user',
20 | sa.Column('id', sa.GUID(), nullable=False),
21 | sa.Column('email', sa.String(length=128), nullable=False),
22 | sa.Column('is_admin', sa.Boolean(), nullable=False),
23 | sa.Column('date_created', sa.DateTime(), nullable=True),
24 | sa.PrimaryKeyConstraint('id'),
25 | sa.UniqueConstraint('email')
26 | )
27 |
28 |
29 | def downgrade():
30 | op.drop_table('user')
31 |
--------------------------------------------------------------------------------
/migrations/versions/37e782a55ca6_set_on_delete_cascad.py:
--------------------------------------------------------------------------------
1 | """Set ON DELETE CASCADE on Project*
2 |
3 | Revision ID: 37e782a55ca6
4 | Revises: 218e84b10a0e
5 | Create Date: 2013-12-23 16:41:27.462599
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '37e782a55ca6'
11 | down_revision = '218e84b10a0e'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_constraint('project_repository_id_fkey', 'project')
18 | op.create_foreign_key('project_repository_id_fkey', 'project', 'repository', ['repository_id'], ['id'], ondelete='RESTRICT')
19 |
20 | op.drop_constraint('projectoption_project_id_fkey', 'projectoption')
21 | op.create_foreign_key('projectoption_project_id_fkey', 'projectoption', 'project', ['project_id'], ['id'], ondelete='CASCADE')
22 |
23 |
24 | def downgrade():
25 | pass
26 |
--------------------------------------------------------------------------------
/migrations/versions/524b3c27203b_remove_job_author_id.py:
--------------------------------------------------------------------------------
1 | """Remove Job.{author_id,repository_id,patch_id,revision_sha,message,target,cause,parent_id}
2 |
3 | Revision ID: 524b3c27203b
4 | Revises: 3042d0ca43bf
5 | Create Date: 2014-01-05 16:36:43.476520
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '524b3c27203b'
11 | down_revision = '3042d0ca43bf'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.drop_column('job', 'revision_sha')
18 | op.drop_column('job', 'patch_id')
19 | op.drop_column('job', 'author_id')
20 | op.drop_column('job', 'repository_id')
21 | op.drop_column('job', 'message')
22 | op.drop_column('job', 'target')
23 | op.drop_column('job', 'cause')
24 | op.drop_column('job', 'parent_id')
25 |
26 |
27 | def downgrade():
28 | raise NotImplementedError
29 |
--------------------------------------------------------------------------------
/static/vendor/react-bootstrap/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-bootstrap",
3 | "version": "0.29.3",
4 | "homepage": "http://react-bootstrap.github.io/",
5 | "author": "Stephen J. Collings ",
6 | "license": "MIT",
7 | "main": [
8 | "react-bootstrap.js"
9 | ],
10 | "keywords": [
11 | "react",
12 | "ecosystem-react",
13 | "react-component",
14 | "bootstrap"
15 | ],
16 | "ignore": [
17 | "**/.*"
18 | ],
19 | "dependencies": {
20 | "react": ">=0.14.0"
21 | },
22 | "_release": "0.29.3",
23 | "_resolution": {
24 | "type": "version",
25 | "tag": "v0.29.3",
26 | "commit": "9e09d0a906e396d754761a7343b6920b820df3aa"
27 | },
28 | "_source": "https://github.com/react-bootstrap/react-bootstrap-bower.git",
29 | "_target": "~0.29",
30 | "_originalSource": "react-bootstrap"
31 | }
--------------------------------------------------------------------------------
/changes/utils/locking.py:
--------------------------------------------------------------------------------
1 | from flask import current_app
2 | from functools import wraps
3 | from hashlib import md5
4 |
5 | from changes.ext.redis import UnableToGetLock
6 | from changes.config import redis
7 |
8 |
9 | def lock(func):
10 | @wraps(func)
11 | def wrapped(**kwargs):
12 | key = '{0}:{1}:{2}'.format(
13 | func.__module__,
14 | func.__name__,
15 | md5(
16 | '&'.join('{0}={1}'.format(k, repr(v))
17 | for k, v in sorted(kwargs.iteritems()))
18 | ).hexdigest()
19 | )
20 | try:
21 | with redis.lock(key, expire=300, nowait=True):
22 | return func(**kwargs)
23 | except UnableToGetLock:
24 | current_app.logger.warn('Unable to get lock for %s', key)
25 | raise
26 |
27 | return wrapped
28 |
--------------------------------------------------------------------------------
/migrations/versions/4768ba7627ac_adjust_command_schema.py:
--------------------------------------------------------------------------------
1 | """Adjust Command schema
2 |
3 | Revision ID: 4768ba7627ac
4 | Revises: 3f6a69c14037
5 | Create Date: 2014-07-17 14:32:47.347947
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4768ba7627ac'
11 | down_revision = '3f6a69c14037'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('command', sa.Column('order', sa.Integer(), nullable=False,
19 | server_default='0'))
20 | op.alter_column('command', 'return_code', existing_type=sa.INTEGER(),
21 | nullable=True)
22 |
23 |
24 | def downgrade():
25 | op.alter_column('command', 'return_code', existing_type=sa.INTEGER(),
26 | nullable=False)
27 | op.drop_column('command', 'order')
28 |
--------------------------------------------------------------------------------
/migrations/versions/5026dbcee21d_test_build_id_job_id.py:
--------------------------------------------------------------------------------
1 | """Test*.build_id => job_id
2 |
3 | Revision ID: 5026dbcee21d
4 | Revises: 42e9d35a4098
5 | Create Date: 2013-12-25 23:50:12.762986
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '5026dbcee21d'
11 | down_revision = '42e9d35a4098'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.execute('ALTER TABLE test RENAME COLUMN build_id TO job_id')
18 | op.execute('ALTER TABLE testgroup RENAME COLUMN build_id TO job_id')
19 | op.execute('ALTER TABLE testsuite RENAME COLUMN build_id TO job_id')
20 |
21 |
22 | def downgrade():
23 | op.execute('ALTER TABLE test RENAME COLUMN job_id TO build_id')
24 | op.execute('ALTER TABLE testgroup RENAME COLUMN job_id TO build_id')
25 | op.execute('ALTER TABLE testsuite RENAME COLUMN job_id TO build_id')
26 |
--------------------------------------------------------------------------------
/static/vendor/underscore/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "underscore",
3 | "version": "1.8.3",
4 | "main": "underscore.js",
5 | "keywords": [
6 | "util",
7 | "functional",
8 | "server",
9 | "client",
10 | "browser"
11 | ],
12 | "ignore": [
13 | "docs",
14 | "test",
15 | "*.yml",
16 | "CNAME",
17 | "index.html",
18 | "favicon.ico",
19 | "CONTRIBUTING.md",
20 | ".*",
21 | "component.json",
22 | "package.json",
23 | "karma.*"
24 | ],
25 | "homepage": "https://github.com/jashkenas/underscore",
26 | "_release": "1.8.3",
27 | "_resolution": {
28 | "type": "version",
29 | "tag": "1.8.3",
30 | "commit": "e4743ab712b8ab42ad4ccb48b155034d02394e4d"
31 | },
32 | "_source": "https://github.com/jashkenas/underscore.git",
33 | "_target": "~1.8.3",
34 | "_originalSource": "underscore"
35 | }
--------------------------------------------------------------------------------
/migrations/versions/19910015c867_add_systemoption.py:
--------------------------------------------------------------------------------
1 | """Add SystemOption
2 |
3 | Revision ID: 19910015c867
4 | Revises: 2b7153fe25af
5 | Create Date: 2014-07-11 15:48:26.276042
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '19910015c867'
11 | down_revision = '2b7153fe25af'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_table(
19 | 'systemoption',
20 | sa.Column('id', sa.GUID(), nullable=False),
21 | sa.Column('name', sa.String(length=64), nullable=False),
22 | sa.Column('value', sa.Text(), nullable=False),
23 | sa.Column('date_created', sa.DateTime(), nullable=False),
24 | sa.PrimaryKeyConstraint('id'),
25 | sa.UniqueConstraint('name')
26 | )
27 |
28 |
29 | def downgrade():
30 | op.drop_table('systemoption')
31 |
--------------------------------------------------------------------------------
/tests/changes/api/validators/test_datetime.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | import pytest
4 | from changes.api.validators.datetime import ISODatetime
5 |
6 |
7 | def test_isodatetime_valid():
8 | validator = ISODatetime()
9 | assert validator('2016-09-01T19:46:18.9Z') == datetime(year=2016,
10 | month=9,
11 | day=1,
12 | hour=19,
13 | minute=46,
14 | second=18,
15 | microsecond=900000)
16 |
17 |
18 | def test_isodatetime_invalid():
19 | with pytest.raises(ValueError):
20 | ISODatetime()('invalid')
21 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/change.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.change import Change
3 | from changes.utils.http import build_web_uri
4 |
5 |
6 | @register(Change)
7 | class ChangeCrumbler(Crumbler):
8 | def crumble(self, instance, attrs):
9 | result = {
10 | 'id': instance.id.hex,
11 | 'name': instance.label,
12 | 'project': instance.project,
13 | 'author': instance.author,
14 | 'message': instance.message,
15 | 'link': build_web_uri('/changes/%s/' % (instance.id.hex,)),
16 | 'dateCreated': instance.date_created.isoformat(),
17 | 'dateModified': instance.date_modified.isoformat(),
18 | }
19 | if hasattr(instance, 'last_job'):
20 | result['lastBuild'] = instance.last_job
21 | return result
22 |
--------------------------------------------------------------------------------
/changes/artifacts/dummylogfile.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from .base import ArtifactHandler
4 |
5 |
6 | class DummyLogFileHandler(ArtifactHandler):
7 | """
8 | Dummy Artifact handler for log files.
9 | We only fetch artifacts from Jenkins masters if (according to the registered
10 | ArtifactHandlers) we are interested in the contents.
11 | For projects using ArtifactStore this works out fine, but for others it means
12 | that they'll only have reliable access to the contents of artifacts that are processed.
13 |
14 | This handler exists to signal our interest in the contents of *.log files in those cases
15 | where they won't be available otherwise.
16 | """
17 | FILENAMES = ('*.log',)
18 |
19 | def process(self, fp, artifact):
20 | # We don't need to do anything with the file contents.
21 | pass
22 |
--------------------------------------------------------------------------------
/migrations/versions/11a7a7f2652f_add_itemstat.py:
--------------------------------------------------------------------------------
1 | """Add ItemStat
2 |
3 | Revision ID: 11a7a7f2652f
4 | Revises: 4276d58dd1e6
5 | Create Date: 2014-03-13 13:33:32.840399
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '11a7a7f2652f'
11 | down_revision = '4276d58dd1e6'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_table(
19 | 'itemstat',
20 | sa.Column('id', sa.GUID(), nullable=False),
21 | sa.Column('item_id', sa.GUID(), nullable=False),
22 | sa.Column('name', sa.String(length=64), nullable=False),
23 | sa.Column('value', sa.Integer(), nullable=False),
24 | sa.PrimaryKeyConstraint('id'),
25 | sa.UniqueConstraint('item_id', 'name', name='unq_itemstat_name')
26 | )
27 |
28 |
29 | def downgrade():
30 | op.drop_table('itemstat')
31 |
--------------------------------------------------------------------------------
/webapp/pages/examples_page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Examples from 'es6!display/examples';
4 | import { ChangesPage } from 'es6!display/page_chrome';
5 |
6 | /*
7 | * Renders example uses of the reusable display tags in display/
8 | */
9 | var DisplayExamplesPage = React.createClass({
10 |
11 | getInitialTitle: function() {
12 | return "Examples";
13 | },
14 |
15 | render: function() {
16 | var removeHref = URI(window.location.href)
17 | .addQuery('disable_custom', 1)
18 | .toString();
19 |
20 | var removeLink =
21 | Render without any custom JS/CSS
22 | ;
23 |
24 | return
25 |
26 | {removeLink}
27 |
28 | {Examples.render()}
29 | ;
30 | }
31 | });
32 |
33 | export default DisplayExamplesPage;
34 |
--------------------------------------------------------------------------------
/tests/changes/api/test_job_artifact_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class JobArtifactIndexTest(APITestCase):
5 | def test_simple(self):
6 | project = self.create_project()
7 | build = self.create_build(project)
8 | job = self.create_job(build)
9 | jobphase = self.create_jobphase(job)
10 | jobstep = self.create_jobstep(jobphase)
11 | artifact_1 = self.create_artifact(jobstep, 'junit.xml')
12 | artifact_2 = self.create_artifact(jobstep, 'coverage.xml')
13 |
14 | path = '/api/0/jobs/{0}/artifacts/'.format(job.id.hex)
15 |
16 | resp = self.client.get(path)
17 | assert resp.status_code == 200
18 | data = self.unserialize(resp)
19 | assert len(data) == 2
20 | assert data[0]['id'] == artifact_2.id.hex
21 | assert data[1]['id'] == artifact_1.id.hex
22 |
--------------------------------------------------------------------------------
/changes/lib/project_lib.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import literal
2 | from changes.config import db
3 | from changes.models.option import ItemOption
4 | from changes.models.plan import Plan, PlanStatus
5 | from changes.models.project import Project # NOQA
6 |
7 |
8 | def contains_active_autogenerated_plan(project):
9 | """Returns true if there are any active autogenerated build plans for
10 | this project.
11 | """
12 | # type: (Project)->bool
13 | contains = db.session.query(literal(True)).filter(
14 | ItemOption.query.join(
15 | Plan, Plan.id == ItemOption.item_id,
16 | ).filter(
17 | ItemOption.name == 'bazel.autogenerate',
18 | ItemOption.value == '1',
19 | Plan.project_id == project.id,
20 | Plan.status == PlanStatus.active,
21 | ).exists()
22 | ).scalar()
23 | return bool(contains)
24 |
--------------------------------------------------------------------------------
/migrations/versions/18c045b75331_add_various_build_in.py:
--------------------------------------------------------------------------------
1 | """Add various Build indexes
2 |
3 | Revision ID: 18c045b75331
4 | Revises: d324e8fc580
5 | Create Date: 2013-11-04 17:14:33.455855
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '18c045b75331'
11 | down_revision = 'd324e8fc580'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_build_project_id', 'build', ['project_id'])
18 | op.create_index('idx_build_repository_id', 'build', ['repository_id'])
19 | op.create_index('idx_build_author_id', 'build', ['author_id'])
20 | op.create_index('idx_build_patch_id', 'build', ['patch_id'])
21 | op.create_index('idx_build_change_id', 'build', ['change_id'])
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | pass
27 | ### end Alembic commands ###
28 |
--------------------------------------------------------------------------------
/changes/api/controller.py:
--------------------------------------------------------------------------------
1 | from flask.signals import got_request_exception
2 | from flask.ext.restful import Api, Resource
3 |
4 |
5 | class APIController(Api):
6 | def handle_error(self, e):
7 | """
8 | Almost identical to Flask-Restful's handle_error, but fixes some minor
9 | issues.
10 |
11 | Specifically, this fixes exceptions so they get propagated correctly
12 | when ``propagate_exceptions`` is set.
13 | """
14 | if not hasattr(e, 'code') and self.app.propagate_exceptions:
15 | got_request_exception.send(self.app, exception=e)
16 | raise
17 |
18 | return super(APIController, self).handle_error(e)
19 |
20 |
21 | class APICatchall(Resource):
22 | def get(self, path):
23 | return {'error': 'Not Found'}, 404
24 |
25 | post = get
26 | put = get
27 | delete = get
28 | patch = get
29 |
--------------------------------------------------------------------------------
/changes/api/task_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.api.base import APIView
4 | from changes.models.task import Task
5 |
6 |
7 | class TaskDetailsAPIView(APIView):
8 | def _collect_children(self, task):
9 | children = Task.query.filter(
10 | Task.parent_id == task.task_id,
11 | )
12 | results = []
13 | for child in children:
14 | child_data = self.serialize(child)
15 | child_data['children'] = self._collect_children(child)
16 | results.append(child_data)
17 |
18 | return results
19 |
20 | def get(self, task_id):
21 | task = Task.query.get(task_id)
22 | if task is None:
23 | return '', 404
24 |
25 | context = self.serialize(task)
26 | context['children'] = self._collect_children(task)
27 |
28 | return self.respond(context)
29 |
--------------------------------------------------------------------------------
/migrations/versions/1b53d33197bf_add_build_cause_and_.py:
--------------------------------------------------------------------------------
1 | """Add Build.cause and Buld.parent
2 |
3 | Revision ID: 1b53d33197bf
4 | Revises: 2b8459f1e2d6
5 | Create Date: 2013-10-25 14:33:55.401260
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '1b53d33197bf'
11 | down_revision = '2b8459f1e2d6'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('build', sa.Column('cause', sa.Enum(), server_default='0', nullable=False))
20 | op.add_column('build', sa.Column('parent', sa.GUID(), nullable=True))
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.drop_column('build', 'parent')
27 | op.drop_column('build', 'cause')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/migrations/versions/21c9439330f_add_cascade_to_filec.py:
--------------------------------------------------------------------------------
1 | """Add cascade to FileCoverage
2 |
3 | Revision ID: 21c9439330f
4 | Revises: 1f5caa34d9c2
5 | Create Date: 2014-04-01 15:29:26.765288
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '21c9439330f'
11 | down_revision = '1f5caa34d9c2'
12 |
13 | from alembic import op
14 |
15 |
16 | def upgrade():
17 | op.create_index('idx_filecoverage_job_id', 'filecoverage', ['job_id'])
18 | op.create_index('idx_filecoverage_project_id', 'filecoverage', ['project_id'])
19 |
20 | op.drop_constraint('filecoverage_build_id_fkey', 'filecoverage')
21 | op.create_foreign_key('filecoverage_job_id_fkey', 'filecoverage', 'job', ['job_id'], ['id'], ondelete='CASCADE')
22 |
23 | op.create_foreign_key('filecoverage_project_id_fkey', 'filecoverage', 'project', ['project_id'], ['id'], ondelete='CASCADE')
24 |
25 |
26 | def downgrade():
27 | pass
28 |
--------------------------------------------------------------------------------
/changes/buildsteps/dummy.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.buildsteps.base import BuildStep
4 | from changes.config import db
5 | from changes.constants import Status, Result
6 |
7 |
8 | class DummyBuildStep(BuildStep):
9 | def get_label(self):
10 | return 'do nothing'
11 |
12 | def execute(self, job):
13 | job.status = Status.finished
14 | job.result = Result.aborted
15 | db.session.add(job)
16 |
17 | def update(self, job):
18 | job.status = Status.finished
19 | job.result = Result.aborted
20 | db.session.add(job)
21 |
22 | def update_step(self, step):
23 | step.status = Status.finished
24 | step.result = Result.aborted
25 | db.session.add(step)
26 |
27 | def cancel_step(self, step):
28 | step.status = Status.finished
29 | step.result = Result.aborted
30 | db.session.add(step)
31 |
--------------------------------------------------------------------------------
/migrations/versions/2d8acbec66df_add_build_target.py:
--------------------------------------------------------------------------------
1 | """Add Build.target
2 |
3 | Revision ID: 2d8acbec66df
4 | Revises: 5aad6f742b77
5 | Create Date: 2013-11-11 17:07:11.624071
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2d8acbec66df'
11 | down_revision = '5aad6f742b77'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('build', sa.Column('target', sa.String(length=128), nullable=True))
20 | op.drop_column('build', u'parent_revision_sha')
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.add_column('build', sa.Column(u'parent_revision_sha', sa.VARCHAR(length=40), nullable=True))
27 | op.drop_column('build', 'target')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_bazeltargetmessage.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from changes.api.serializer import serialize
4 | from changes.testutils.cases import TestCase
5 |
6 |
7 | class BazelTargetMessageTestCase(TestCase):
8 |
9 | def test_correct(self):
10 | project = self.create_project()
11 | build = self.create_build(project)
12 | job = self.create_job(build)
13 | target = self.create_target(job)
14 | message = self.create_target_message(
15 | target,
16 | text="Test message",
17 | date_created=datetime(2013, 9, 19, 22, 15, 22),
18 | )
19 |
20 | result = serialize(message)
21 | assert result['id'] == message.id.hex
22 | assert result['target']['id'] == message.target_id.hex
23 | assert result['text'] == message.text
24 | assert result['dateCreated'] == '2013-09-19T22:15:22'
25 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/jobphase.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.jobphase import JobPhase
3 |
4 |
5 | @register(JobPhase)
6 | class JobPhaseCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'name': instance.label,
11 | 'result': instance.result,
12 | 'status': instance.status,
13 | 'duration': instance.duration,
14 | 'dateCreated': instance.date_created,
15 | 'dateStarted': instance.date_started,
16 | 'dateFinished': instance.date_finished,
17 | }
18 |
19 |
20 | class JobPhaseWithStepsCrumbler(JobPhaseCrumbler):
21 | def crumble(self, instance, attrs):
22 | data = super(JobPhaseWithStepsCrumbler, self).crumble(instance, attrs)
23 | data['steps'] = list(instance.steps)
24 | return data
25 |
--------------------------------------------------------------------------------
/changes/api/validators/author.py:
--------------------------------------------------------------------------------
1 | from changes.models.author import Author
2 | from changes.db.utils import get_or_create
3 |
4 |
5 | class AuthorValidator(object):
6 | def __call__(self, value):
7 | parsed = self.parse(value)
8 | if not parsed:
9 | raise ValueError(value)
10 |
11 | name, email = parsed
12 | author, _ = get_or_create(Author, where={
13 | 'email': email,
14 | }, defaults={
15 | 'name': name,
16 | })
17 | return author
18 |
19 | def parse(self, value):
20 | import re
21 | match = re.match(r'^(.+) <([^>]+)>$', value)
22 |
23 | if not match:
24 | if '@' in value:
25 | name, email = value, value
26 | else:
27 | raise ValueError(value)
28 | else:
29 | name, email = match.group(1), match.group(2)
30 | return name, email
31 |
--------------------------------------------------------------------------------
/changes/listeners/snapshot_build.py:
--------------------------------------------------------------------------------
1 | from changes.config import db
2 | from changes.constants import Cause, Result
3 | from changes.models.build import Build
4 | from changes.models.snapshot import SnapshotStatus
5 | from changes.utils.locking import lock
6 |
7 |
8 | @lock
9 | def build_finished_handler(build_id, **kwargs):
10 | build = Build.query.get(build_id)
11 |
12 | # only handle snapshot builds
13 | if build is None or build.cause != Cause.snapshot:
14 | return
15 |
16 | # If the snapshot build did not pass then we guarantee that
17 | # the snapshot is either invalidated or is failed.
18 | if build.result != Result.passed and build.snapshot is not None:
19 | if build.snapshot.status == SnapshotStatus.active:
20 | build.snapshot.status = SnapshotStatus.invalidated
21 | else:
22 | build.snapshot.status = SnapshotStatus.failed
23 | db.session.commit()
24 |
--------------------------------------------------------------------------------
/migrations/versions/4d302aa44bc8_add_additional_revis.py:
--------------------------------------------------------------------------------
1 | """Add additional revision data
2 |
3 | Revision ID: 4d302aa44bc8
4 | Revises: 215db24a630a
5 | Create Date: 2013-11-26 16:20:59.454360
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '4d302aa44bc8'
11 | down_revision = '215db24a630a'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('revision', sa.Column('committer_id', sa.GUID(), nullable=True))
20 | op.add_column('revision', sa.Column('date_committed', sa.DateTime(), nullable=True))
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.drop_column('revision', 'date_committed')
27 | op.drop_column('revision', 'committer_id')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | **NOTICE: THIS REPO IS NO LONGER UPDATED**
2 |
3 | Changes
4 | -------
5 |
6 | Changes is a build coordinator and reporting solution written in Python.
7 |
8 | The project is primarily built on top of Jenkins, but efforts are underway to
9 | replace the underlying dependency. The current work-in-progress tooling exists
10 | under several additional repositories:
11 |
12 | - https://github.com/dropbox/changes-client
13 | - https://github.com/dropbox/changes-mesos-framework
14 |
15 | .. note:: **Changes is under extremely active and rapid development, and you probably shouldn't use it unless you like broken software.**
16 |
17 |
18 | Screenshot
19 | ==========
20 |
21 | .. image:: https://github.com/dropbox/changes/raw/master/docs/images/example.png
22 |
23 | Getting Started
24 | ===============
25 |
26 | While still fairly rough, you'll want to refer to our `documentation `_.
27 |
--------------------------------------------------------------------------------
/tests/changes/api/test_build_target_message_index.py:
--------------------------------------------------------------------------------
1 | from changes.testutils import APITestCase
2 |
3 |
4 | class BuildTargetMessageIndexTestCase(APITestCase):
5 | path = '/api/0/builds/{build_id}/targets/{target_id}/messages/'
6 |
7 | def test_correct(self):
8 | build = self.create_build(self.create_project())
9 | job = self.create_job(build)
10 | target = self.create_target(job)
11 | message1 = self.create_target_message(target, text="Test message")
12 | message2 = self.create_target_message(target, text="Test message")
13 | message3 = self.create_target_message(target, text="Test message")
14 |
15 | resp = self.client.get(self.path.format(build_id=build.id.hex, target_id=target.id.hex))
16 | assert resp.status_code == 200
17 | data = self.unserialize(resp)
18 |
19 | assert [m['id'] for m in data] == [message1.id.hex, message2.id.hex, message3.id.hex]
20 |
--------------------------------------------------------------------------------
/changes/api/build_test_index_counts.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import contains_eager
2 |
3 | from changes.api.base import APIView
4 | from changes.constants import Result
5 | from changes.models.build import Build
6 | from changes.models.job import Job
7 | from changes.models.test import TestCase
8 |
9 |
10 | class BuildTestIndexCountsAPIView(APIView):
11 |
12 | def get(self, build_id):
13 | build = Build.query.get(build_id)
14 | if build is None:
15 | return '', 404
16 |
17 | test_list = TestCase.query.options(
18 | contains_eager('job')
19 | ).join(
20 | Job, TestCase.job_id == Job.id,
21 | ).filter(
22 | Job.build_id == build.id,
23 | )
24 |
25 | count_dict = {result.name: 0 for result in Result}
26 |
27 | for test in test_list:
28 | count_dict[test.result.name] += 1
29 |
30 | return self.respond(count_dict)
31 |
--------------------------------------------------------------------------------
/changes/api/node_job_index.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from sqlalchemy.orm import joinedload
4 |
5 | from changes.api.base import APIView
6 | from changes.api.serializer.models.job import JobWithBuildCrumbler
7 | from changes.models.job import Job
8 | from changes.models.jobstep import JobStep
9 | from changes.models.node import Node
10 |
11 |
12 | class NodeJobIndexAPIView(APIView):
13 | def get(self, node_id):
14 | node = Node.query.get(node_id)
15 | if node is None:
16 | return '', 404
17 |
18 | jobs = Job.query.join(
19 | JobStep, JobStep.job_id == Job.id,
20 | ).options(
21 | joinedload(Job.build, innerjoin=True),
22 | ).filter(
23 | JobStep.node_id == node.id,
24 | ).order_by(Job.date_created.desc())
25 |
26 | return self.paginate(jobs, serializers={
27 | Job: JobWithBuildCrumbler(),
28 | })
29 |
--------------------------------------------------------------------------------
/migrations/versions/2b1b01d74175_added_adminmessage_table.py:
--------------------------------------------------------------------------------
1 | """Added adminmessage table
2 |
3 | Revision ID: 2b1b01d74175
4 | Revises: 1cb3db431e29
5 | Create Date: 2015-04-15 22:55:56.097518
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '2b1b01d74175'
11 | down_revision = '1cb3db431e29'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.create_table('adminmessage',
19 | sa.Column('id', sa.GUID(), nullable=False),
20 | sa.Column('user_id', sa.GUID(), nullable=True),
21 | sa.Column('date_created', sa.DateTime(), nullable=True),
22 | sa.Column('message', sa.Text(), nullable=True),
23 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
24 | sa.PrimaryKeyConstraint('id'))
25 |
26 |
27 | def downgrade():
28 | op.drop_table('adminmessage')
29 |
--------------------------------------------------------------------------------
/migrations/versions/32274e97552_build_parent_build_p.py:
--------------------------------------------------------------------------------
1 | """Build.parent -> Build.parent_id
2 |
3 | Revision ID: 32274e97552
4 | Revises: 1b53d33197bf
5 | Create Date: 2013-10-25 15:41:21.120737
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '32274e97552'
11 | down_revision = '1b53d33197bf'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 | from sqlalchemy.dialects import postgresql
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('build', sa.Column('parent_id', sa.GUID(), nullable=True))
20 | op.drop_column('build', u'parent')
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.add_column('build', sa.Column(u'parent', postgresql.UUID(), nullable=True))
27 | op.drop_column('build', 'parent_id')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/migrations/versions/346c011ca77a_optional_parent_revi.py:
--------------------------------------------------------------------------------
1 | """Optional parent_revision_sha
2 |
3 | Revision ID: 346c011ca77a
4 | Revises: 2d8acbec66df
5 | Create Date: 2013-11-11 17:08:40.087545
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '346c011ca77a'
11 | down_revision = '2d8acbec66df'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.alter_column('patch', 'parent_revision_sha',
20 | existing_type=sa.VARCHAR(length=40),
21 | nullable=True)
22 | ### end Alembic commands ###
23 |
24 |
25 | def downgrade():
26 | ### commands auto generated by Alembic - please adjust! ###
27 | op.alter_column('patch', 'parent_revision_sha',
28 | existing_type=sa.VARCHAR(length=40),
29 | nullable=False)
30 | ### end Alembic commands ###
31 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_adminmessage.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from uuid import UUID
3 |
4 | from changes.api.serializer import serialize
5 | from changes.models.adminmessage import AdminMessage
6 | from changes.testutils import TestCase
7 |
8 |
9 | class AdminMessageTest(TestCase):
10 | def test_simple(self):
11 | message = AdminMessage(
12 | id=UUID(hex='33846695b2774b29a71795a009e8168a'),
13 | message='Foo bar',
14 | user=self.create_user(
15 | email='foo@example.com',
16 | ),
17 | date_created=datetime(2013, 9, 19, 22, 15, 22),
18 | )
19 | result = serialize(message)
20 | assert result['id'] == '33846695b2774b29a71795a009e8168a'
21 | assert result['user']['id'] == message.user.id.hex
22 | assert result['message'] == 'Foo bar'
23 | assert result['dateCreated'] == '2013-09-19T22:15:22'
24 |
--------------------------------------------------------------------------------
/changes/buildfailures/testfailure.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from jinja2 import Markup
4 |
5 | from changes.buildfailures.base import BuildFailure
6 | from changes.utils.http import build_web_uri
7 |
8 |
9 | class TestFailure(BuildFailure):
10 | def get_html_label(self, build):
11 | link = build_web_uri('/projects/{0}/builds/{1}/tests/?result=failed'.format(build.project.slug, build.id.hex))
12 |
13 | try:
14 | test_failures = (
15 | s.value for s in build.stats if s.name == 'test_failures'
16 | ).next()
17 | except StopIteration:
18 | return Markup('There were an unknown number of test failures.'.format(
19 | link=link,
20 | ))
21 |
22 | return Markup('There were {count} failing tests.'.format(
23 | link=link,
24 | count=test_failures,
25 | ))
26 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_change.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from uuid import UUID
3 |
4 | from changes.api.serializer import serialize
5 | from changes.models.change import Change
6 | from changes.models.project import Project
7 |
8 |
9 | def test_simple():
10 | change = Change(
11 | id=UUID(hex='33846695b2774b29a71795a009e8168a'),
12 | label='Hello world',
13 | project=Project(slug='test', name='test'),
14 | date_created=datetime(2013, 9, 19, 22, 15, 22),
15 | date_modified=datetime(2013, 9, 19, 22, 15, 23),
16 | )
17 | result = serialize(change)
18 | assert result['name'] == 'Hello world'
19 | assert result['link'] == 'http://example.com/changes/33846695b2774b29a71795a009e8168a/'
20 | assert result['id'] == '33846695b2774b29a71795a009e8168a'
21 | assert result['dateCreated'] == '2013-09-19T22:15:22'
22 | assert result['dateModified'] == '2013-09-19T22:15:23'
23 |
--------------------------------------------------------------------------------
/tests/changes/api/serializer/models/test_logchunk.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from uuid import UUID
3 |
4 | from changes.api.serializer import serialize
5 | from changes.models.log import LogSource, LogChunk
6 |
7 |
8 | def test_simple():
9 | logchunk = LogChunk(
10 | id=UUID(hex='33846695b2774b29a71795a009e8168a'),
11 | source_id=UUID(hex='0b61b8a47ec844918d372d5741187b1c'),
12 | source=LogSource(id=UUID(hex='0b61b8a47ec844918d372d5741187b1c')),
13 | offset=10,
14 | size=7,
15 | text='\x1b[0;36mnotice: foo bar',
16 | date_created=datetime(2013, 9, 19, 22, 15, 22),
17 | )
18 | result = serialize(logchunk)
19 | assert result['id'] == '33846695b2774b29a71795a009e8168a'
20 | assert result['source']['id'] == '0b61b8a47ec844918d372d5741187b1c'
21 | assert result['text'] == '\x1b[0;36mnotice: foo bar'
22 | assert result['size'] == 7
23 | assert result['offset'] == 10
24 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/command.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.command import Command
3 |
4 |
5 | @register(Command)
6 | class CommandCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | return {
9 | 'id': instance.id.hex,
10 | 'name': instance.label,
11 | 'status': instance.status,
12 | 'script': instance.script,
13 | 'returnCode': instance.return_code,
14 | 'env': dict(instance.env or {}),
15 | 'cwd': instance.cwd,
16 | 'type': instance.type,
17 | 'captureOutput': instance.type.is_collector(),
18 | 'artifacts': instance.artifacts or [],
19 | 'duration': instance.duration,
20 | 'dateCreated': instance.date_created,
21 | 'dateStarted': instance.date_started,
22 | 'dateFinished': instance.date_finished,
23 | }
24 |
--------------------------------------------------------------------------------
/changes/api/snapshotimage_details.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from flask.ext.restful import reqparse
4 |
5 | from changes.api.base import APIView
6 | from changes.models.snapshot import SnapshotImage, SnapshotStatus
7 |
8 |
9 | class SnapshotImageDetailsAPIView(APIView):
10 | parser = reqparse.RequestParser()
11 | parser.add_argument('status', choices=SnapshotStatus._member_names_)
12 |
13 | def get(self, image_id):
14 | image = SnapshotImage.query.get(image_id)
15 | if image is None:
16 | return '', 404
17 |
18 | return self.respond(image)
19 |
20 | def post(self, image_id):
21 | image = SnapshotImage.query.get(image_id)
22 | if image is None:
23 | return '', 404
24 |
25 | args = self.parser.parse_args()
26 |
27 | if args.status:
28 | image.change_status(SnapshotStatus[args.status])
29 |
30 | return self.respond(image)
31 |
--------------------------------------------------------------------------------
/migrations/versions/25d63e09ea3b_add_build_job_number.py:
--------------------------------------------------------------------------------
1 | """Add Build/Job numbers
2 |
3 | Revision ID: 25d63e09ea3b
4 | Revises: 1ad857c78a0d
5 | Create Date: 2013-12-26 02:36:58.663362
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '25d63e09ea3b'
11 | down_revision = '1ad857c78a0d'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | op.add_column('build', sa.Column('number', sa.Integer(), nullable=True))
19 | op.add_column('job', sa.Column('number', sa.Integer(), nullable=True))
20 | op.create_unique_constraint('unq_build_number', 'build', ['project_id', 'number'])
21 | op.create_unique_constraint('unq_job_number', 'job', ['build_id', 'number'])
22 |
23 |
24 | def downgrade():
25 | op.drop_column('build', 'number')
26 | op.drop_column('job', 'number')
27 | op.drop_constraint('unq_build_number', 'build')
28 | op.drop_constraint('unq_job_number', 'job')
29 |
--------------------------------------------------------------------------------
/changes/debug/mail/build_result.py:
--------------------------------------------------------------------------------
1 | from flask import render_template
2 | from flask.views import MethodView
3 |
4 | from changes.models.build import Build
5 | from changes.listeners.mail import MailNotificationHandler
6 |
7 |
8 | class BuildResultMailView(MethodView):
9 | def get(self, build_id):
10 | build = Build.query.get(build_id)
11 | assert build, 'There is no build for {}'.format(build_id)
12 |
13 | builds = list(
14 | Build.query.filter(Build.collection_id == build.collection_id))
15 | notification_handler = MailNotificationHandler()
16 | context = notification_handler.get_collection_context(builds)
17 | msg = notification_handler.get_msg(context)
18 | return render_template(
19 | 'debug/email.html',
20 | recipients=msg.recipients,
21 | subject=msg.subject,
22 | text_content=msg.body,
23 | html_content=msg.html,
24 | )
25 |
--------------------------------------------------------------------------------
/changes/buildsteps/lxc.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from changes.buildsteps.default import DefaultBuildStep
4 |
5 |
6 | class LXCBuildStep(DefaultBuildStep):
7 | """
8 | Similar to the default build step, except that it runs the client using
9 | the LXC adapter.
10 | """
11 | def can_snapshot(self):
12 | return True
13 |
14 | def get_label(self):
15 | return 'Build via Changes Client (LXC)'
16 |
17 | def get_client_adapter(self):
18 | return 'lxc'
19 |
20 | @classmethod
21 | def custom_bin_path(cls):
22 | # This is where we mount custom binaries in the container
23 | return '/var/changes/input/'
24 |
25 | def get_allocation_params(self, jobstep):
26 | params = super(LXCBuildStep, self).get_allocation_params(jobstep)
27 | params['memory'] = str(self.resources['mem'])
28 | params['cpus'] = str(self.resources['cpus'])
29 | return params
30 |
--------------------------------------------------------------------------------
/tests/changes/utils/test_http.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import uuid
3 | import mock
4 |
5 | from changes.utils.http import build_patch_uri
6 |
7 |
8 | class TestBuildPatchUri(unittest.TestCase):
9 | def test_internal(self):
10 | patch_id = uuid.UUID('ee62b37b-bc3f-4efe-83bc-f75152d60405')
11 | app = mock.Mock(config={'INTERNAL_BASE_URI': 'https://base_uri/'})
12 | uri = build_patch_uri(patch_id, app)
13 | assert uri == 'https://base_uri/api/0/patches/{0}/?raw=1'.format(
14 | patch_id.hex)
15 |
16 | def test_use_patch(self):
17 | patch_id = uuid.UUID('ee62b37b-bc3f-4efe-83bc-f75152d60405')
18 | app = mock.Mock(config={
19 | 'INTERNAL_BASE_URI': 'https://base_uri/',
20 | 'PATCH_BASE_URI': 'https://patch_uri/'
21 | })
22 | uri = build_patch_uri(patch_id, app)
23 | assert uri == 'https://patch_uri/api/0/patches/{0}/?raw=1'.format(
24 | patch_id.hex)
25 |
--------------------------------------------------------------------------------
/migrations/versions/215db24a630a_add_repository_last_.py:
--------------------------------------------------------------------------------
1 | """Add Repository.last_update, last_update_attempt
2 |
3 | Revision ID: 215db24a630a
4 | Revises: 208224023555
5 | Create Date: 2013-11-25 15:41:16.798396
6 |
7 | """
8 |
9 | # revision identifiers, used by Alembic.
10 | revision = '215db24a630a'
11 | down_revision = '208224023555'
12 |
13 | from alembic import op
14 | import sqlalchemy as sa
15 |
16 |
17 | def upgrade():
18 | ### commands auto generated by Alembic - please adjust! ###
19 | op.add_column('repository', sa.Column('last_update', sa.DateTime(), nullable=True))
20 | op.add_column('repository', sa.Column('last_update_attempt', sa.DateTime(), nullable=True))
21 | ### end Alembic commands ###
22 |
23 |
24 | def downgrade():
25 | ### commands auto generated by Alembic - please adjust! ###
26 | op.drop_column('repository', 'last_update_attempt')
27 | op.drop_column('repository', 'last_update')
28 | ### end Alembic commands ###
29 |
--------------------------------------------------------------------------------
/changes/api/serializer/models/task.py:
--------------------------------------------------------------------------------
1 | from changes.api.serializer import Crumbler, register
2 | from changes.models.task import Task
3 |
4 |
5 | @register(Task)
6 | class TaskCrumbler(Crumbler):
7 | def crumble(self, instance, attrs):
8 | if instance.data:
9 | args = instance.data.get('kwargs') or {}
10 | else:
11 | args = {}
12 |
13 | return {
14 | 'id': instance.id.hex,
15 | 'objectID': instance.task_id,
16 | 'parentObjectID': instance.parent_id,
17 | 'name': instance.task_name,
18 | 'args': args,
19 | 'attempts': instance.num_retries + 1,
20 | 'status': instance.status,
21 | 'result': instance.result,
22 | 'dateCreated': instance.date_created,
23 | 'dateStarted': instance.date_started,
24 | 'dateFinished': instance.date_finished,
25 | 'dateModified': instance.date_modified,
26 | }
27 |
--------------------------------------------------------------------------------