├── test ├── tmp │ └── .gitkeep ├── fixtures │ ├── invalid.mp3 │ ├── respondent_phone_numbers_empty.csv │ ├── respondent_phone_numbers_one.csv │ ├── invalid_audio.csv │ ├── respondent_phone_numbers_r.csv │ ├── audio.aac │ ├── audio.m4a │ ├── audio.mp3 │ ├── audio.wav │ ├── respondent_phone_numbers_invalid.csv │ ├── respondent_phone_numbers_duplicated2.csv │ ├── respondent_phone_numbers_newline.csv │ ├── respondent_phone_numbers_special_characters.csv │ ├── respondent_phone_numbers_utf16le.csv │ ├── respondent_phone_numbers.csv │ ├── respondent_phone_numbers_duplicated.csv │ └── respondent_phone_numbers_additional_columns.csv ├── support │ ├── mocks.ex │ ├── base64_hasher.ex │ ├── audio_checker.ex │ ├── log_helper.ex │ ├── survey_stub.ex │ ├── guisso.ex │ └── ask_swoosh_adapter.ex ├── ask_web │ ├── views │ │ ├── page_view_test.exs │ │ └── error_view_test.exs │ ├── emails │ │ └── email_test.exs │ └── controllers │ │ └── page_controller_test.exs ├── js │ ├── mocha.opts │ └── spec_helper.js ├── test_helper.exs └── ask │ ├── audio_test.exs │ ├── url_shortener_test.exs │ ├── project_test.exs │ ├── response_test.exs │ ├── oauth_token_test.exs │ ├── questionnaire_variable_test.exs │ ├── survey_questionnaire_test.exs │ ├── respondent_group_channel_test.exs │ ├── respondent_disposition_history_test.exs │ └── translation_test.exs ├── priv └── repo │ ├── migrations │ ├── .keep │ ├── 20170126172256_confirm_all_users.exs │ ├── 20161014181536_add_unique_email_index_to_users.exs │ ├── 20161101174551_add_mode_to_surveys.exs │ ├── 20161101174731_set_mode_to_existing_surveys.exs │ ├── 20160909195628_add_state_to_surveys.exs │ ├── 20160913171925_add_cutoff_to_surveys.exs │ ├── 20161014221336_set_questionnaires_mode_to_sms.exs │ ├── 20161104165607_set_timezone_to_existing_surveys.exs │ ├── 20161123205617_add_quotas_to_survey.exs │ ├── 20170109180032_add_settings_to_users.exs │ ├── 20170119184752_add_name_to_users.exs │ ├── 20170329214637_mark_existing_surveys_as_valid.exs │ ├── 20161101163857_downcase_questionnaire_modes.exs │ ├── 20161104145701_add_timezone_to_surveys.exs │ ├── 20161227192753_index_respondents_by_survey_id_and_state.exs │ ├── 20161229195124_index_respondents_by_state_and_timeout.exs │ ├── 20241007192540_pause_channel.exs │ ├── 20160916194539_add_session_to_respondent.exs │ ├── 20170130141552_put_sth_in_password_hash.exs │ ├── 20170301154642_add_scope_to_translation.exs │ ├── 20171122200548_add_stats_to_respondents.exs │ ├── 20190329144509_add_unique_constraint_on_name_in_folders.exs │ ├── 20160906173441_add_modes_to_questionnaire.exs │ ├── 20160909152241_add_steps_to_questionnaires.exs │ ├── 20170102165430_add_comparisons_to_surveys.exs │ ├── 20190328175014_create_folder.exs │ ├── 20191127150645_add_ended_at_to_surveys.exs │ ├── 20230402091100_add_batch_size_to_projects.exs │ ├── 20161110190547_add_started_at_to_surveys.exs │ ├── 20170105213430_add_inviter_email_to_invites.exs │ ├── 20170215173627_add_fallback_delay_to_survey.exs │ ├── 20170315151438_set_default_colour_scheme_to_projects.exs │ ├── 20170329195359_add_valid_to_questionnaires.exs │ ├── 20170428143816_add_language_to_respondents.exs │ ├── 20170731210541_index_respondents_by_sanitized_phone_number.exs │ ├── 20160914215134_add_state_to_respondent.exs │ ├── 20161223131256_add_error_msg_to_quesionnaires.exs │ ├── 20170424180020_add_settings_to_questionnaires.exs │ ├── 20180131174235_add_archived_to_projects.exs │ ├── 20220131103226_add_unconfirmed_email_to_users.exs │ ├── 20161108201151_add_timeout_at_to_respondent.exs │ ├── 20170519125438_add_simulation_to_surveys.exs │ ├── 20180226163507_add_state_to_floip_endpoint_table.exs │ ├── 20190821214420_add_section_order_to_respondent.exs │ ├── 20160926225157_add_expires_at_to_oauth_tokens.exs │ ├── 20161004181711_add_completed_at_to_respondents.exs │ ├── 20161130183834_add_quota_bucket_id_to_respondent.exs │ ├── 20170524132106_add_effective_modes_to_respondent.exs │ ├── 20191204182139_add_retry_stat_time_to_respondent.exs │ ├── 20160906161147_remove_description_from_questionnaires.exs │ ├── 20170131101552_add_remember_created_at_to_users.exs │ ├── 20170308202613_add_lock_version_to_respondents.exs │ ├── 20170622182023_add_deleted_to_questionnaires.exs │ ├── 20170306224401_add_mode_to_respondent_group_channels.exs │ ├── 20170315145645_add_colour_scheme_to_projects.exs │ ├── 20180226195859_add_auth_token_to_floip_endpoint_table.exs │ ├── 20200907093359_add_archived_to_questionnaires.exs │ ├── 20210326160009_add_last_window_ends_at_to_surveys.exs │ ├── 20190621180921_index_respondent_disposition_history_by_survey_id_and_id.exs │ ├── 20210211131024_add_incentives_enabled_to_surveys.exs │ ├── 20230317094712_add_batch_limit_per_minute_to_projects.exs │ ├── 20160812145257_create_user.exs │ ├── 20161116163735_add_sanitized_phone_number_to_respondent.exs │ ├── 20170417193442_add_mobile_web_cookie_code_to_respondents.exs │ ├── 20200211130308_add_respondents_canonical_phone_number.exs │ ├── 20210323192808_add_first_window_started_at_to_surveys.exs │ ├── 20170216184642_add_count_partial_results_to_surveys.exs │ ├── 20170327142610_add_mode_to_respondent_disposition_history.exs │ ├── 20170508193540_add_questionnaire_quota_completed_steps.exs │ ├── 20191213131858_remove_invalid_retry_stat_time_in_respondents.exs │ ├── 20161201181704_add_quota_completed_message_to_questionnaires.exs │ ├── 20170308192425_add_mobile_web_sms_message_to_questionnaire.exs │ ├── 20170406224331_add_mobileweb_retry_configuration_to_surveys.exs │ ├── 20180503032000_modify_survey_log_entry_action_data_to_long_text.exs │ ├── 20210614154820_add_generates_panel_survey_to_surveys.exs │ ├── 20160905173031_add_questionnaire_id_to_surveys.exs │ ├── 20170116175546_add_respondent_group_id_to_respondents.exs │ ├── 20170503172812_add_snapshot_of_to_questionnaires.exs │ ├── 20170601040741_index_survey_log_entries_by_survey_id_respondent_hash_and_id.exs │ ├── 20190328182425_add_project_to_folders.exs │ ├── 20190401140258_add_folder_id_to_surveys.exs │ ├── 20170505203058_create_respondents_index_by_hashed_number_and_updated_at.exs │ ├── 20161109053659_add_retry_configuration_to_surveys.exs │ ├── 20161118175719_set_languages_and_default_language_to_existing_questionnaires.exs │ ├── 20171023212559_create_short_links.exs │ ├── 20170417172528_add_mobile_web_survey_is_over_message_to_questionnaire.exs │ ├── 20161013170416_change_survey_schedule_start_and_end_to_time.exs │ ├── 20170109115814_add_questionnaire_and_mode_to_respondents.exs │ ├── 20170117143633_add_salt_to_projects.exs │ ├── 20180110182428_add_floip_package_id_to_surveys.exs │ ├── 20201124112819_add_panel_surveys.exs │ ├── 20171004213434_add_survey_schedule.exs │ ├── 20161118174623_add_languages_and_default_language_to_questionnaires.exs │ ├── 20170117190223_add_sample_and_respondents_count_to_respondent_group.exs │ ├── 20161110191532_set_default_started_at_for_already_launched_surveys.exs │ ├── 20170116175403_create_respondent_group.exs │ ├── 20160929135859_add_schedule_to_surveys.exs │ ├── 20161013162339_set_default_completed_at_for_completed_respondents.exs │ ├── 20181022174941_add_description_to_surveys.exs │ ├── 20211213094856_add_index_to_respondents_canonical_phone_numbers.exs │ ├── 20191223182932_modify-retry-stats-unique-index.exs │ ├── 20160816183915_create_project.exs │ ├── 20170320182452_add_base_url_to_channel_and_oauth_tokens.exs │ ├── 20180312173843_add_remote_ip_to_activity_log.exs │ ├── 20210512142133_create_panel_survey.exs │ ├── 20160830200454_create_survey.exs │ ├── 20170125165633_add_disposition_to_respondents.exs │ ├── 20170502170714_clear_session_and_timeout_at_for_failed_respondents.exs │ ├── 20160906192317_create_survey_channels.exs │ ├── 20161109034931_create_audios.exs │ ├── 20161229171246_create_invite.exs │ ├── 20170119123633_add_hashed_number_to_respondents.exs │ ├── 20191127152001_set_default_ended_at_for_already_finished_surveys.exs │ ├── 20200122115222_add_description_to_questionnaires.exs │ ├── 20160909144711_create_respondent.exs │ ├── 20161220152839_remove_user_id_from_projects.exs │ ├── 20170125151039_set_default_comparisons_on_surveys.exs │ ├── 20160914194540_modify_questionnaires_steps_to_longtext.exs │ ├── 20200211131441_set_default_respondents_canonical_phone_number.exs │ ├── 20170117191259_modify_respondent_group_sample_to_long_text.exs │ ├── 20230217143550_add_settings_to_projects.exs │ ├── 20191212203052_set_default_retry_stat_time_to_respondents.exs │ ├── 20230413101342_remove_mobile_web_cookie_code_from_respondents.exs │ ├── 20160914174501_create_response.exs │ ├── 20161124170829_create_quota_buckets.exs │ ├── 20161219200232_create_project_membership.exs │ ├── 20200420135544_add_partial_relevant_config_to_questionnaires.exs │ ├── 20160902185110_create_questionnaire.exs │ ├── 20160924055948_create_oauth_token.exs │ ├── 20161221101027_remove_questionnaire_id_from_surveys.exs │ ├── 20170202175824_create_sessions_table.exs │ ├── 20170420194102_change_questionnaire_mobile_web_sms_message_to_text.exs │ ├── 20190621174448_add_survey_id_and_respondent_hashed_number_to_respondent_disposition_history.exs │ ├── 20210326160205_fill_last_window_ends_at_in_surveys.exs │ ├── 20170119153421_remove_survey_channels.exs │ ├── 20170125165933_set_disposition_as_completed_where_applies_in_respondents.exs │ ├── 20160905135419_create_channel.exs │ ├── 20161115202151_set_default_schedule_start_and_end_times.exs │ ├── 20200212200209_add-user-stopped-to-respondents.exs │ ├── 20160908162553_create_questionnaire_step.exs │ ├── 20160915191129_rename_survey_state_default_to_not_ready.exs │ ├── 20170206162219_create_respondent_disposition_history.exs │ ├── 20180315000255_create_project_channels.exs │ ├── 20210325194736_fill_first_window_started_at_in_surveys.exs │ ├── 20180302023759_create_activity_log.exs │ ├── 20170207152142_modify_translation_source_text_and_target_text_to_long_text.exs │ ├── 20191011174319_create_retry_stats.exs │ ├── 20160909150600_drop_questionnaire_steps.exs │ ├── 20161221100014_create_survey_questionnaire.exs │ ├── 20170818184001_add_default_disposition_to_rejected_respondents.exs │ ├── 20160913180125_set_default_state_for_surveys.exs │ ├── 20170113200644_add_coherence_to_user.exs │ ├── 20170119152252_create_respondent_group_channel.exs │ ├── 20161219204505_create_questionnaire_variable.exs │ ├── 20170224213051_create_survey_log_entry.exs │ ├── 20170307180659_set_default_mode_to_respondent_group_channels.exs │ ├── 20170322170704_alter_unique_index_of_oauth_tokens.exs │ ├── 20171023192702_add_completed_respondents_table.exs │ ├── 20180219132514_add_floip_endpoints_table.exs │ ├── 20211125141835_optimize_audios_uuid.exs │ ├── 20171213183513_force_respondent_stats_not_null.exs │ ├── 20170110002354_set_default_settings_to_users.exs │ ├── 20171004213436_remove_survey_day_of_week_start_time_and_end_time.exs │ ├── 20170201123448_add_unique_index_to_invites.exs │ ├── 20170818182906_add_respondent_stats_table.exs │ ├── 20181102164026_add_locked_to_surveys.exs │ ├── 20180504144910_add_patterns_to_channels.exs │ ├── 20170424193129_remove_old_messages_from_questionnaires.exs │ ├── 20190621180432_fill_survey_id_and_respondent_hashed_numbnerin_respondent_disposition_history.exs │ ├── 20161222124950_create_translation.exs │ ├── 20161220165340_change_survey_modes_to_arary_of_arrays.exs │ ├── 20170119182423_create_coherence_rememberable.exs │ ├── 20161013154229_set_default_schedule_day_of_week_for_surveys.exs │ └── 20180413192752_replace_floip_endpoints_table.exs │ └── seeds.exs ├── .prettierignore ├── .prettierrc ├── assets ├── js │ ├── decls │ │ ├── jquery.js │ │ ├── mocha.js │ │ ├── project.js │ │ ├── channel.js │ │ └── undo.js │ ├── jsconfig.json │ ├── components │ │ ├── layout │ │ │ ├── Root.jsx │ │ │ ├── Root.prod.jsx │ │ │ ├── Root.dev.jsx │ │ │ └── Footer.jsx │ │ ├── questionnaires │ │ │ ├── withQuestionnaire.jsx.flow │ │ │ ├── withQuestionnaire.jsx │ │ │ └── StepDeleteButton.jsx │ │ ├── surveys │ │ │ └── PhoneNumberRow.jsx │ │ ├── timezones │ │ │ └── util.js │ │ ├── ui │ │ │ ├── Card.jsx │ │ │ ├── UntitledIfEmpty.jsx │ │ │ └── ScrollToLink.jsx │ │ ├── charts │ │ │ ├── utils.js │ │ │ ├── Stats.jsx │ │ │ └── References.jsx │ │ ├── integrations │ │ │ └── IntegrationRow.jsx │ │ └── simulation │ │ │ └── MobileWebWindow.jsx │ ├── store │ │ ├── configureStore.js │ │ └── configureStore.dev.js │ ├── reducers │ │ ├── ui │ │ │ └── index.js │ │ ├── respondentsStats.js │ │ ├── surveyStats.js │ │ ├── surveyRetriesHistograms.js │ │ ├── guisso.js │ │ ├── invite.js │ │ ├── respondentsCount.js │ │ ├── timezones.js │ │ ├── unusedSample.js │ │ └── channels.js │ ├── debounce.js │ ├── actions │ │ ├── autoSaveStatus.js │ │ ├── guisso.js │ │ └── timezones.js │ ├── app.jsx │ ├── config.js │ ├── pigeon.js │ ├── propsAreEqual.js │ └── surveyModes.js ├── static │ ├── favicon.ico │ ├── images │ │ ├── map.png │ │ ├── phoenix.png │ │ ├── header_icon.png │ │ ├── nuntium@2x.png │ │ ├── verboice@2x.png │ │ ├── warning@2x.png │ │ ├── email-logo@2x.png │ │ ├── surveda-name.png │ │ ├── welcome-img01.jpg │ │ ├── welcome-img01.png │ │ ├── welcome-img02.png │ │ ├── welcome-img03.png │ │ ├── welcome-img04.png │ │ ├── welcome-main.png │ │ ├── favicon │ │ │ ├── favicon.ico │ │ │ ├── favicon-128.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── mstile-70x70.png │ │ │ ├── favicon-196x196.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-310x310.png │ │ │ ├── apple-touch-icon-57x57.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-72x72.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon-114x114.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-144x144.png │ │ │ └── apple-touch-icon-152x152.png │ │ ├── file_download_off.svg │ │ └── header_icon_default.svg │ ├── fonts │ │ ├── roboto │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.woff │ │ │ ├── Roboto-Thin.woff2 │ │ │ └── Roboto-Regular.woff2 │ │ └── material-icons │ │ │ ├── MaterialIcons-Regular.eot │ │ │ ├── MaterialIcons-Regular.ttf │ │ │ ├── MaterialIcons-Regular.woff │ │ │ └── MaterialIcons-Regular.woff2 │ ├── files │ │ └── phone_numbers_example.csv │ └── robots.txt ├── mobile_survey │ └── js │ │ ├── store │ │ ├── configureStore.js │ │ ├── configureStore.dev.js │ │ └── configureStore.prod.js │ │ ├── reducers │ │ ├── index.js │ │ └── config.js │ │ ├── components │ │ ├── Prompt.jsx │ │ ├── layout │ │ │ └── Root.jsx │ │ └── steps │ │ │ └── EndStep.jsx │ │ ├── mobileSurvey.jsx │ │ └── api.js ├── vendor │ ├── css │ │ └── materialize │ │ │ └── components │ │ │ ├── _mixins.scss │ │ │ ├── _icons-material-design.scss │ │ │ ├── _transitions.scss │ │ │ ├── _ncdvariables.scss │ │ │ ├── forms │ │ │ └── _forms.scss │ │ │ ├── _tooltip.scss │ │ │ ├── _pulse.scss │ │ │ └── _table_of_contents.scss │ └── js │ │ └── materialize-dropdown-fix.js └── css │ └── app.scss ├── .dockerignore ├── lib ├── ask │ ├── mailer.ex │ ├── prometheus │ │ ├── prometheus_exporter.ex │ │ └── phoenix_instrumenter.ex │ ├── ecto_types │ │ ├── schedule_error.ex │ │ ├── json.ex │ │ ├── erlang_term.ex │ │ └── string_list.ex │ ├── metrics │ │ └── metrics_endpoint.ex │ ├── time.ex │ ├── session.ex │ ├── model.ex │ ├── completed_respondents.ex │ ├── bom_parser.ex │ ├── questionnaire_variable.ex │ ├── config_helper.ex │ ├── survey_questionnaire.ex │ ├── db_session_store.ex │ ├── project_channel.ex │ ├── repo.ex │ ├── floip_endpoint.ex │ ├── logger.ex │ ├── invite.ex │ ├── file_info.ex │ ├── respondent_group_channel.ex │ └── response.ex ├── ask_web │ ├── templates │ │ ├── email │ │ │ ├── channel_down.text.eex │ │ │ ├── channel_error.text.eex │ │ │ ├── notify.text.eex │ │ │ └── invite.text.eex │ │ ├── coherence │ │ │ ├── layout │ │ │ │ ├── email.text.eex │ │ │ │ └── email.html.eex │ │ │ ├── email │ │ │ │ ├── invitation.text.eex │ │ │ │ ├── confirmation.text.eex │ │ │ │ ├── password.text.eex │ │ │ │ ├── unlock.html.eex │ │ │ │ └── invitation.html.eex │ │ │ ├── registration │ │ │ │ ├── edit.html.eex │ │ │ │ ├── confirmation_expired.html.eex │ │ │ │ ├── confirmation_sent.html.eex │ │ │ │ └── show.html.eex │ │ │ └── password │ │ │ │ └── password_recovery_sent.html.eex │ │ ├── page │ │ │ └── index.html.eex │ │ ├── o_auth_client │ │ │ └── callback.html.eex │ │ ├── mobile_survey │ │ │ ├── 404.html.eex │ │ │ ├── index.html.eex │ │ │ └── unauthorized.html.eex │ │ ├── mobile_survey_simulation │ │ │ ├── 404.html.eex │ │ │ ├── index.html.eex │ │ │ └── unauthorized.html.eex │ │ └── layout │ │ │ ├── app.html.eex │ │ │ ├── mobile_survey.html.eex │ │ │ └── mobile_survey_simulation.html.eex │ ├── views │ │ ├── email_view.ex │ │ ├── mobile_survey_view.ex │ │ ├── coherence │ │ │ ├── coherence_view.ex │ │ │ ├── email_view.ex │ │ │ ├── layout_view.ex │ │ │ ├── session_view.ex │ │ │ ├── confirmation_view.ex │ │ │ └── password_view.ex │ │ ├── mobile_survey_simulation_view.ex │ │ ├── page_view.ex │ │ ├── timezone_view.ex │ │ ├── audio_view.ex │ │ ├── user.ex │ │ ├── oauth_client_view.ex │ │ └── changeset_view.ex │ ├── controllers │ │ ├── coherence │ │ │ ├── responders │ │ │ │ └── json.ex │ │ │ └── confirmation_controller.ex │ │ ├── callback_controller.ex │ │ ├── fallback_controller.ex │ │ ├── audio_delivery_controller.ex │ │ └── timezone_controller.ex │ ├── emails │ │ └── coherence │ │ │ └── coherence_mailer.ex │ ├── helpers │ │ ├── changeset_helper.ex │ │ └── pagination_helper.ex │ └── plugs │ │ ├── sentry_context.ex │ │ ├── not_allowed_error.ex │ │ └── unauthorized_error.ex ├── guisso │ └── token_exchange.ex └── mix │ └── tasks │ └── ecto_dump.ex ├── docs ├── callbacks_flow.png ├── surveda-layers.png ├── verboice_limit.png ├── callcentric_extensions.png ├── callcentric_preferences.png └── cloud-instance-configs │ ├── etc-docker-daemon.json │ ├── haproxy.cfg │ ├── ona.rancher-compose.yml │ ├── db.rancher-compose.yml │ ├── iam.s3-backup-user-policy.json │ ├── verboice.rancher-compose.yml │ ├── proxy.docker-compose.yml │ ├── iam.ses-smtp-user-policy.json │ └── iam.route53-rancher-user-policy.json ├── integration_tests ├── cypress │ ├── fixtures │ │ ├── 2118.zip │ │ └── respondents_sample.csv │ ├── integration │ │ └── projects.spec.js │ └── support │ │ └── index.js ├── cypress.sample.json ├── docker-compose.yml └── package.json ├── .babelrc.js ├── .formatter.exs ├── .vscode └── settings.json ├── .iex.exs ├── .flowconfig └── dev-setup.sh /test/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /priv/repo/migrations/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/invalid.mp3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | assets/vendor 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | printWidth: 100 3 | -------------------------------------------------------------------------------- /assets/js/decls/jquery.js: -------------------------------------------------------------------------------- 1 | declare var $: Function 2 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_empty.csv: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_one.csv: -------------------------------------------------------------------------------- 1 | 123456789 -------------------------------------------------------------------------------- /test/support/mocks.ex: -------------------------------------------------------------------------------- 1 | Mox.defmock(Ask.TimeMock, for: Ask.RealTime) 2 | -------------------------------------------------------------------------------- /test/fixtures/invalid_audio.csv: -------------------------------------------------------------------------------- 1 | Call me! Call me! Baby baby call me now 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | _build/ 3 | deps/ 4 | node_modules/ 5 | **/.DS_Store 6 | -------------------------------------------------------------------------------- /assets/js/decls/mocha.js: -------------------------------------------------------------------------------- 1 | declare var it: Function 2 | declare var describe: Function 3 | -------------------------------------------------------------------------------- /lib/ask/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Mailer do 2 | use Swoosh.Mailer, otp_app: :ask 3 | end 4 | -------------------------------------------------------------------------------- /lib/ask_web/templates/email/channel_down.text.eex: -------------------------------------------------------------------------------- 1 | <%= @channel_name %> channel is down 2 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_r.csv: -------------------------------------------------------------------------------- 1 | 15044020205 15042966031 16302635123 16306979171 -------------------------------------------------------------------------------- /docs/callbacks_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/docs/callbacks_flow.png -------------------------------------------------------------------------------- /docs/surveda-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/docs/surveda-layers.png -------------------------------------------------------------------------------- /docs/verboice_limit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/docs/verboice_limit.png -------------------------------------------------------------------------------- /lib/ask_web/views/email_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.EmailView do 2 | use AskWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/audio.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/test/fixtures/audio.aac -------------------------------------------------------------------------------- /test/fixtures/audio.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/test/fixtures/audio.m4a -------------------------------------------------------------------------------- /test/fixtures/audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/test/fixtures/audio.mp3 -------------------------------------------------------------------------------- /test/fixtures/audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/test/fixtures/audio.wav -------------------------------------------------------------------------------- /assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/favicon.ico -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_invalid.csv: -------------------------------------------------------------------------------- 1 | aa15123412 2 | bb523511521 3 | cc125132115123 4 | -------------------------------------------------------------------------------- /assets/static/images/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/map.png -------------------------------------------------------------------------------- /docs/callcentric_extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/docs/callcentric_extensions.png -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/layout/email.text.eex: -------------------------------------------------------------------------------- 1 | <%= render @view_module, @view_template, assigns %> 2 | -------------------------------------------------------------------------------- /lib/ask_web/views/mobile_survey_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.MobileSurveyView do 2 | use AskWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /assets/static/images/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/phoenix.png -------------------------------------------------------------------------------- /docs/callcentric_preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/docs/callcentric_preferences.png -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_duplicated2.csv: -------------------------------------------------------------------------------- 1 | +1234567 2 | 1234567 3 | (+1)234567 4 | 1234567 5 | (12) 34-567 -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_newline.csv: -------------------------------------------------------------------------------- 1 | 15044020205 2 | 15042966031 3 | 16302635123 4 | 16306979171 5 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_special_characters.csv: -------------------------------------------------------------------------------- 1 | +154 11 1213 2345 2 | 4545-2829 3 | (011)1234-3322 4 | -------------------------------------------------------------------------------- /assets/static/images/header_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/header_icon.png -------------------------------------------------------------------------------- /assets/static/images/nuntium@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/nuntium@2x.png -------------------------------------------------------------------------------- /assets/static/images/verboice@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/verboice@2x.png -------------------------------------------------------------------------------- /assets/static/images/warning@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/warning@2x.png -------------------------------------------------------------------------------- /lib/ask/prometheus/prometheus_exporter.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.PrometheusExporter do 2 | use Prometheus.PlugExporter 3 | end 4 | -------------------------------------------------------------------------------- /test/ask_web/views/page_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.PageViewTest do 2 | use AskWeb.ConnCase, async: true 3 | end 4 | -------------------------------------------------------------------------------- /assets/static/images/email-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/email-logo@2x.png -------------------------------------------------------------------------------- /assets/static/images/surveda-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/surveda-name.png -------------------------------------------------------------------------------- /assets/static/images/welcome-img01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-img01.jpg -------------------------------------------------------------------------------- /assets/static/images/welcome-img01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-img01.png -------------------------------------------------------------------------------- /assets/static/images/welcome-img02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-img02.png -------------------------------------------------------------------------------- /assets/static/images/welcome-img03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-img03.png -------------------------------------------------------------------------------- /assets/static/images/welcome-img04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-img04.png -------------------------------------------------------------------------------- /assets/static/images/welcome-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/welcome-main.png -------------------------------------------------------------------------------- /lib/ask/ecto_types/schedule_error.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.ScheduleError do 2 | defexception message: "schedule error" 3 | end 4 | -------------------------------------------------------------------------------- /lib/ask_web/views/coherence/coherence_view.ex: -------------------------------------------------------------------------------- 1 | defmodule Coherence.CoherenceView do 2 | use AskWeb.Coherence, :view 3 | end 4 | -------------------------------------------------------------------------------- /lib/ask_web/views/coherence/email_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.EmailView do 2 | use AskWeb.Coherence, :view 3 | end 4 | -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon.ico -------------------------------------------------------------------------------- /lib/ask/prometheus/phoenix_instrumenter.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.PhoenixInstrumenter do 2 | use Prometheus.PhoenixInstrumenter 3 | end 4 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/coherence/responders/json.ex: -------------------------------------------------------------------------------- 1 | defmodule Coherence.Responders.Json do 2 | use Responders.Json 3 | end 4 | -------------------------------------------------------------------------------- /lib/ask_web/views/mobile_survey_simulation_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.MobileSurveySimulationView do 2 | use AskWeb, :view 3 | end 4 | -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /integration_tests/cypress/fixtures/2118.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/integration_tests/cypress/fixtures/2118.zip -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon-128.png -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /assets/static/images/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /assets/static/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /assets/static/images/favicon/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/favicon-196x196.png -------------------------------------------------------------------------------- /assets/static/images/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /assets/static/images/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /assets/static/images/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /assets/static/images/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /test/js/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:@babel/register 2 | -r jsdom-global/register 3 | --reporter dot 4 | --recursive 5 | --require ignore-styles 6 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_utf16le.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/test/fixtures/respondent_phone_numbers_utf16le.csv -------------------------------------------------------------------------------- /assets/js/decls/project.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type Project = { 3 | id: number, 4 | name: string, 5 | updatedAt: string, 6 | readOnly: boolean, 7 | } 8 | -------------------------------------------------------------------------------- /assets/js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "experimentalDecorators": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /lib/ask/metrics/metrics_endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.MetricsEndpoint do 2 | use Phoenix.Endpoint, otp_app: :ask 3 | 4 | plug Ask.PrometheusExporter 5 | end 6 | -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /assets/static/images/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/images/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /assets/static/fonts/material-icons/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/material-icons/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /assets/static/fonts/material-icons/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/material-icons/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /assets/static/fonts/material-icons/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/material-icons/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /assets/static/fonts/material-icons/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instedd/surveda/HEAD/assets/static/fonts/material-icons/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /docs/cloud-instance-configs/etc-docker-daemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "log-driver": "json-file", 3 | "log-opts": { 4 | "max-size": "100m", 5 | "max-file": "3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/ask_web/templates/email/channel_error.text.eex: -------------------------------------------------------------------------------- 1 | Error when connecting with <%= @channel_name %> channel 2 | 3 | Follow this link to go to the channel: <%= @url %> 4 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: "defaults" }], 4 | "@babel/preset-react", 5 | "@babel/preset-flow" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /assets/js/components/layout/Root.jsx: -------------------------------------------------------------------------------- 1 | if (window.mixEnv == "prod") { 2 | module.exports = require("./Root.prod") 3 | } else { 4 | module.exports = require("./Root.dev") 5 | } 6 | -------------------------------------------------------------------------------- /lib/ask_web/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.PageView do 2 | use AskWeb, :view 3 | 4 | def config_intercom(_conn) do 5 | Ask.Intercom.config_intercom() 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/js/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (window.mixEnv == "prod") { 2 | module.exports = require("./configureStore.prod") 3 | } else { 4 | module.exports = require("./configureStore.dev") 5 | } 6 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/haproxy.cfg: -------------------------------------------------------------------------------- 1 | listen stats 2 | bind 0.0.0.0:9000 3 | mode http 4 | stats enable 5 | stats hide-version 6 | stats realm Haproxy\ Statistics 7 | stats uri /haproxy_stats 8 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/ona.rancher-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | app: 4 | scale: 1 5 | start_on_create: true 6 | db: 7 | scale: 1 8 | start_on_create: true 9 | -------------------------------------------------------------------------------- /assets/static/files/phone_numbers_example.csv: -------------------------------------------------------------------------------- 1 | (012) 3456-7890 2 | (012) 34 5678-9013 3 | (012) 34 5678-9014 4 | (012) 34 5678-9015 5 | (012) 34 5678-9011 6 | (012) 34 5678-8235 7 | (012) 34 2629-2276 8 | -------------------------------------------------------------------------------- /lib/ask_web/templates/page/index.html.eex: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/db.rancher-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | mysql: 4 | scale: 1 5 | start_on_create: true 6 | mysql-backup: 7 | scale: 1 8 | start_on_create: true 9 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix, :ecto, :ecto_sql], 3 | inputs: ["*.{ex,exs}", "{config,lib,priv,test}/**/*.{ex,exs}"], 4 | locals_without_parens: [ 5 | coherence_routes: :* 6 | ] 7 | ] 8 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (window.mixEnv == "prod") { 2 | module.exports = require("./configureStore.prod") 3 | } else { 4 | module.exports = require("./configureStore.dev") 5 | } 6 | -------------------------------------------------------------------------------- /lib/ask_web/templates/email/notify.text.eex: -------------------------------------------------------------------------------- 1 | <%= @invited_by %> has added you as a collaborator on <%= @project_name %>. 2 | 3 | <%= @explanation %> 4 | 5 | Follow this link to see the project: <%= @url %> 6 | -------------------------------------------------------------------------------- /assets/vendor/css/materialize/components/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin box-shadow-2($args1, $args2) { 2 | -webkit-box-shadow: $args1, $args2; 3 | -moz-box-shadow: $args1, $args2; 4 | box-shadow: $args1, $args2; 5 | } -------------------------------------------------------------------------------- /lib/ask_web/emails/coherence/coherence_mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.Mailer do 2 | @moduledoc false 3 | if Coherence.Config.mailer?() do 4 | use Swoosh.Mailer, otp_app: :coherence 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/email/invitation.text.eex: -------------------------------------------------------------------------------- 1 | Hello <%= @name %>! 2 | 3 | You have been invited to create an Account. Use the link below to create an account. 4 | 5 | <%= @url %> 6 | 7 | Thank you! 8 | -------------------------------------------------------------------------------- /lib/ask_web/templates/email/invite.text.eex: -------------------------------------------------------------------------------- 1 | <%= @invited_by %> has invited you to collaborate on <%= @project_name %>. 2 | 3 | <%= @explanation %> 4 | 5 | Follow this link to accept the invitation: <%= @url %> 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "javascript.validate.enable": false, 4 | "[elixir]": { 5 | "editor.formatOnSave": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/ask_web/views/coherence/layout_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.LayoutView do 2 | use AskWeb.Coherence, :view 3 | 4 | def config_intercom(_conn) do 5 | Ask.Intercom.config_intercom() 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/email/confirmation.text.eex: -------------------------------------------------------------------------------- 1 | Hello <%= @name %>! 2 | 3 | Your new Surveda account is almost ready, follow the link below to confirm and activate it. 4 | 5 | <%= @url %> 6 | 7 | Thank you! 8 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(exclude: [:skip]) 2 | 3 | {:ok, _} = Application.ensure_all_started(:ex_machina) 4 | {:ok, _} = Application.ensure_all_started(:bypass) 5 | {:ok, _} = Application.ensure_all_started(:mox) 6 | -------------------------------------------------------------------------------- /.iex.exs: -------------------------------------------------------------------------------- 1 | import Ecto 2 | import Ecto.Query 3 | 4 | alias Ask.{ 5 | Repo, 6 | Channel, 7 | Project, 8 | Questionnaire, 9 | Respondent, 10 | Response, 11 | Survey, 12 | SurveyChannel, 13 | User 14 | } 15 | -------------------------------------------------------------------------------- /lib/ask_web/helpers/changeset_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule Changeset.Helper do 2 | def changed_properties(changeset) do 3 | changes = changeset.changes 4 | Enum.filter(Map.keys(changes), fn key -> changes[key] != [] end) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/ask_web/views/timezone_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.TimezoneView do 2 | use AskWeb, :view 3 | 4 | def render("index.json", %{timezones: timezones}) do 5 | %{ 6 | timezones: timezones 7 | } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170126172256_confirm_all_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ConfirmAllUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("UPDATE users SET confirmed_at=NOW()") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { combineReducers } from "redux" 3 | import step from "./step" 4 | import config from "./config" 5 | 6 | export default combineReducers({ 7 | step, 8 | config, 9 | }) 10 | -------------------------------------------------------------------------------- /assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161014181536_add_unique_email_index_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddUniqueEmailIndexToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create unique_index(:users, [:email]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/support/base64_hasher.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Base64Hasher do 2 | def hashpwsalt(password) do 3 | Base.encode64(password) 4 | end 5 | 6 | def checkpw(password, hash) do 7 | Base.decode64!(hash) == password 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161101174551_add_mode_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddModeToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :mode, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161101174731_set_mode_to_existing_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetModeToExistingSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update surveys set mode = '[\"sms\"]'") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/vendor/css/materialize/components/_icons-material-design.scss: -------------------------------------------------------------------------------- 1 | /* This is needed for some mobile phones to display the Google Icon font properly */ 2 | .material-icons { 3 | text-rendering: optimizeLegibility; 4 | font-feature-settings: 'liga'; 5 | } 6 | -------------------------------------------------------------------------------- /lib/ask/time.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.SystemTime do 2 | def time(), do: Application.get_env(:ask, :time, Ask.RealTime) 3 | end 4 | 5 | defmodule Ask.RealTime do 6 | @callback now() :: DateTime 7 | def now() do 8 | DateTime.utc_now() 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/ask_web/templates/o_auth_client/callback.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160909195628_add_state_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddStateToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :state, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/js/components/questionnaires/withQuestionnaire.jsx.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare function withQuestionnaire(x: Class>): 3 | Class, S>> 4 | 5 | module.exports = withQuestionnaire 6 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/registration/edit.html.eex: -------------------------------------------------------------------------------- 1 |

Edit Account

2 | 3 | <%= render "form.html", changeset: @changeset, 4 | label: "Update", required: [], 5 | action: registration_path(@conn, :update) %> 6 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160913171925_add_cutoff_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddCutoffToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :cutoff, :integer 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161014221336_set_questionnaires_mode_to_sms.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetQuestionnairesModeToSms do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update questionnaires set modes = 'SMS'") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161104165607_set_timezone_to_existing_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetTimezoneToExistingSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update surveys set timezone = 'Etc/UTC'") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161123205617_add_quotas_to_survey.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuotasToSurvey do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :quota_vars, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170109180032_add_settings_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSettingsToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | add :settings, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170119184752_add_name_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddNameToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | add :name, :string, default: "" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170329214637_mark_existing_surveys_as_valid.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.MarkExistingSurveysAsValid do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update questionnaires set valid = ?", [true]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/ask/ecto_types/json.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Ecto.Type.JSON do 2 | use Ecto.Type 3 | def type, do: :longtext 4 | def cast(any), do: {:ok, any} 5 | def load(string) when is_binary(string), do: Poison.decode(string) 6 | def dump(json), do: Poison.encode(json) 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161101163857_downcase_questionnaire_modes.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.DowncaseQuestionnaireModes do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update questionnaires set modes = lower(modes)") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161104145701_add_timezone_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddTimezoneToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :timezone, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161227192753_index_respondents_by_survey_id_and_state.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.IndexRespondentsBySurveyIdAndState do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:respondents, [:survey_id, :state]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161229195124_index_respondents_by_state_and_timeout.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.IndexRespondentsByStateAndTimeout do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:respondents, [:state, :timeout_at]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20241007192540_pause_channel.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.PauseChannel do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:channels) do 6 | add :paused, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/js/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import prodStore from "./configureStore.prod" 2 | import createLogger from "redux-logger" 3 | 4 | export default function configureStore(preloadedState) { 5 | return prodStore(preloadedState, [createLogger({ collapsed: true })]) 6 | } 7 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160916194539_add_session_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSessionToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :session, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170130141552_put_sth_in_password_hash.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.PutSthInPasswordHash do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("UPDATE users SET password_hash='1' WHERE password_hash IS NULL") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170301154642_add_scope_to_translation.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddScopeToTranslation do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:translations) do 6 | add :scope, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171122200548_add_stats_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddStatsToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :stats, :longtext 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190329144509_add_unique_constraint_on_name_in_folders.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddUniqueConstraintOnNameInFolders do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create unique_index(:folders, [:name, :project_id]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160906173441_add_modes_to_questionnaire.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddModesToQuestionnaire do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :modes, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160909152241_add_steps_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddStepsToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :steps, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170102165430_add_comparisons_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddComparisonsToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :comparisons, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190328175014_create_folder.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateFolder do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:folders) do 6 | add :name, :string 7 | 8 | timestamps() 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191127150645_add_ended_at_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddEndedAtToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :ended_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20230402091100_add_batch_size_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddBatchSizeToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:projects) do 6 | add :batch_size, :integer 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import prodStore from "./configureStore.prod" 2 | import createLogger from "redux-logger" 3 | 4 | export default function configureStore(preloadedState) { 5 | return prodStore(preloadedState, [createLogger({ collapsed: true })]) 6 | } 7 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161110190547_add_started_at_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLaunchedAtToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :started_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170105213430_add_inviter_email_to_invites.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddInviterEmailToInvites do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:invites) do 6 | add :inviter_email, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170215173627_add_fallback_delay_to_survey.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFallbackDelayToSurvey do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :fallback_delay, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170315151438_set_default_colour_scheme_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultColourSchemeToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!(~s(update projects set colour_scheme = 'default')) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170329195359_add_valid_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddValidToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :valid, :boolean 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170428143816_add_language_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLanguageToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :language, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170731210541_index_respondents_by_sanitized_phone_number.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.IndexRespondentsBySanitizedPhoneNumber do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:respondents, [:sanitized_phone_number]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/js/spec_helper.js: -------------------------------------------------------------------------------- 1 | import each from 'lodash/each' 2 | 3 | export const playActionsFromState = (state, reducer) => (actions) => { 4 | let resultState = state 5 | each(actions, (a) => { 6 | resultState = reducer(resultState, a) 7 | }) 8 | return resultState 9 | } 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160914215134_add_state_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddStateToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :state, :string, default: "pending" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161223131256_add_error_msg_to_quesionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddErrorMsgToQuesionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :error_msg, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170424180020_add_settings_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSettingsToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :settings, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180131174235_add_archived_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddArchivedToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:projects) do 6 | add :archived, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20220131103226_add_unconfirmed_email_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddUnconfirmedEmailToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | add :unconfirmed_email, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/js/decls/channel.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type Channel = { 3 | userId?: number, 4 | name: string, 5 | type: string, 6 | projects: Project[], 7 | provider: string, 8 | settings: {}, 9 | patterns: Array<{ input: string, output: string }>, 10 | errorsByPath: {}, 11 | } 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161108201151_add_timeout_at_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddTimeoutAtToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :timeout_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170519125438_add_simulation_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSimulationToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :simulation, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180226163507_add_state_to_floip_endpoint_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddStateToFloipEndpointTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:floip_endpoints) do 6 | add :state, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190821214420_add_section_order_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSectionOrderToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :section_order, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160926225157_add_expires_at_to_oauth_tokens.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddExpiresAtToOauthTokens do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:oauth_tokens) do 6 | add :expires_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161004181711_add_completed_at_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddCompletedAtToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :completed_at, :utc_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161130183834_add_quota_bucket_id_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuotaBucketIdToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :quota_bucket_id, :integer 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170524132106_add_effective_modes_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddEffectiveModesToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :effective_modes, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191204182139_add_retry_stat_time_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRetryStatTimeToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :retry_stat_time, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /integration_tests/cypress.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://surveda-stg.instedd.org", 3 | "env": { 4 | "guisso_host": "https://login-stg.instedd.org", 5 | "email": "foo@bar.com", 6 | "password": "foobar", 7 | "project_id": "1", 8 | "sms_channel_id": "1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160906161147_remove_description_from_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveDescriptionFromQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | remove :description 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170131101552_add_remember_created_at_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRememberCreatedAtToUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | add :remember_created_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170308202613_add_lock_version_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLockVersionToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :lock_version, :integer, default: 1 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170622182023_add_deleted_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddDeletedToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :deleted, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /integration_tests/cypress/fixtures/respondents_sample.csv: -------------------------------------------------------------------------------- 1 | 1000000 2 | 1000001 3 | 1000002 4 | 1000003 5 | 1000004 6 | 1000005 7 | 1000006 8 | 1000007 9 | 1000008 10 | 1000009 11 | 1000010 12 | 1000011 13 | 1000012 14 | 1000013 15 | 1000014 16 | 1000015 17 | 1000016 18 | 1000017 19 | 1000018 20 | 1000019 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170306224401_add_mode_to_respondent_group_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddModeToRespondentGroupChannels do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondent_group_channels) do 6 | add :mode, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170315145645_add_colour_scheme_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddColourSchemeToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:projects) do 6 | add :colour_scheme, :string, default: "default" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180226195859_add_auth_token_to_floip_endpoint_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddAuthTokenToFloipEndpointTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:floip_endpoints) do 6 | add :auth_token, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200907093359_add_archived_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddArchivedToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :archived, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210326160009_add_last_window_ends_at_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLastWindowEndsAtToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :last_window_ends_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/ask_web/views/audio_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.AudioView do 2 | use AskWeb, :view 3 | 4 | def render("show.json", %{audio: audio}) do 5 | %{data: render_one(audio, AskWeb.AudioView, "audio.json")} 6 | end 7 | 8 | def render("audio.json", %{audio: audio}) do 9 | audio 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190621180921_index_respondent_disposition_history_by_survey_id_and_id.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.IndexRespondentDispositionHistoryBySurveyIdAndId do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:respondent_disposition_history, [:survey_id, :id]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210211131024_add_incentives_enabled_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddIncentivesEnabledToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :incentives_enabled, :boolean, default: true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20230317094712_add_batch_limit_per_minute_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddBatchLimitPerMinuteToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:projects) do 6 | add :batch_limit_per_minute, :integer 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160812145257_create_user.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateUser do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users) do 6 | add :email, :string 7 | add :encrypted_password, :string 8 | 9 | timestamps() 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161116163735_add_sanitized_phone_number_to_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSanitizedPhoneNumberToRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :sanitized_phone_number, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170417193442_add_mobile_web_cookie_code_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddMobileWebCookieCodeToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :mobile_web_cookie_code, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200211130308_add_respondents_canonical_phone_number.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRespondentsCanonicalPhoneNumber do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :canonical_phone_number, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210323192808_add_first_window_started_at_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFirstWindowStartedAtToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :first_window_started_at, :naive_datetime 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/callback_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.CallbackController do 2 | use AskWeb, :controller 3 | 4 | alias Ask.Channel 5 | 6 | def callback(conn, params = %{"provider" => provider}) do 7 | channel = Channel.provider(provider) 8 | channel.callback(conn, params) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/registration/confirmation_expired.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 | warning 4 |
Your activation link expired
5 | Resend confirmation email 6 |
7 |
-------------------------------------------------------------------------------- /priv/repo/migrations/20170216184642_add_count_partial_results_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddCountPartialResultsToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :count_partial_results, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170327142610_add_mode_to_respondent_disposition_history.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddModeToRespondentDispositionHistory do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondent_disposition_history) do 6 | add :mode, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170508193540_add_questionnaire_quota_completed_steps.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuestionnaireQuotaCompletedSteps do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :quota_completed_steps, :longtext 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191213131858_remove_invalid_retry_stat_time_in_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveInvalidRetryStatTimeInRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "UPDATE respondents SET retry_stat_time = null WHERE CHAR_LENGTH(retry_stat_time) > 10" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /assets/js/reducers/ui/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux" 2 | import questionnaireEditor from "./questionnaireEditor" 3 | import surveyWizard from "./surveyWizard" 4 | 5 | const data = combineReducers({ 6 | questionnaireEditor, 7 | surveyWizard, 8 | }) 9 | 10 | export default combineReducers({ data }) 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161201181704_add_quota_completed_message_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuotaCompletedMessageToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :quota_completed_msg, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170308192425_add_mobile_web_sms_message_to_questionnaire.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddMobileWebSmsMessageToQuestionnaire do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :mobile_web_sms_message, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170406224331_add_mobileweb_retry_configuration_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddMobilewebRetryConfigurationToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :mobileweb_retry_configuration, :text 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180503032000_modify_survey_log_entry_action_data_to_long_text.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ModifySurveyLogEntryActionDataToLongText do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:survey_log_entries) do 6 | modify :action_data, :longtext 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210614154820_add_generates_panel_survey_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddGeneratesPanelSurveyToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :generates_panel_survey, :boolean, default: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/password/password_recovery_sent.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 | email 4 |
Password reset link sent
5 | We sent you an email with a password reset link. Please click that link to reset your password. 6 |
7 |
-------------------------------------------------------------------------------- /priv/repo/migrations/20160905173031_add_questionnaire_id_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuestionnaireIdToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170116175546_add_respondent_group_id_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRespondentGroupIdToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :respondent_group_id, references(:respondent_groups) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/iam.s3-backup-user-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "VisualEditor0", 6 | "Effect": "Allow", 7 | "Action": "s3:PutObject", 8 | "Resource": "arn:aws:s3:::instedd-backups/surveda-xx/*" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170503172812_add_snapshot_of_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSnapshotOfToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :snapshot_of, references(:questionnaires, on_delete: :nothing) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170601040741_index_survey_log_entries_by_survey_id_respondent_hash_and_id.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.IndexSurveyLogEntriesBySurveyIdRespondentHashAndId do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:survey_log_entries, [:survey_id, :respondent_hashed_number, :id]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190328182425_add_project_to_folders.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddProjectToFolders do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:folders) do 6 | add :project_id, references(:projects) 7 | end 8 | 9 | create index(:folders, [:project_id]) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190401140258_add_folder_id_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFolderIdToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :folder_id, references(:folders) 7 | end 8 | 9 | create index(:surveys, [:folder_id]) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/static/images/file_download_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /integration_tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.0' 2 | 3 | volumes: 4 | node_modules: 5 | 6 | services: 7 | cypress: 8 | image: cypress/base:12 9 | working_dir: /src 10 | volumes: 11 | - '.:/src' 12 | - 'node_modules:/src/node_modules' 13 | command: > 14 | /bin/sh -c 'yarn && npm run cypress:run' 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170505203058_create_respondents_index_by_hashed_number_and_updated_at.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRespondentsIndexByHashedNumberAndUpdatedAt do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create index(:respondents, [:hashed_number]) 6 | create index(:respondents, [:updated_at]) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /assets/vendor/js/materialize-dropdown-fix.js: -------------------------------------------------------------------------------- 1 | // This is a workaround until this PR is released in Materialize: https://github.com/Dogfalo/materialize/pull/6339 2 | // Suggested by @burnEAx: https://github.com/Dogfalo/materialize/issues/6336#issuecomment-523409695 3 | 4 | $(document).on('click', '.select-wrapper', function (e) { e.stopPropagation(); }) 5 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/registration/confirmation_sent.html.eex: -------------------------------------------------------------------------------- 1 |
2 |
3 | email 4 |
Thank you for signing up to Surveda
5 | We sent you an email with a confirmation link. Please confirm your account by clicking on that link. 6 |
7 |
-------------------------------------------------------------------------------- /priv/repo/migrations/20161109053659_add_retry_configuration_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRetryConfigurationToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :sms_retry_configuration, :text 7 | add :ivr_retry_configuration, :text 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161118175719_set_languages_and_default_language_to_existing_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetLanguagesAndDefaultLanguageToExistingQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!(~s(update questionnaires set languages = '["en"]', default_language = 'en')) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171023212559_create_short_links.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateShortLinks do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:short_links) do 6 | add :hash, :string 7 | add :name, :string 8 | add :target, :string 9 | 10 | timestamps() 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from "redux" 2 | import rootReducer from "../reducers" 3 | 4 | export default function configureStore(preState, middlewares = [], enhancers = []) { 5 | return createStore(rootReducer, preState, compose(applyMiddleware(...middlewares), ...enhancers)) 6 | } 7 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170417172528_add_mobile_web_survey_is_over_message_to_questionnaire.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddMobileWebSurveyIsOverMessageToQuestionnaire do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :mobile_web_survey_is_over_message, :string 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/vendor/css/materialize/components/_transitions.scss: -------------------------------------------------------------------------------- 1 | // Scale transition 2 | .scale-transition { 3 | &.scale-out { 4 | transform: scale(0); 5 | transition: transform .2s !important; 6 | } 7 | 8 | &.scale-in { 9 | transform: scale(1); 10 | } 11 | 12 | transition: transform .3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important; 13 | } -------------------------------------------------------------------------------- /lib/ask_web/plugs/sentry_context.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Plugs.SentryContext do 2 | import User.Helper 3 | 4 | def init(default), do: default 5 | 6 | def call(conn, _) do 7 | case current_user(conn) do 8 | nil -> :ok 9 | user -> Sentry.Context.set_user_context(%{email: user.email}) 10 | end 11 | 12 | conn 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161013170416_change_survey_schedule_start_and_end_to_time.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ChangeSurveyScheduleStartAndEndToTime do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | modify :schedule_start_time, :time 7 | modify :schedule_end_time, :time 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170109115814_add_questionnaire_and_mode_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddQuestionnaireAndModeToRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondents) do 6 | add :questionnaire_id, references(:questionnaires) 7 | add :mode, :string 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170117143633_add_salt_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSaltToProjects do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:projects) do 6 | add :salt, :string 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:projects) do 12 | remove :salt 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180110182428_add_floip_package_id_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFloipPackageIdToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :floip_package_id, :string 7 | end 8 | 9 | execute "UPDATE surveys SET floip_package_id = UUID();" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20201124112819_add_panel_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddPanelSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :panel_survey_of, references(:surveys, on_delete: :nothing) 7 | add :latest_panel_survey, :boolean, default: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/email/password.text.eex: -------------------------------------------------------------------------------- 1 | Hello <%= @name %>! 2 | 3 | Someone has requested a link to change your password, and you can do this through the link below. 4 | 5 | <%= @url %> 6 | 7 | If you didn't request this, please ignore this email. 8 | 9 | Your password won't change until you access the link above and create a new one. 10 | 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171004213434_add_survey_schedule.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSurveySchedule do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:surveys) do 6 | add :schedule, :text 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:surveys) do 12 | remove :schedule 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/js/components/surveys/PhoneNumberRow.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react" 2 | 3 | export const PhoneNumberRow = ({ id, phoneNumber }) => { 4 | return ( 5 | 6 | {phoneNumber} 7 | 8 | ) 9 | } 10 | 11 | PhoneNumberRow.propTypes = { 12 | id: PropTypes.string, 13 | phoneNumber: PropTypes.string, 14 | } 15 | -------------------------------------------------------------------------------- /assets/js/components/timezones/util.js: -------------------------------------------------------------------------------- 1 | export const formatTimezone = (tz) => { 2 | const split = (tz || "UTC").replace("_", " ").split("/") 3 | switch (split.length) { 4 | case 2: 5 | return `${split[0]} - ${split[1]}` 6 | case 3: 7 | return `${split[0]} - ${split[2]}, ${split[1]}` 8 | default: 9 | return split[0] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/js/components/ui/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react" 2 | import classNames from "classnames/bind" 3 | 4 | export const Card = ({ children, className }) => ( 5 |
{children}
6 | ) 7 | 8 | Card.propTypes = { 9 | children: PropTypes.node, 10 | className: PropTypes.string, 11 | } 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161118174623_add_languages_and_default_language_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLanguagesAndDefaultLanguageToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:questionnaires) do 6 | add :languages, :text 7 | add :default_language, :string 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170117190223_add_sample_and_respondents_count_to_respondent_group.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSampleAndRespondentsCountToRespondentGroup do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondent_groups) do 6 | add :sample, :string 7 | add :respondents_count, :integer 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers.csv: -------------------------------------------------------------------------------- 1 | (549) 11 4234 2343 2 | (549) 11 2421 3125 3 | (549) 11 1231 2323 4 | (549) 11 1241 2421 5 | (549) 11 4367 6723 6 | (549) 11 5432 1329 7 | (549) 11 6765 4335 8 | (549) 11 3264 3526 9 | (549) 11 9876 5424 10 | (549) 11 1234 5625 11 | (549) 11 2345 6721 12 | (549) 11 5434 5666 13 | (549) 11 2345 6432 14 | (549) 11 6765 4334 15 | -------------------------------------------------------------------------------- /test/support/audio_checker.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.AudioChecker do 2 | def get_audio_format(data) do 3 | path = "test/tmp/#{Ecto.UUID.generate()}.audio" 4 | File.write(path, data, [:binary]) 5 | 6 | try do 7 | %{^path => mime_type} = Ask.FileInfo.get_info(path) 8 | mime_type 9 | after 10 | File.rm(path) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161110191532_set_default_started_at_for_already_launched_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultStartedAtForAlreadyLaunchedSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query( 6 | "UPDATE surveys SET started_at = inserted_at WHERE state = 'completed' OR state = 'running'" 7 | ) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170116175403_create_respondent_group.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRespondentGroup do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:respondent_groups) do 6 | add :name, :string 7 | add :survey_id, references(:surveys, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /assets/js/decls/undo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type UndoStore = { 4 | undo: T[], 5 | redo: T[], 6 | } 7 | 8 | export type DataStoreWithUndo = DataStore & UndoStore 9 | 10 | export type UndoReducer = (state: ?DataStoreWithUndo, action: any) => DataStoreWithUndo 11 | 12 | export type UndoActions = { 13 | UNDO: string, 14 | REDO: string, 15 | } 16 | -------------------------------------------------------------------------------- /assets/js/reducers/respondentsStats.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/respondents" 2 | 3 | export default (state = {}, action) => { 4 | switch (action.type) { 5 | case actions.RECEIVE_RESPONDENTS_STATS: 6 | return { 7 | ...state, 8 | ...action.response.entities.respondents, 9 | } 10 | default: 11 | return state 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160929135859_add_schedule_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddScheduleToSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:surveys) do 6 | add :schedule_day_of_week, :integer 7 | add :schedule_start_time, :utc_datetime 8 | add :schedule_end_time, :utc_datetime 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/layout/email.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= @email.subject %> 4 | 5 | 6 | <%= render @view_module, @view_template, assigns %> 7 | 8 | 9 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161013162339_set_default_completed_at_for_completed_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultCompletedAtForCompletedRespondents do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query( 6 | "UPDATE respondents SET completed_at = updated_at WHERE state = 'completed' AND completed_at IS NULL" 7 | ) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20181022174941_add_description_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddDescriptionToSurveys do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:surveys) do 6 | add :description, :text 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:surveys) do 12 | remove :description 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20211213094856_add_index_to_respondents_canonical_phone_numbers.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddIndexToRespondentsCanonicalPhoneNumbers do 2 | use Ecto.Migration 3 | 4 | def up do 5 | create index(:respondents, :canonical_phone_number) 6 | end 7 | 8 | def down do 9 | drop index(:respondents, :canonical_phone_number) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/react-timeago 3 | /node_modules/csv-info 4 | /node_modules/draft-js 5 | /node_modules/react-i18next/src/shallowEqual.js 6 | [include] 7 | [libs] 8 | assets/js/decls/ 9 | [options] 10 | munge_underscores=true 11 | esproposal.optional_chaining=enable 12 | esproposal.nullish_coalescing=enable 13 | -------------------------------------------------------------------------------- /assets/js/reducers/surveyStats.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/survey" 2 | 3 | export default (state = {}, action) => { 4 | switch (action.type) { 5 | case actions.RECEIVE_SURVEY_STATS: 6 | return { 7 | ...state, 8 | surveyId: action.surveyId, 9 | stats: action.response, 10 | } 11 | default: 12 | return state 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/vendor/css/materialize/components/_ncdvariables.scss: -------------------------------------------------------------------------------- 1 | /* 1. materialize reset Variables 2 | ========================================================================== */ 3 | 4 | $primary-color: color("grey", "darken-3") !default; 5 | 6 | $secondary-color: color("green", "darken-2") !default; 7 | 8 | $input-error-color: #ff5722; 9 | 10 | $radio-fill-color: #000000; 11 | 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191223182932_modify-retry-stats-unique-index.exs: -------------------------------------------------------------------------------- 1 | defmodule :"Elixir.Ask.Repo.Migrations.Modify-retry-stats-unique-index" do 2 | use Ecto.Migration 3 | 4 | def change do 5 | drop unique_index(:retry_stats, [:mode, :attempt, :retry_time, :survey_id]) 6 | create unique_index(:retry_stats, [:mode, :attempt, :retry_time, :ivr_active, :survey_id]) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160816183915_create_project.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateProject do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:projects) do 6 | add :name, :string 7 | add :user_id, references(:users, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:projects, [:user_id]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170320182452_add_base_url_to_channel_and_oauth_tokens.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFriendlyNameToChannelAndOauthTokens do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:channels) do 6 | add :base_url, :string 7 | end 8 | 9 | alter table(:oauth_tokens) do 10 | add :base_url, :string 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180312173843_add_remote_ip_to_activity_log.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRemoteIpToActivityLog do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:activity_log) do 6 | add :remote_ip, :string 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:activity_log) do 12 | remove :remote_ip 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210512142133_create_panel_survey.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreatePanelSurvey do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:panel_surveys) do 6 | add(:name, :string) 7 | add(:project_id, references(:projects)) 8 | add(:folder_id, references(:folders)) 9 | 10 | timestamps() 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160830200454_create_survey.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateSurvey do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:surveys) do 6 | add :name, :string 7 | add :project_id, references(:projects, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:surveys, [:project_id]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170125165633_add_disposition_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddDispositionToRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:respondents) do 6 | add :disposition, :string 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:respondents) do 12 | remove :disposition 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170502170714_clear_session_and_timeout_at_for_failed_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ClearSessionAndTimeoutAtForFailedRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query!( 6 | "UPDATE respondents SET session = NULL, timeout_at = NULL WHERE state = 'failed'" 7 | ) 8 | end 9 | 10 | def down do 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /integration_tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "surveda_cypress", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "cypress:run": "cypress run --headless", 8 | "cypress:open": "cypress open" 9 | }, 10 | "dependencies": { 11 | "cypress": "^6.1.0", 12 | "cypress-file-upload": "^4.1.1", 13 | "route-parser": "^0.0.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey/404.html.eex: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 8 |
9 |
10 |

This survey is no longer available

11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # Ask.Repo.insert!(%Ask.SomeModel{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /test/ask/audio_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.AudioTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.Audio 5 | 6 | test "mime type" do 7 | assert "audio/mpeg" = %Audio{filename: "foo.mp3"} |> Audio.mime_type() 8 | assert "audio/wav" = %Audio{filename: "foo.wav"} |> Audio.mime_type() 9 | assert "application/octet-stream" = %Audio{filename: "foo.bar"} |> Audio.mime_type() 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/verboice.rancher-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db-migrate: 4 | scale: 1 5 | start_on_create: true 6 | asterisk-bridge: 7 | scale: 1 8 | start_on_create: true 9 | web: 10 | scale: 1 11 | start_on_create: true 12 | asterisk: 13 | scale: 1 14 | start_on_create: true 15 | broker: 16 | scale: 1 17 | start_on_create: true 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160906192317_create_survey_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateSurveyChannel do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:survey_channels) do 6 | add :survey_id, references(:surveys, on_delete: :nothing) 7 | add :channel_id, references(:channels, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161109034931_create_audios.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateAudios do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:audios) do 6 | add :uuid, :string 7 | add :data, :mediumblob 8 | add :filename, :string 9 | add :source, :string 10 | add :duration, :integer 11 | 12 | timestamps() 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161229171246_create_invite.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateInvite do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:invites) do 6 | add :code, :string 7 | add :level, :string 8 | add :project_id, references(:projects, on_delete: :delete_all) 9 | add :email, :string 10 | 11 | timestamps() 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170119123633_add_hashed_number_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddHashedNumberToRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:respondents) do 6 | add :hashed_number, :string 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:respondents) do 12 | remove :hashed_number 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191127152001_set_default_ended_at_for_already_finished_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultEndedAtForAlreadyFinishedSurveys do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query("UPDATE surveys SET ended_at = updated_at WHERE state = 'terminated'") 6 | end 7 | 8 | def down do 9 | # Do nothing since it doesn't make sense 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200122115222_add_description_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddDescriptionToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:questionnaires) do 6 | add :description, :text 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:questionnaires) do 12 | remove :description 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/ask/url_shortener_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.UrlShortenerTest do 2 | use ExUnit.Case 3 | alias Ask.UrlShortener 4 | 5 | test "build short url from shorter response" do 6 | body = """ 7 | {"key": "P9CKRx","url": "https://google.com"} 8 | """ 9 | 10 | short_url = UrlShortener.build_short_url("test.host", body) 11 | 12 | assert short_url == "test.host/P9CKRx" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/reducers/config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // This reducer handles static values for the state 4 | // These values are set once when the React app is served 5 | const initialState = { 6 | introMessage: "", 7 | colorStyle: {}, 8 | } 9 | 10 | export default (state: any = initialState, action: any) => { 11 | switch (action.type) { 12 | default: 13 | return state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey_simulation/404.html.eex: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 8 |
9 |
10 |

This survey is no longer available

11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/ask_web/views/user.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.UserView do 2 | use AskWeb, :view 3 | 4 | def render("settings.json", %{settings: settings}) do 5 | %{ 6 | data: %{ 7 | settings: settings 8 | } 9 | } 10 | end 11 | 12 | def render("error.json", %{changeset: changeset}) do 13 | %{ 14 | data: %{ 15 | changeset: changeset 16 | } 17 | } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/guisso/token_exchange.ex: -------------------------------------------------------------------------------- 1 | defmodule Guisso.OAuth.Strategy.TokenExchange do 2 | use OAuth2.Strategy 3 | 4 | def get_token(client, params, _headers) do 5 | client 6 | |> put_param(:grant_type, "token_exchange") 7 | |> put_param(:client_id, client.client_id) 8 | |> put_param(:client_secret, client.client_secret) 9 | |> put_param(:access_token, params[:access_token]) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/js/reducers/surveyRetriesHistograms.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/survey" 2 | 3 | export default (state = {}, action) => { 4 | switch (action.type) { 5 | case actions.RECEIVE_SURVEY_RETRIES_HISTOGRAMS: 6 | return { 7 | ...state, 8 | surveyId: action.surveyId, 9 | histograms: action.response, 10 | } 11 | default: 12 | return state 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/ask_web/plugs/not_allowed_error.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.ConflictError do 2 | defexception plug_status: 405, message: "not allowed", conn: nil 3 | 4 | def exception(_) do 5 | %AskWeb.ConflictError{} 6 | end 7 | end 8 | 9 | defimpl Plug.Exception, for: AskWeb.ConflictError do 10 | def status(_), do: 409 11 | def actions(_), do: [%{label: "ConflictError", handler: {IO, :puts, "ConflictError"}}] 12 | end 13 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160909144711_create_respondent.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRespondent do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:respondents) do 6 | add :phone_number, :string 7 | add :survey_id, references(:surveys, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:respondents, [:survey_id]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161220152839_remove_user_id_from_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveUserIdFromProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.transaction(fn -> 6 | Ask.Repo.query!("ALTER TABLE projects DROP FOREIGN KEY projects_user_id_fkey") 7 | 8 | alter table(:projects) do 9 | remove :user_id 10 | end 11 | end) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170125151039_set_default_comparisons_on_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultComparisonsOnSurveys do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query("UPDATE surveys SET comparisons = '[]' WHERE comparisons IS NULL") 6 | end 7 | 8 | def down do 9 | Ask.Repo.query("UPDATE surveys SET comparisons = NULL WHERE comparisons = '[]'") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/js/reducers/guisso.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/guisso" 2 | 3 | export default (state = {}, action) => { 4 | switch (action.type) { 5 | case actions.GUISSO_TOKEN: 6 | return guissoToken(state, action) 7 | default: 8 | return state 9 | } 10 | } 11 | 12 | const guissoToken = (state, action) => ({ 13 | ...state, 14 | [action.app]: { 15 | ...action.token, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160914194540_modify_questionnaires_steps_to_longtext.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ModifyQuestionnairesStepsToLongtext do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:questionnaires) do 6 | modify :steps, :longtext 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:questionnaires) do 12 | modify :steps, :text 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200211131441_set_default_respondents_canonical_phone_number.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultRespondentsCanonicalPhoneNumber do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "UPDATE respondents SET canonical_phone_number = sanitized_phone_number" 6 | end 7 | 8 | def down do 9 | execute "UPDATE respondents SET canonical_phone_number = NULL" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/support/log_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.LogHelper do 2 | defmacro __using__(_) do 3 | quote do 4 | import Ask.LogHelper 5 | end 6 | end 7 | 8 | defmacro without_logging(do: block) do 9 | quote do 10 | Logger.remove_backend(:console) 11 | 12 | try do 13 | unquote(block) 14 | after 15 | Logger.add_backend(:console) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /assets/css/app.scss: -------------------------------------------------------------------------------- 1 | @import "fonts"; 2 | 3 | @import "materialize/materialize"; 4 | @import "reset-materialize"; 5 | 6 | // NCD 7 | @import "charts"; 8 | @import "layout"; 9 | @import "card-modal"; 10 | @import "card-table"; 11 | @import "buttons"; 12 | @import "forms"; 13 | @import "sections"; 14 | @import "scrollto"; 15 | @import "global"; 16 | @import "welcome"; 17 | @import "responsive"; 18 | @import "quex-simulation"; 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170117191259_modify_respondent_group_sample_to_long_text.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ModifyRespondentGroupSampleToLongText do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:respondent_groups) do 6 | modify :sample, :longtext 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:respondent_groups) do 12 | modify :sample, :string 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20230217143550_add_settings_to_projects.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSettingsToProjects do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:projects) do 6 | add :timezone, :string 7 | add :initial_success_rate, :float 8 | add :eligibility_rate, :float 9 | add :response_rate, :float 10 | add :valid_respondent_rate, :float 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/ask/project_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.ProjectTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.Project 5 | 6 | @valid_attrs %{name: "some content"} 7 | 8 | test "changeset with valid attributes" do 9 | changeset = Project.changeset(%Project{}, @valid_attrs) 10 | assert changeset.valid? 11 | end 12 | 13 | test "default value for archived is false" do 14 | assert %Project{}.archived == false 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /assets/js/components/charts/utils.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3" 2 | 3 | export function percentFormat(number) { 4 | return d3.format(".1%")(number) 5 | } 6 | 7 | export function numberFormat(number) { 8 | return number < 0.1 ? d3.format(".1%")(number) : d3.format(".0%")(number) 9 | } 10 | 11 | export function labelFormat(number) { 12 | return number > 0 && number < 1 ? d3.format(".2f")(number) : d3.format(".0f")(number) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /lib/ask_web/plugs/unauthorized_error.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.UnauthorizedError do 2 | defexception plug_status: 403, message: "unauthorized", conn: nil 3 | 4 | def exception(_) do 5 | %AskWeb.UnauthorizedError{} 6 | end 7 | end 8 | 9 | defimpl Plug.Exception, for: AskWeb.UnauthorizedError do 10 | def status(_), do: 403 11 | def actions(_), do: [%{label: "UnauthorizedError", handler: {IO, :puts, "UnauthorizedError"}}] 12 | end 13 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191212203052_set_default_retry_stat_time_to_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultRetryStatTimeToRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "UPDATE respondents SET retry_stat_time = timeout_at WHERE timeout_at IS NOT NULL AND retry_stat_time IS NULL" 6 | end 7 | 8 | def down do 9 | execute "UPDATE respondents SET retry_stat_time = NULL" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20230413101342_remove_mobile_web_cookie_code_from_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveMobileWebCookieCodeFromRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:respondents) do 6 | remove :mobile_web_cookie_code 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:respondents) do 12 | add :mobile_web_cookie_code, :string 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160914174501_create_response.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateResponse do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:responses) do 6 | add :field_name, :string 7 | add :value, :string 8 | add :respondent_id, references(:respondents, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | 13 | create index(:responses, [:respondent_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161124170829_create_quota_buckets.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateQuotaBuckets do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:quota_buckets) do 6 | add :condition, :text 7 | add :quota, :integer 8 | add :count, :integer 9 | add :survey_id, references(:surveys, on_delete: :delete_all, on_update: :update_all) 10 | 11 | timestamps() 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161219200232_create_project_membership.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateProjectMembership do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:project_memberships) do 6 | add :user_id, references(:users, on_delete: :delete_all) 7 | add :project_id, references(:projects, on_delete: :delete_all) 8 | add :level, :string 9 | 10 | timestamps() 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200420135544_add_partial_relevant_config_to_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddPartialRelevantConfigToQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:questionnaires) do 6 | add :partial_relevant_config, :string 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:questionnaires) do 12 | remove :partial_relevant_config 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/components/Prompt.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component, PropTypes } from "react" 3 | 4 | type Props = { 5 | text: string, 6 | } 7 | 8 | class Prompt extends Component { 9 | render() { 10 | const { text } = this.props 11 | return ( 12 |
13 |

14 |

15 | ) 16 | } 17 | } 18 | 19 | export default Prompt 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160902185110_create_questionnaire.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateQuestionnaire do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:questionnaires) do 6 | add :name, :string 7 | add :description, :string 8 | add :project_id, references(:projects, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | 13 | create index(:questionnaires, [:project_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160924055948_create_oauth_token.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateOAuthToken do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:oauth_tokens) do 6 | add :provider, :string 7 | add :access_token, :map 8 | add :user_id, references(:users, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | 13 | create unique_index(:oauth_tokens, [:user_id, :provider]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161221101027_remove_questionnaire_id_from_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveQuestionnaireIdFromSurveys do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!("update surveys set questionnaire_id = null") 6 | Ask.Repo.query!("alter table surveys drop foreign key surveys_questionnaire_id_fkey") 7 | 8 | alter table(:surveys) do 9 | remove :questionnaire_id 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /integration_tests/cypress/integration/projects.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('projects', () => { 4 | beforeEach(() => { 5 | cy.loginGuisso(Cypress.env('email'), Cypress.env('password')) 6 | }) 7 | 8 | it('show surveys by default', () => { 9 | const projectId = Cypress.env('project_id') 10 | cy.visit(`/projects/${projectId}`) 11 | 12 | cy.url().should('include', `/projects/${projectId}/surveys`) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /lib/ask/session.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Session do 2 | use Ask.Model 3 | 4 | schema "sessions" do 5 | field :token, :string 6 | field :user_type, :string 7 | field :user_id, :string 8 | timestamps() 9 | end 10 | 11 | @fields ~w(token user_type user_id)a 12 | 13 | def changeset(model, params \\ %{}) do 14 | model 15 | |> cast(params, @fields) 16 | |> validate_required(@fields) 17 | |> unique_constraint(:token) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170202175824_create_sessions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateSessionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:sessions) do 6 | add :token, :string, unique: true 7 | add :user_type, :string 8 | add :user_id, :string 9 | 10 | timestamps() 11 | end 12 | 13 | create unique_index(:sessions, [:token]) 14 | create index(:sessions, [:user_id]) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170420194102_change_questionnaire_mobile_web_sms_message_to_text.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ChangeQuestionnaireMobileWebSmsMessageToText do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:questionnaires) do 6 | modify :mobile_web_sms_message, :text 7 | end 8 | end 9 | 10 | def down do 11 | alter table(:questionnaires) do 12 | modify :mobile_web_sms_message, :string 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190621174448_add_survey_id_and_respondent_hashed_number_to_respondent_disposition_history.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddSurveyIdAndRespondentHashedNumberToRespondentDispositionHistory do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:respondent_disposition_history) do 6 | add :survey_id, references(:surveys, on_delete: :delete_all) 7 | add :respondent_hashed_number, :string 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210326160205_fill_last_window_ends_at_in_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.FillLastWindowEndsAtInSurveys do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query("UPDATE surveys SET last_window_ends_at = ended_at WHERE ended_at IS NOT NULL") 6 | end 7 | 8 | def down do 9 | Ask.Repo.query( 10 | "UPDATE surveys SET last_window_ends_at = null WHERE last_window_ends_at IS NOT NULL" 11 | ) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /assets/js/debounce.js: -------------------------------------------------------------------------------- 1 | export default (func, wait, immediate) => { 2 | let timeout 3 | return function () { 4 | const context = this 5 | const args = arguments 6 | const later = function () { 7 | timeout = null 8 | if (!immediate) func.apply(context, args) 9 | } 10 | const callNow = immediate && !timeout 11 | clearTimeout(timeout) 12 | timeout = setTimeout(later, wait) 13 | if (callNow) func.apply(context, args) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/ask/model.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Model do 2 | defmacro __using__(_) do 3 | quote do 4 | use Ecto.Schema 5 | 6 | import Ecto 7 | import Ecto.Changeset 8 | import Ecto.Query 9 | 10 | # Avoid microseconds. Mysql doesn't support them. 11 | # See [usec in datetime](https://hexdocs.pm/ecto_sql/Ecto.Adapters.MyXQL.html#module-usec-in-datetime) 12 | @timestamps_opts [type: :utc_datetime, usec: false] 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/email/unlock.html.eex: -------------------------------------------------------------------------------- 1 |
2 |

<%= dgettext "coherence", "Hello %{name}!", name: @name %>

3 |

4 | <%= dgettext "coherence", "You requested unlock instructions for your locked account. Please click the link below to unlock your account." %> 5 |

6 |

7 | <%= dgettext "coherence", "Unlock my Account" %> 8 |

9 |

<%= dgettext "coherence", "Thank you!" %>

10 |
11 | 12 | -------------------------------------------------------------------------------- /lib/mix/tasks/ecto_dump.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Ask.EctoDump do 2 | @moduledoc "" 3 | @shortdoc "" 4 | 5 | use Mix.Task 6 | 7 | @impl Mix.Task 8 | def run(_args) do 9 | if Mix.env() == :dev do 10 | Mix.Tasks.Ecto.Dump.run([]) 11 | 12 | sql = File.read!("priv/repo/structure.sql") 13 | sql = String.replace(sql, ~r/ AUTO_INCREMENT=\d+ /, " ", [global: true]) 14 | File.write!("priv/repo/structure.sql", sql) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170119153421_remove_survey_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveSurveyChannels do 2 | use Ecto.Migration 3 | 4 | def up do 5 | drop table(:survey_channels) 6 | end 7 | 8 | def down do 9 | create table(:survey_channels) do 10 | add :survey_id, references(:surveys, on_delete: :nothing) 11 | add :channel_id, references(:channels, on_delete: :nothing) 12 | 13 | timestamps() 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170125165933_set_disposition_as_completed_where_applies_in_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDispositionAsCompletedWhereAppliesInRespondents do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query("UPDATE respondents SET disposition = 'completed' WHERE state = 'completed'") 6 | end 7 | 8 | def down do 9 | Ask.Repo.query("UPDATE respondents SET disposition = NULL WHERE state = 'completed'") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/ask/completed_respondents.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.CompletedRespondents do 2 | use Ask.Model 3 | 4 | @primary_key false 5 | schema "completed_respondents" do 6 | belongs_to :survey, Ask.Survey, primary_key: true 7 | field :questionnaire_id, :integer, primary_key: true 8 | field :quota_bucket_id, :integer, primary_key: true 9 | field :mode, :string, primary_key: true 10 | field :date, :date, primary_key: true 11 | field :count, :integer 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160905135419_create_channel.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateChannel do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:channels) do 6 | add :name, :string 7 | add :type, :string 8 | add :provider, :string 9 | add :settings, :map 10 | add :user_id, references(:users, on_delete: :nothing) 11 | 12 | timestamps() 13 | end 14 | 15 | create index(:channels, [:user_id]) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161115202151_set_default_schedule_start_and_end_times.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultScheduleStartAndEndTimes do 2 | use Ecto.Migration 3 | 4 | def change do 5 | Ask.Repo.query!( 6 | "UPDATE surveys SET schedule_start_time = '09:00:00' WHERE schedule_start_time IS NULL" 7 | ) 8 | 9 | Ask.Repo.query!( 10 | "UPDATE surveys SET schedule_end_time = '18:00:00' WHERE schedule_end_time IS NULL" 11 | ) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200212200209_add-user-stopped-to-respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule :"Elixir.Ask.Repo.Migrations.Add-user-stopped-to-respondents" do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:respondents), do: add(:user_stopped, :boolean, null: false, default: false) 6 | alter table(:respondents), do: modify(:user_stopped, :boolean, null: false) 7 | end 8 | 9 | def down do 10 | alter table(:respondents), do: remove(:user_stopped) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/fallback_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.FallbackController do 2 | use Phoenix.Controller 3 | 4 | def call(conn, {:error, :not_found}) do 5 | conn 6 | |> put_status(:not_found) 7 | |> put_view(AskWeb.ErrorView) 8 | |> render(:"404") 9 | end 10 | 11 | def call(conn, {:error, :invalid_simulation}), 12 | do: 13 | conn 14 | |> put_status(:bad_request) 15 | |> put_view(AskWeb.ErrorView) 16 | |> render(:"400") 17 | end 18 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey/index.html.eex: -------------------------------------------------------------------------------- 1 | 2 |
3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160908162553_create_questionnaire_step.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateQuestionnaireStep do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:questionnaire_steps) do 6 | add :type, :string 7 | add :settings, :map 8 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | 13 | create index(:questionnaire_steps, [:questionnaire_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160915191129_rename_survey_state_default_to_not_ready.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RenameSurveyStateDefaultToNotReady do 2 | use Ecto.Migration 3 | import Ecto.Query 4 | 5 | def up do 6 | from(s in Ask.Survey, where: s.state == :pending) 7 | |> Ask.Repo.update_all(set: [state: :not_ready]) 8 | end 9 | 10 | def down do 11 | from(s in Ask.Survey, where: s.state == :not_ready) 12 | |> Ask.Repo.update_all(set: [state: :pending]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170206162219_create_respondent_disposition_history.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRespondentDispositionHistory do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:respondent_disposition_history) do 6 | add :disposition, :string 7 | add :respondent_id, references(:respondents, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:respondent_disposition_history, [:respondent_id]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180315000255_create_project_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateProjectChannels do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:project_channels) do 6 | add :channel_id, references(:channels, on_delete: :delete_all) 7 | add :project_id, references(:projects, on_delete: :delete_all) 8 | 9 | timestamps() 10 | end 11 | 12 | create unique_index(:project_channels, [:channel_id, :project_id]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/ask/bom_parser.ex: -------------------------------------------------------------------------------- 1 | # Parser and stripper of BOM (byte order mark) 2 | defmodule Ask.BomParser do 3 | def parse(string) do 4 | case :unicode.bom_to_encoding(string) do 5 | {_, 0} -> 6 | string 7 | 8 | {encoding, bom_size} -> 9 | # Strip BOM 10 | bom_bits = bom_size * 8 11 | <<_bom::size(bom_bits), string::binary>> = string 12 | 13 | # Convert to UTF-8 14 | :unicode.characters_to_binary(string, encoding) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/ask_web/views/oauth_client_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.OAuthClientView do 2 | use AskWeb, :view 3 | 4 | def render("index.json", %{authorizations: auths}) do 5 | %{ 6 | data: 7 | auths 8 | |> Enum.map(fn auth -> 9 | %{ 10 | provider: auth.provider, 11 | baseUrl: auth.base_url 12 | } 13 | end) 14 | } 15 | end 16 | 17 | def render("ui_token.json", %{token: token}) do 18 | token.access_token 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /assets/js/reducers/invite.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/invites" 2 | 3 | const initialState = { 4 | fetching: false, 5 | data: null, 6 | } 7 | 8 | export default (state = initialState, action) => { 9 | switch (action.type) { 10 | case actions.RECEIVE_INVITE: 11 | return receiveInvite(state, action) 12 | default: 13 | return state 14 | } 15 | } 16 | 17 | const receiveInvite = (state, action) => { 18 | return { 19 | ...state, 20 | data: action.data, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/audio_delivery_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.AudioDeliveryController do 2 | use AskWeb, :controller 3 | 4 | alias Ask.Audio 5 | 6 | def show(conn, %{"id" => uuid}) do 7 | audio = Repo.get_by!(Audio, uuid: uuid) 8 | 9 | conn 10 | |> put_resp_header("content-type", Audio.mime_type(audio)) 11 | |> put_resp_header("content-disposition", "attachment; filename=#{audio.filename}") 12 | |> put_cache_headers() 13 | |> send_resp(200, audio.data) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20210325194736_fill_first_window_started_at_in_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.FillFirstWindowStartedAtInSurveys do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query( 6 | "UPDATE surveys SET first_window_started_at = started_at WHERE started_at IS NOT NULL" 7 | ) 8 | end 9 | 10 | def down do 11 | Ask.Repo.query( 12 | "UPDATE surveys SET first_window_started_at = null WHERE first_window_started_at IS NOT NULL" 13 | ) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/vendor/css/materialize/components/forms/_forms.scss: -------------------------------------------------------------------------------- 1 | // Remove Focus Boxes 2 | select:focus { 3 | outline: $select-focus; 4 | } 5 | 6 | button:focus { 7 | outline: none; 8 | background-color: $button-background-focus; 9 | } 10 | 11 | label { 12 | font-size: $label-font-size; 13 | color: $input-border-color; 14 | } 15 | 16 | @import 'input-fields'; 17 | @import 'radio-buttons'; 18 | @import 'checkboxes'; 19 | @import 'switches'; 20 | @import 'select'; 21 | @import 'file-input'; 22 | @import 'range'; 23 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180302023759_create_activity_log.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateActivityLog do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:activity_log) do 6 | add :project_id, references(:projects, on_delete: :nothing) 7 | add :user_id, references(:users, on_delete: :nothing) 8 | add :entity_type, :string 9 | add :entity_id, :integer 10 | add :action, :string 11 | add :metadata, :text 12 | 13 | timestamps() 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /assets/js/actions/autoSaveStatus.js: -------------------------------------------------------------------------------- 1 | export const RECEIVE = "AUTOSAVE_RECEIVE" 2 | export const SAVED = "AUTOSAVE_SAVED" 3 | export const SAVING = "AUTOSAVE_SAVING" 4 | export const ERROR = "AUTOSAVE_ERROR" 5 | 6 | export const receive = (entity) => ({ 7 | type: RECEIVE, 8 | data: entity, 9 | }) 10 | 11 | export const saved = (entity) => ({ 12 | type: SAVED, 13 | data: entity, 14 | }) 15 | 16 | export const saving = () => ({ 17 | type: SAVING, 18 | }) 19 | 20 | export const error = () => ({ 21 | type: ERROR, 22 | }) 23 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/components/layout/Root.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from "react" 2 | import { Provider } from "react-redux" 3 | import Step from "../Step" 4 | 5 | export default class Root extends Component { 6 | static propTypes = { 7 | store: PropTypes.object.isRequired, 8 | } 9 | 10 | render() { 11 | const { store } = this.props 12 | return ( 13 | 14 |
15 | 16 |
17 |
18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/proxy.docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | lb: 4 | image: rancher/lb-service-haproxy:v0.7.15 5 | ports: 6 | - 80:80/tcp 7 | - 443:443/tcp 8 | labels: 9 | io.rancher.container.agent.role: environmentAdmin,agent 10 | io.rancher.container.agent_service.drain_provider: 'true' 11 | io.rancher.container.create_agent: 'true' 12 | redirect-to-https: 13 | image: instedd/redirect-to-https 14 | labels: 15 | io.rancher.container.pull_image: always 16 | -------------------------------------------------------------------------------- /test/fixtures/respondent_phone_numbers_duplicated.csv: -------------------------------------------------------------------------------- 1 | (549) 11 4234 2343 2 | (549) 11 2421 3125 3 | (549) 11 1231 2323 4 | (549) 11 1241 2421 5 | (549) 11 4367 6723 6 | (549) 11 5432 1329 7 | (549) 11 6765 4335 8 | (549) 11 3264 3526 9 | (549) 11 9876 5424 10 | (549) 11 1234 5625 11 | (549) 11 2345 6721 12 | (549) 11 5434 5666 13 | (549) 11 2345 6432 14 | (549) 11 6765 4334 15 | (549) 11 1231 2323 16 | (549) 11 1241 2421 17 | (549) 11 4367 6023 18 | (549) 11 9876 5424 19 | (549) 11 1234 5625 20 | (549) 11 2345 1223 21 | (549) 11 5434 5666 22 | -------------------------------------------------------------------------------- /assets/js/components/questionnaires/withQuestionnaire.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { connect } from "react-redux" 3 | 4 | const withQuestionnaire = (ComponentClass) => (props: any) => { 5 | if (props.questionnaire) { 6 | return 7 | } else { 8 | return null 9 | } 10 | } 11 | 12 | const mapStateToProps = (state, ownProps) => ({ 13 | questionnaire: state.questionnaire.data, 14 | }) 15 | 16 | export default (ComponentClass) => connect(mapStateToProps)(withQuestionnaire(ComponentClass)) 17 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey_simulation/index.html.eex: -------------------------------------------------------------------------------- 1 | 2 |
3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170207152142_modify_translation_source_text_and_target_text_to_long_text.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ModifyTranslationSourceTextAndTargetTextToLongText do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:translations) do 6 | modify :source_text, :longtext 7 | modify :target_text, :longtext 8 | end 9 | end 10 | 11 | def down do 12 | alter table(:translations) do 13 | modify :source_text, :string 14 | modify :target_text, :string 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /assets/js/reducers/respondentsCount.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/respondents" 2 | 3 | export default (state = {}, action) => { 4 | switch (action.type) { 5 | case actions.CREATE_RESPONDENT: 6 | case actions.UPDATE_RESPONDENT: 7 | return 1 8 | case actions.RECEIVE_RESPONDENTS: 9 | if (action.response) { 10 | return action.response.respondentsCount 11 | } 12 | return state 13 | case actions.REMOVE_RESPONDENTS: 14 | return 0 15 | default: 16 | return state 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/ask/questionnaire_variable.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.QuestionnaireVariable do 2 | use Ask.Model 3 | 4 | schema "questionnaire_variables" do 5 | field :name, :string 6 | belongs_to :project, Ask.Project 7 | belongs_to :questionnaire, Ask.Questionnaire 8 | 9 | timestamps() 10 | end 11 | 12 | @doc """ 13 | Builds a changeset based on the `struct` and `params`. 14 | """ 15 | def changeset(struct, params \\ %{}) do 16 | struct 17 | |> cast(params, [:name]) 18 | |> validate_required([:name]) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191011174319_create_retry_stats.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRetryStats do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:retry_stats) do 6 | add :mode, :string 7 | add :attempt, :integer 8 | add :retry_time, :string 9 | add :count, :integer 10 | add :survey_id, references(:surveys, on_delete: :delete_all) 11 | 12 | timestamps() 13 | end 14 | 15 | create unique_index(:retry_stats, [:mode, :attempt, :retry_time, :survey_id]) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/ask/response_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.ResponseTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.Response 5 | 6 | @valid_attrs %{field_name: "some content", value: "some content"} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = Response.changeset(%Response{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = Response.changeset(%Response{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/ask/config_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.ConfigHelper do 2 | def get_config(module, config_key, sys_config_mapper \\ fn value -> value end) do 3 | case Application.get_env(:ask, module)[config_key] do 4 | {:system, env_var} -> 5 | sys_config_mapper.(System.get_env(env_var)) 6 | 7 | {:system, env_var, default} -> 8 | env_value = System.get_env(env_var) 9 | if env_value, do: sys_config_mapper.(env_value), else: default 10 | 11 | simple_config -> 12 | simple_config 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160909150600_drop_questionnaire_steps.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.DropQuestionnaireSteps do 2 | use Ecto.Migration 3 | 4 | def up do 5 | drop table(:questionnaire_steps) 6 | end 7 | 8 | def down do 9 | create table(:questionnaire_steps) do 10 | add :type, :string 11 | add :settings, :map 12 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 13 | 14 | timestamps() 15 | end 16 | 17 | create index(:questionnaire_steps, [:questionnaire_id]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/ask/survey_questionnaire.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.SurveyQuestionnaire do 2 | use Ask.Model 3 | 4 | schema "survey_questionnaires" do 5 | belongs_to :survey, Ask.Survey 6 | belongs_to :questionnaire, Ask.Questionnaire 7 | 8 | timestamps() 9 | end 10 | 11 | def changeset(struct, params \\ %{}) do 12 | struct 13 | |> cast(params, [:survey_id, :questionnaire_id]) 14 | |> validate_required([:survey_id, :questionnaire_id]) 15 | |> foreign_key_constraint(:survey) 16 | |> foreign_key_constraint(:questionnaire) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161221100014_create_survey_questionnaire.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateSurveyQuestionnaire do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:survey_questionnaires) do 6 | add :survey_id, references(:surveys, on_delete: :nothing) 7 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:survey_questionnaires, [:survey_id]) 13 | create index(:survey_questionnaires, [:questionnaire_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170818184001_add_default_disposition_to_rejected_respondents.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddDefaultDispositionToRejectedRespondents do 2 | use Ecto.Migration 3 | alias Ask.Repo 4 | 5 | def up do 6 | Repo.transaction(fn -> 7 | Repo.query!( 8 | """ 9 | UPDATE respondents 10 | SET disposition = 'rejected' 11 | WHERE state = 'rejected' 12 | AND disposition IS NULL 13 | """, 14 | [] 15 | ) 16 | end) 17 | end 18 | 19 | def down do 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey/unauthorized.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 |
7 |
8 | 11 |
12 |
13 |

You are not authorized to participate in the survey

14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/ask_web/views/coherence/session_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.SessionView do 2 | use AskWeb.Coherence, :view 3 | 4 | def render("session.json", %{user: user}) do 5 | %{ 6 | user: %{ 7 | id: user.id, 8 | name: user.name, 9 | email: user.email 10 | } 11 | } 12 | end 13 | 14 | def render("error.json", %{error: error}) do 15 | %{ 16 | error: error 17 | } 18 | end 19 | 20 | def render("error.json", _opts) do 21 | %{ 22 | error: "Invalid credentials" 23 | } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /priv/repo/migrations/20160913180125_set_default_state_for_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultStateForSurveys do 2 | use Ecto.Migration 3 | import Ecto.Query 4 | 5 | def up do 6 | alter table(:surveys) do 7 | modify :state, :string, default: :pending 8 | end 9 | 10 | flush() 11 | 12 | from(s in Ask.Survey, where: is_nil(s.state)) 13 | |> Ask.Repo.update_all(set: [state: :pending]) 14 | end 15 | 16 | def down do 17 | alter table(:surveys) do 18 | modify :state, :string 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/timezone_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.TimezoneController do 2 | use AskWeb, :api_controller 3 | 4 | def timezones(conn, _) do 5 | # only contains the valid (non-deprecated) timezones 6 | canonical_links = Tzdata.canonical_zone_list() |> Enum.map(fn tz -> {tz, tz} end) |> Map.new() 7 | # only contains the deprecated timezones with its links to the 'new' timezones 8 | links = Tzdata.links() 9 | 10 | timezones = Map.merge(canonical_links, links) 11 | render(conn, "index.json", timezones: timezones) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/ask/oauth_token_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.OAuthTokenTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.OAuthToken 5 | 6 | @valid_attrs %{access_token: %{}, provider: "some content", user_id: 1} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = OAuthToken.changeset(%OAuthToken{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = OAuthToken.changeset(%OAuthToken{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/ask_web/templates/mobile_survey_simulation/unauthorized.html.eex: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 |
7 |
8 | 11 |
12 |
13 |

You are not authorized to participate in the survey

14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /test/support/survey_stub.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Runtime.SurveyStub do 2 | use GenServer 3 | 4 | @server_ref {:global, __MODULE__} 5 | def server_ref, do: @server_ref 6 | 7 | def init(args) do 8 | {:ok, args} 9 | end 10 | 11 | def sync_step(respondent, reply, mode) do 12 | GenServer.call(@server_ref, {:sync_step, respondent, reply, mode}) 13 | end 14 | 15 | def handle_cast({:expects, matcher}, _) do 16 | {:noreply, matcher} 17 | end 18 | 19 | def handle_call(call, _from, matcher) do 20 | {:reply, matcher.(call), matcher} 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /assets/js/actions/guisso.js: -------------------------------------------------------------------------------- 1 | export const GUISSO_TOKEN = "GUISSO_TOKEN" 2 | 3 | export const obtainToken = (guissoSession) => { 4 | return (dispatch, getState) => { 5 | const existingToken = getState().guisso[guissoSession.config.appId] 6 | if (existingToken) { 7 | return Promise.resolve(existingToken) 8 | } 9 | 10 | return guissoSession.authorize("token").then((token) => { 11 | dispatch({ 12 | type: GUISSO_TOKEN, 13 | app: guissoSession.config.appId, 14 | token, 15 | }) 16 | return token 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170113200644_add_coherence_to_user.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddCoherenceToUser do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:users) do 6 | # authenticatable 7 | add :password_hash, :string 8 | # recoverable 9 | add :reset_password_token, :string 10 | add :reset_password_sent_at, :naive_datetime 11 | # confirmable 12 | add :confirmation_token, :string 13 | add :confirmed_at, :naive_datetime 14 | add :confirmation_sent_at, :naive_datetime 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170119152252_create_respondent_group_channel.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateRespondentGroupChannel do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:respondent_group_channels) do 6 | add :respondent_group_id, references(:respondent_groups, on_delete: :nothing) 7 | add :channel_id, references(:channels, on_delete: :nothing) 8 | 9 | timestamps() 10 | end 11 | 12 | create index(:respondent_group_channels, [:respondent_group_id]) 13 | create index(:respondent_group_channels, [:channel_id]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /assets/js/components/integrations/IntegrationRow.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from "react" 3 | 4 | type Props = { 5 | integration: Integration, 6 | } 7 | 8 | class IntegrationRow extends Component { 9 | render() { 10 | const { integration } = this.props 11 | 12 | return ( 13 | 14 | {integration.name} 15 | {integration.uri} 16 | {integration.authToken} 17 | {integration.state} 18 | 19 | ) 20 | } 21 | } 22 | 23 | export default IntegrationRow 24 | -------------------------------------------------------------------------------- /lib/ask/db_session_store.ex: -------------------------------------------------------------------------------- 1 | # lib/my_project/db_store.ex 2 | defimpl Coherence.DbStore, for: Ask.User do 3 | alias Ask.Repo 4 | alias Ask.EctoDbSession 5 | 6 | def get_user_data(user, creds, id_key), 7 | do: EctoDbSession.get_user_data(Repo, user, creds, id_key) 8 | 9 | def put_credentials(user, creds, id_key), 10 | do: EctoDbSession.put_credentials(Repo, user, creds, id_key) 11 | 12 | def delete_credentials(user, creds), 13 | do: EctoDbSession.delete_credentials(user, creds) 14 | 15 | def delete_user_logins(user), 16 | do: EctoDbSession.delete_user_logins(user) 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161219204505_create_questionnaire_variable.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateQuestionnaireVariable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:questionnaire_variables) do 6 | add :name, :string 7 | add :project_id, references(:projects, on_delete: :nothing) 8 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 9 | 10 | timestamps() 11 | end 12 | 13 | create index(:questionnaire_variables, [:project_id]) 14 | create index(:questionnaire_variables, [:questionnaire_id]) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /assets/js/app.jsx: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill" 2 | import "jquery" 3 | import "./errors" 4 | import React from "react" 5 | import { render } from "react-dom" 6 | import { browserHistory } from "react-router" 7 | import { syncHistoryWithStore } from "react-router-redux" 8 | import Root from "./components/layout/Root" 9 | import configureStore from "./store/configureStore" 10 | 11 | const store = configureStore() 12 | const history = syncHistoryWithStore(browserHistory, store) 13 | 14 | const root = document.getElementById("root") 15 | if (root) { 16 | render(, root) 17 | } 18 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/mobileSurvey.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import { render } from "react-dom" 4 | import Root from "./components/layout/Root" 5 | import configureStore from "./store/configureStore" 6 | 7 | const root = document.getElementById("root") 8 | if (root) { 9 | const configAttr = root.getAttribute("data-config") 10 | if (configAttr) { 11 | const config = JSON.parse(configAttr) 12 | const store = configureStore({ config }) 13 | render(, root) 14 | } else { 15 | console.error("Missing data-config in root element", root) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170224213051_create_survey_log_entry.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateSurveyLogEntry do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:survey_log_entries) do 6 | add :survey_id, :integer 7 | add :mode, :string 8 | add :respondent_id, :integer 9 | add :respondent_hashed_number, :string 10 | add :channel_id, :integer 11 | add :disposition, :string 12 | add :action_type, :string 13 | add :action_data, :string 14 | add :timestamp, :naive_datetime 15 | 16 | timestamps() 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170307180659_set_default_mode_to_respondent_group_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultModeToRespondentGroupChannels do 2 | use Ecto.Migration 3 | alias Ask.Repo 4 | 5 | def up do 6 | Repo.query!("SELECT ch.id, ch.type 7 | FROM channels AS ch").rows 8 | |> Enum.each(fn [channel_id, channel_type] -> 9 | Repo.query!( 10 | "UPDATE respondent_group_channels 11 | SET mode = ? 12 | WHERE channel_id = ?", 13 | [channel_type, channel_id] 14 | ) 15 | end) 16 | end 17 | 18 | def down do 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170322170704_alter_unique_index_of_oauth_tokens.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AlterUniqueIndexOfOauthTokens do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query("alter table oauth_tokens drop foreign key oauth_tokens_user_id_fkey") 6 | drop unique_index(:oauth_tokens, [:user_id, :provider]) 7 | create unique_index(:oauth_tokens, [:user_id, :provider, :base_url]) 8 | end 9 | 10 | def down do 11 | drop unique_index(:oauth_tokens, [:user_id, :provider, :base_url]) 12 | create unique_index(:oauth_tokens, [:user_id, :provider]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/ask/questionnaire_variable_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.QuestionnaireVariableTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.QuestionnaireVariable 5 | 6 | @valid_attrs %{name: "some content"} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = QuestionnaireVariable.changeset(%QuestionnaireVariable{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = QuestionnaireVariable.changeset(%QuestionnaireVariable{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/ask/survey_questionnaire_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.SurveyQuestionnaireTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.SurveyQuestionnaire 5 | 6 | @invalid_attrs %{} 7 | 8 | test "valid changeset foreign_key_constraints" do 9 | changeset = 10 | SurveyQuestionnaire.changeset(%SurveyQuestionnaire{}, %{survey_id: 1, questionnaire_id: 1}) 11 | 12 | assert changeset.valid? 13 | end 14 | 15 | test "invalid changeset foreign_key_constraints" do 16 | changeset = SurveyQuestionnaire.changeset(%SurveyQuestionnaire{}, @invalid_attrs) 17 | refute changeset.valid? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /assets/js/reducers/timezones.js: -------------------------------------------------------------------------------- 1 | import * as actions from "../actions/timezones" 2 | 3 | const initialState = { 4 | fetching: false, 5 | items: null, 6 | } 7 | 8 | export default (state = initialState, action) => { 9 | switch (action.type) { 10 | case actions.RECEIVE_TIMEZONES: { 11 | return { 12 | ...state, 13 | fetching: false, 14 | items: action.timezones, 15 | } 16 | } 17 | case actions.FETCH_TIMEZONES: { 18 | return { 19 | ...state, 20 | fetching: true, 21 | } 22 | } 23 | default: 24 | return state 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ask/project_channel.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.ProjectChannel do 2 | use Ask.Model 3 | 4 | schema "project_channels" do 5 | belongs_to :project, Ask.Project 6 | belongs_to :channel, Ask.Channel 7 | field :mode, :string 8 | 9 | timestamps() 10 | end 11 | 12 | def changeset(struct, params \\ %{}) do 13 | struct 14 | |> cast(params, [:project_id, :channel_id, :mode]) 15 | |> validate_required([:project_id, :channel_id, :mode]) 16 | |> unique_constraint(:channel_id_project_id) 17 | |> foreign_key_constraint(:project) 18 | |> foreign_key_constraint(:channel) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/ask/ecto_types/erlang_term.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Ecto.Type.ErlangTerm do 2 | @moduledoc """ 3 | A custom Ecto type for handling the serialization of arbitrary 4 | data types stored as binary data in the database. Requires the 5 | underlying DB field to be a binary. 6 | """ 7 | use Ecto.Type 8 | def type, do: :binary 9 | 10 | def cast(:any, term), do: {:ok, term} 11 | def cast(term), do: {:ok, term} 12 | 13 | def load(binary) when is_binary(binary) do 14 | {:ok, :erlang.binary_to_term(binary)} 15 | end 16 | 17 | def dump(term) do 18 | {:ok, :erlang.term_to_binary(term)} 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /assets/js/components/layout/Root.prod.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from "react" 2 | import { Provider } from "react-redux" 3 | import routes from "../../routes" 4 | import { Router } from "react-router" 5 | 6 | export default class Root extends Component { 7 | static propTypes = { 8 | store: PropTypes.object.isRequired, 9 | history: PropTypes.object.isRequired, 10 | } 11 | 12 | render() { 13 | const { store, history } = this.props 14 | return ( 15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /assets/js/reducers/unusedSample.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as actions from "../actions/surveys" 3 | 4 | const initialState = null 5 | 6 | export default (state : ?[UnusedSampleSurvey] = initialState, action: any) => { 7 | switch (action.type) { 8 | case actions.FETCHING_UNUSED_SAMPLE_SURVEYS: 9 | return initialState 10 | case actions.RECEIVE_UNUSED_SAMPLE_SURVEYS: 11 | return receiveUnusedSampleSurveys(state, action) 12 | default: 13 | return state 14 | } 15 | } 16 | 17 | const receiveUnusedSampleSurveys = (state: ?[UnusedSampleSurvey], action: any) => ( 18 | action.surveys 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /assets/mobile_survey/js/components/steps/EndStep.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component, PropTypes } from "react" 3 | import Prompt from "../Prompt" 4 | 5 | type Props = { 6 | step: Object, 7 | } 8 | 9 | class EndStep extends Component { 10 | getValue() { 11 | return "" 12 | } 13 | 14 | clearValue() {} 15 | 16 | render() { 17 | const { step } = this.props 18 | 19 | return ( 20 |
21 | {(step.prompts || []).map((prompt) => ( 22 | 23 | ))} 24 |
25 | ) 26 | } 27 | } 28 | 29 | export default EndStep 30 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171023192702_add_completed_respondents_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddCompletedRespondentsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:completed_respondents, primary_key: false) do 6 | add :survey_id, references(:surveys, on_delete: :delete_all), primary_key: true 7 | add :questionnaire_id, :integer, primary_key: true 8 | add :quota_bucket_id, :integer, primary_key: true 9 | add :mode, :string, primary_key: true 10 | add :date, :date, primary_key: true 11 | add :count, :integer, default: 0 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180219132514_add_floip_endpoints_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddFloipEndpointsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:floip_endpoints, primary_key: false) do 6 | add :survey_id, references(:surveys, on_delete: :delete_all), primary_key: true 7 | add :uri, :string, primary_key: true 8 | add :last_pushed_response_id, references(:responses, on_delete: :nothing) 9 | add :retries, :integer, default: 0 10 | add :name, :string 11 | 12 | timestamps() 13 | end 14 | 15 | create index(:surveys, [:id]) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20211125141835_optimize_audios_uuid.exs: -------------------------------------------------------------------------------- 1 | # Use 36 bytes of fixed storage for storing UUIDs instead of a dynamically 2 | # allocated storage of `1 + 36 * 3` bytes (109 bytes) with UTF8, without 3 | # changing the actual UUID format. 4 | defmodule Ask.Repo.Migrations.OptimizeAudiosUuid do 5 | use Ecto.Migration 6 | 7 | def up do 8 | execute "ALTER TABLE audios MODIFY uuid CHAR(36) CHARACTER SET ascii" 9 | create index(:audios, :uuid) 10 | end 11 | 12 | def down do 13 | drop index(:audios, :uuid) 14 | execute "ALTER TABLE audios MODIFY uuid VARCHAR(255) CHARACTER SET utf8" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /assets/js/config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type ProviderConfig = { 4 | friendlyName: string, 5 | baseUrl: string, 6 | channel_ui: boolean, 7 | } 8 | 9 | type Config = { 10 | nuntium: ProviderConfig[], 11 | verboice: ProviderConfig[], 12 | available_languages_for_numbers: string[], 13 | custom_language_names: {[string]: string}, 14 | } 15 | 16 | const defaultConfig = { 17 | available_languages_for_numbers: ["en"], 18 | custom_language_names: {}, 19 | user_settings: { 20 | language: "en", 21 | }, 22 | nuntium: [], 23 | verboice: [], 24 | } 25 | 26 | export const config: Config = window.appConfig || defaultConfig 27 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171213183513_force_respondent_stats_not_null.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ForceRespondentStatsNotNull do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "UPDATE respondents SET stats = '{\"total_sent_sms\":0,\"total_received_sms\":0,\"total_call_time\":0,\"current_call_last_interaction_time\":null,\"current_call_first_interaction_time\":null}'" 6 | 7 | alter table(:respondents) do 8 | modify :stats, :longtext, null: false 9 | end 10 | end 11 | 12 | def down do 13 | alter table(:respondents) do 14 | modify :stats, :longtext, null: true 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /docs/cloud-instance-configs/iam.ses-smtp-user-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "VisualEditor0", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "ses:SendEmail", 9 | "ses:SendRawEmail" 10 | ], 11 | "Resource": "*", 12 | "Condition": { 13 | "ForAnyValue:StringLike": { 14 | "ses:FromAddress": [ 15 | "*@surveda-xx.org", 16 | "noreply@instedd.org" 17 | ] 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170110002354_set_default_settings_to_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultSettingsToUsers do 2 | use Ecto.Migration 3 | alias Ask.Repo 4 | 5 | defmodule User do 6 | use AskWeb, :model 7 | 8 | schema "users" do 9 | field :settings, Ask.Ecto.Type.JSON 10 | end 11 | 12 | def changeset(struct, params \\ %{}) do 13 | struct 14 | |> cast(params, [:settings]) 15 | end 16 | end 17 | 18 | def change do 19 | User 20 | |> Repo.all() 21 | |> Enum.each(fn u -> 22 | u |> User.changeset(%{settings: %{}}) |> Repo.update() 23 | end) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/ask/respondent_group_channel_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.RespondentGroupChannelTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.RespondentGroupChannel 5 | 6 | @valid_attrs %{channel_id: 1, respondent_group_id: 2, mode: "a"} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = RespondentGroupChannel.changeset(%RespondentGroupChannel{}, @valid_attrs) 11 | assert changeset.valid? 12 | end 13 | 14 | test "changeset with invalid attributes" do 15 | changeset = RespondentGroupChannel.changeset(%RespondentGroupChannel{}, @invalid_attrs) 16 | refute changeset.valid? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/ask/ecto_types/string_list.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Ecto.Type.StringList do 2 | @moduledoc """ 3 | A custom type to map questionnaires modes to the database, because 4 | MySQL doesn't support arrays. 5 | 6 | This type maps from an array of strings to strings, joined by comma. 7 | """ 8 | use Ecto.Type 9 | def type, do: :string 10 | 11 | def cast(list) when is_list(list), do: {:ok, list} 12 | def cast(_), do: :error 13 | 14 | def load(string) when is_binary(string), do: {:ok, String.split(string, ",", trim: true)} 15 | 16 | def dump(list) when is_list(list), do: {:ok, Enum.join(list, ",")} 17 | def dump(_), do: :error 18 | end 19 | -------------------------------------------------------------------------------- /lib/ask_web/controllers/coherence/confirmation_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.ConfirmationController do 2 | @moduledoc """ 3 | Handle confirmation actions. 4 | 5 | A single action, `edit`, is required for the confirmation module. 6 | 7 | """ 8 | use AskWeb.Coherence, :controller 9 | use Coherence.ConfirmationControllerBase, schemas: Ask.Coherence.Schemas 10 | 11 | plug Coherence.ValidateOption, :confirmable 12 | 13 | plug :layout_view, 14 | layout: {AskWeb.Coherence.LayoutView, :app}, 15 | view: Coherence.ConfirmationView, 16 | caller: __MODULE__ 17 | 18 | plug(:redirect_logged_in when action in [:new]) 19 | end 20 | -------------------------------------------------------------------------------- /lib/ask_web/views/changeset_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.ChangesetView do 2 | use AskWeb, :view 3 | 4 | @doc """ 5 | Traverses and translates changeset errors. 6 | 7 | See `Ecto.Changeset.traverse_errors/2` and 8 | `AskWeb.ErrorHelpers.translate_error/1` for more details. 9 | """ 10 | def translate_errors(changeset) do 11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1) 12 | end 13 | 14 | def render("error.json", %{changeset: changeset}) do 15 | # When encoded, the changeset returns its errors 16 | # as a JSON object. So we just pass it forward. 17 | %{errors: translate_errors(changeset)} 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20171004213436_remove_survey_day_of_week_start_time_and_end_time.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveSurveyDayOfWeekStartTimeAndEndTime do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:surveys) do 6 | remove :schedule_day_of_week 7 | remove :schedule_start_time 8 | remove :schedule_end_time 9 | remove :timezone 10 | end 11 | end 12 | 13 | def down do 14 | alter table(:surveys) do 15 | add :schedule_day_of_week, :integer 16 | add :schedule_start_time, :time 17 | add :schedule_end_time, :time 18 | add :timezone, :string 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /assets/js/components/layout/Root.dev.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from "react" 2 | import { Provider } from "react-redux" 3 | import routes from "../../routes" 4 | import { Router } from "react-router" 5 | 6 | export default class Root extends Component { 7 | static propTypes = { 8 | store: PropTypes.object.isRequired, 9 | history: PropTypes.object.isRequired, 10 | } 11 | 12 | render() { 13 | const { store, history } = this.props 14 | return ( 15 | 16 |
17 | 18 |
19 |
20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /assets/static/images/header_icon_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /lib/ask/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo do 2 | use Ecto.Repo, 3 | otp_app: :ask, 4 | adapter: Ecto.Adapters.MyXQL 5 | 6 | # In Ecto 2.2 we can't stream a query that preloads associations. In order to 7 | # overcome the issue, we create a chunk back from the stream, and only preload 8 | # for the given batch of structs, that is eventually flattened. 9 | # 10 | # See . 11 | def stream_preload(stream, preloads, size \\ 500) do 12 | stream 13 | |> Stream.chunk_every(size) 14 | |> Stream.flat_map(fn batch -> preload(batch, preloads) end) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170201123448_add_unique_index_to_invites.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddUniqueIndexToInvites do 2 | use Ecto.Migration 3 | 4 | def up do 5 | Ask.Repo.query!("CREATE TABLE tmp LIKE invites") 6 | Ask.Repo.query!("ALTER TABLE tmp ADD UNIQUE INDEX(project_id, email)") 7 | Ask.Repo.query!("INSERT INTO tmp SELECT * FROM invites 8 | ON DUPLICATE KEY UPDATE tmp.id=invites.id") 9 | Ask.Repo.query!("RENAME TABLE invites to deleteme, tmp to invites") 10 | Ask.Repo.query!("DROP TABLE deleteme") 11 | end 12 | 13 | def down do 14 | Ask.Repo.query!("ALTER TABLE invites DROP INDEX project_id") 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170818182906_add_respondent_stats_table.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddRespondentStatsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:respondent_stats, primary_key: false) do 6 | add :survey_id, references(:surveys, on_delete: :delete_all), primary_key: true 7 | add :questionnaire_id, :integer, primary_key: true 8 | add :state, :string, primary_key: true 9 | add :disposition, :string, primary_key: true 10 | add :quota_bucket_id, :integer, primary_key: true 11 | add :mode, :string, primary_key: true 12 | add :count, :integer, default: 0 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /priv/repo/migrations/20181102164026_add_locked_to_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddLockedToSurveys do 2 | use Ecto.Migration 3 | import Ecto.Query 4 | alias Ask.Repo 5 | 6 | defmodule Survey do 7 | use AskWeb, :model 8 | 9 | schema "surveys" do 10 | field :locked, :boolean 11 | end 12 | end 13 | 14 | def up do 15 | alter table(:surveys) do 16 | add :locked, :boolean, default: false 17 | end 18 | 19 | flush() 20 | 21 | from(s in Survey) |> Repo.update_all(set: [locked: false]) 22 | end 23 | 24 | def down do 25 | alter table(:surveys) do 26 | remove :locked 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/ask_web/templates/coherence/registration/show.html.eex: -------------------------------------------------------------------------------- 1 |

Show account

2 |
    3 |
  • 4 | Name: 5 | <%= @user.name %> 6 |
  • 7 | <%= unless (login_field = Coherence.Config.login_field) == :email do %> 8 |
  • 9 | <%= humanize login_field %> 10 | <%= Map.get(@user, login_field) %> 11 |
  • 12 | <% end %> 13 | 14 |
  • 15 | Email: 16 | <%= @user.email %> 17 |
  • 18 | 19 |
20 | 21 | <%= link "Edit", to: registration_path(@conn, :edit) %> | 22 | <%= link "Delete", to: registration_path(@conn, :delete), method: :delete, data: [confirm: "Are you sure?"] %> 23 | -------------------------------------------------------------------------------- /priv/repo/migrations/20180504144910_add_patterns_to_channels.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.AddPatternsToChannels do 2 | use Ecto.Migration 3 | import Ecto.Query 4 | alias Ask.Repo 5 | 6 | defmodule Channel do 7 | use AskWeb, :model 8 | 9 | schema "channels" do 10 | field :patterns, Ask.Ecto.Type.JSON 11 | end 12 | end 13 | 14 | def up do 15 | alter table(:channels) do 16 | add :patterns, :text 17 | end 18 | 19 | flush() 20 | 21 | from(c in Channel) |> Repo.update_all(set: [patterns: []]) 22 | end 23 | 24 | def down do 25 | alter table(:channels) do 26 | remove :patterns 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /assets/js/components/charts/Stats.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import * as d3 from "d3" 3 | 4 | const formatter = d3.format(",") 5 | 6 | type Props = { 7 | data: Array, 8 | } 9 | 10 | export default class Stats extends Component { 11 | render() { 12 | const { data } = this.props 13 | 14 | return ( 15 |
16 | {data.map((item, index) => { 17 | return ( 18 |
19 | {formatter(item.value)} 20 | {item.label} 21 |
22 | ) 23 | })} 24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/ask_web/views/coherence/confirmation_view.ex: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.Coherence.ConfirmationView do 2 | use AskWeb.Coherence, :view 3 | 4 | def render("confirmation.json", %{info: info}) do 5 | %{ 6 | info: info 7 | } 8 | end 9 | 10 | def render("error.json", %{changeset: changeset}) do 11 | changeset = 12 | cond do 13 | is_nil(changeset) || changeset == "" -> "Unknown error." 14 | is_bitstring(changeset) -> changeset 15 | true -> error_string_from_changeset(changeset) 16 | end 17 | 18 | %{error: changeset} 19 | end 20 | 21 | def render("error.json", %{error: error}) do 22 | %{error: error} 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170424193129_remove_old_messages_from_questionnaires.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.RemoveOldMessagesFromQuestionnaires do 2 | use Ecto.Migration 3 | 4 | def up do 5 | alter table(:questionnaires) do 6 | remove :error_msg 7 | remove :mobile_web_sms_message 8 | remove :mobile_web_survey_is_over_message 9 | remove :quota_completed_msg 10 | end 11 | end 12 | 13 | def down do 14 | alter table(:questionnaires) do 15 | add :error_msg, :text 16 | add :mobile_web_sms_message, :text 17 | add :mobile_web_survey_is_over_message, :string 18 | add :quota_completed_msg, :string 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190621180432_fill_survey_id_and_respondent_hashed_numbnerin_respondent_disposition_history.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.FillSurveyIdAndRespondentHashedNumbnerinRespondentDispositionHistory do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "UPDATE respondent_disposition_history INNER JOIN respondents ON respondent_id = respondents.id SET respondent_disposition_history.survey_id = respondents.survey_id, respondent_disposition_history.respondent_hashed_number = respondents.hashed_number" 6 | end 7 | 8 | def down do 9 | execute "UPDATE respondent_disposition_history SET survey_id = NULL, respondent_hashed_number = NULL" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161222124950_create_translation.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateTranslation do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:translations) do 6 | add :mode, :string 7 | add :source_lang, :string 8 | add :source_text, :string 9 | add :target_lang, :string 10 | add :target_text, :string 11 | add :project_id, references(:projects, on_delete: :nothing) 12 | add :questionnaire_id, references(:questionnaires, on_delete: :nothing) 13 | 14 | timestamps() 15 | end 16 | 17 | create index(:translations, [:project_id]) 18 | create index(:translations, [:questionnaire_id]) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/support/guisso.ex: -------------------------------------------------------------------------------- 1 | Mox.defmock(GuissoMock, for: Guisso.Client) 2 | 3 | defmodule Ask.MockGuissoCase do 4 | def enable_guisso() do 5 | Mox.expect(GuissoMock, :enabled?, fn -> true end) 6 | :ok 7 | end 8 | 9 | def disable_guisso() do 10 | Mox.expect(GuissoMock, :enabled?, fn -> false end) 11 | :ok 12 | end 13 | 14 | defmacro __using__(_) do 15 | quote do 16 | import Mox 17 | import Ask.MockGuissoCase 18 | 19 | setup :verify_on_exit! 20 | 21 | setup do 22 | # Methods that aren't mocked will delegate to the original client. 23 | Mox.stub_with(GuissoMock, Guisso) 24 | 25 | :ok 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/ask/floip_endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule Ask.FloipEndpoint do 2 | use Ask.Model 3 | 4 | schema "floip_endpoints" do 5 | field :name, :string 6 | field :uri, :string 7 | field :last_pushed_response_id, :integer 8 | field :retries, :integer 9 | 10 | # disabled | enabled | terminated 11 | field :state, :string, default: "enabled" 12 | field :auth_token, :string, default: "" 13 | 14 | belongs_to :survey, Ask.Survey 15 | 16 | timestamps() 17 | end 18 | 19 | @doc """ 20 | Builds a changeset based on the `struct` and `params`. 21 | """ 22 | def changeset(struct, params \\ %{}) do 23 | struct 24 | |> cast(params, [:name, :uri, :auth_token]) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161220165340_change_survey_modes_to_arary_of_arrays.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.ChangeSurveyModesToAraryOfArrays do 2 | use Ecto.Migration 3 | 4 | alias Ask.Repo 5 | 6 | defmodule Survey do 7 | use AskWeb, :model 8 | 9 | schema "surveys" do 10 | field :mode, Ask.Ecto.Type.JSON 11 | end 12 | 13 | def changeset(struct, params \\ %{}) do 14 | struct 15 | |> cast(params, [:mode]) 16 | end 17 | end 18 | 19 | def change do 20 | Survey 21 | |> Repo.all() 22 | |> Enum.each(fn survey -> 23 | survey 24 | |> Survey.changeset(%{mode: [survey.mode || []]}) 25 | |> Repo.update!() 26 | end) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /priv/repo/migrations/20170119182423_create_coherence_rememberable.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.CreateCoherenceRememberable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:rememberables) do 6 | add :series_hash, :string 7 | add :token_hash, :string 8 | add :token_created_at, :naive_datetime 9 | add :user_id, references(:users, on_delete: :delete_all) 10 | 11 | timestamps() 12 | end 13 | 14 | create index(:rememberables, [:user_id]) 15 | create index(:rememberables, [:series_hash]) 16 | create index(:rememberables, [:token_hash]) 17 | create unique_index(:rememberables, [:user_id, :series_hash, :token_hash]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/ask/respondent_disposition_history_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.RespondentDispositionHistoryTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.RespondentDispositionHistory 5 | 6 | @valid_attrs %{disposition: "some content", mode: "sms"} 7 | @invalid_attrs %{} 8 | 9 | test "changeset with valid attributes" do 10 | changeset = 11 | RespondentDispositionHistory.changeset(%RespondentDispositionHistory{}, @valid_attrs) 12 | 13 | assert changeset.valid? 14 | end 15 | 16 | test "changeset with invalid attributes" do 17 | changeset = 18 | RespondentDispositionHistory.changeset(%RespondentDispositionHistory{}, @invalid_attrs) 19 | 20 | refute changeset.valid? 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/ask_web/emails/email_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.EmailTest do 2 | use Ask.DataCase 3 | use ExUnit.Case 4 | alias AskWeb.Email 5 | 6 | test "Reads SMTP_FROM_ADDRESS environment variable" do 7 | assert {"Test name", "test@email"} == Email.smtp_from_address() 8 | end 9 | 10 | test "Splits name and address when the format is valid" do 11 | assert {:ok, {"Other", "other@domain.com"}} == 12 | Email.smtp_from_address("Other ") 13 | end 14 | 15 | test "Errors and defaults to Instedd when the format is invalid" do 16 | assert {:error, {"InSTEDD Surveda", "noreply@instedd.org"}} == 17 | Email.smtp_from_address("Invalid format") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20161013154229_set_default_schedule_day_of_week_for_surveys.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.Repo.Migrations.SetDefaultScheduleDayOfWeekForSurveys do 2 | use Ecto.Migration 3 | import Ecto.Query 4 | 5 | defmodule Survey do 6 | use AskWeb, :model 7 | 8 | schema "surveys" do 9 | field :schedule_day_of_week, :integer 10 | end 11 | 12 | def changeset(struct, params \\ %{}) do 13 | struct 14 | |> cast(params, [:schedule_day_of_week]) 15 | end 16 | end 17 | 18 | def change do 19 | from(s in Survey, where: is_nil(s.schedule_day_of_week)) 20 | # 127 was every_day in the old ecto type 21 | |> Ask.Repo.update_all(set: [schedule_day_of_week: 127]) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /assets/js/pigeon.js: -------------------------------------------------------------------------------- 1 | export const loadPigeonScript = (nuntiumBaseUrl) => { 2 | if (typeof window.pigeon != typeof undefined) { 3 | return Promise.resolve() 4 | } 5 | 6 | return new Promise((resolve, reject) => { 7 | const script = document.createElement("script") 8 | script.src = nuntiumBaseUrl + "/assets/pigeonui.js" 9 | script.async = 1 10 | script.onload = () => { 11 | resolve() 12 | } 13 | 14 | document.body.appendChild(script) 15 | }) 16 | } 17 | 18 | export const addChannel = (accessToken) => 19 | new Promise((resolve, reject) => { 20 | const callback = (channel) => resolve(channel) 21 | window.pigeon.addChannel({ callback: callback, accessToken: accessToken }) 22 | }) 23 | -------------------------------------------------------------------------------- /assets/js/reducers/channels.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as actions from "../actions/channels" 3 | import collectionReducer, { projectFilterProvider } from "./collection" 4 | 5 | const itemsReducer = (state: IndexedList, action: any): IndexedList => { 6 | switch (action.type) { 7 | case actions.CREATE: 8 | return create(state, action) 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | export default collectionReducer(actions, itemsReducer, projectFilterProvider) 15 | 16 | const create = (state, action) => { 17 | return { 18 | ...state, 19 | items: { 20 | ...state.items, 21 | [action.id]: { 22 | ...action.channel, 23 | }, 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dev-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # set color variables 5 | COLOR_REST="$(tput sgr0)" 6 | COLOR_GREEN="$(tput setaf 2)" 7 | COLOR_MAGENTA="$(tput setaf 5)" 8 | COLOR_LIGHT_BLUE="$(tput setaf 81)" 9 | 10 | echo "$COLOR_LIGHT_BLUE 🧑‍🔧 Building Docker images... $COLOR_REST" 11 | docker compose build 12 | 13 | echo "$COLOR_LIGHT_BLUE 🧑‍🔧 Installing dependencies... $COLOR_REST" 14 | docker compose run --rm app mix deps.get 15 | docker compose run --rm webpack yarn install 16 | 17 | echo "$COLOR_LIGHT_BLUE 🧑‍🔧 Setting up database... $COLOR_REST" 18 | docker compose run --rm app mix ecto.setup 19 | docker compose run --rm -e MIX_ENV=test app mix ecto.setup 20 | 21 | echo "$COLOR_LIGHT_BLUE ✨ Everything ready! $COLOR_REST" 22 | -------------------------------------------------------------------------------- /test/ask/translation_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ask.TranslationTest do 2 | use Ask.DataCase 3 | 4 | alias Ask.Translation 5 | 6 | @valid_attrs %{ 7 | mode: "some content", 8 | scope: "prompt", 9 | source_lang: "some content", 10 | source_text: "some content", 11 | target_lang: "some content", 12 | target_text: "some content" 13 | } 14 | @invalid_attrs %{} 15 | 16 | test "changeset with valid attributes" do 17 | changeset = Translation.changeset(%Translation{}, @valid_attrs) 18 | assert changeset.valid? 19 | end 20 | 21 | test "changeset with invalid attributes" do 22 | changeset = Translation.changeset(%Translation{}, @invalid_attrs) 23 | refute changeset.valid? 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/ask_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AskWeb.ErrorViewTest do 2 | use AskWeb.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(AskWeb.ErrorView, "404.html", []) == 9 | "Page not found" 10 | end 11 | 12 | test "render 500.html" do 13 | assert render_to_string(AskWeb.ErrorView, "500.html", []) == 14 | "There was an error. Please try again later" 15 | end 16 | 17 | test "render any other" do 18 | assert render_to_string(AskWeb.ErrorView, "505.html", []) == 19 | "There was an error. Please try again later" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /assets/js/components/simulation/MobileWebWindow.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from "react" 3 | import { translate } from "react-i18next" 4 | 5 | type MobileWebWindowProps = { 6 | indexUrl: string, 7 | t: Function, 8 | } 9 | 10 | class MobileWebWindow extends Component { 11 | render() { 12 | const { indexUrl, t } = this.props 13 | 14 | return ( 15 |
16 |
17 |
{t("Mobileweb mode")}
18 |
19 |