diff --git a/cosmosdb-emulator/init_emulator.sh b/cosmosdb-emulator/init_emulator.sh deleted file mode 100644 index 545ed6a3..00000000 --- a/cosmosdb-emulator/init_emulator.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -containerId=$(docker ps --all | grep 'Time-Tracker-Cosmos-Db' | awk '{print $1}') -if [ -z "$containerId" ]; then - ipaddr="`ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}' | head -n 1`" - containerId=$(docker create -p 8081:8081 -p 10251:10251 -p 10252:10252 -p 10253:10253 -p 10254:10254 -m 3g --cpus=2.0 --name=Time-Tracker-Cosmos-Db -e AZURE_COSMOS_EMULATOR_PARTITION_COUNT=10 -e AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE=true -e AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE=$ipaddr -it mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator) - echo "##vso[task.setvariable variable=cosmosDbContainerId]$containerId"> /dev/tty -fi -docker start $containerId - -until curl -ksf "127.0.0.1:8081/_explorer/emulator.pem" -o 'cosmosdb-emulator/emulatorcert.crt'; do - echo "Waiting for Cosmosdb to start..." - sleep 10 -done - -echo "Container cosmosemulator started." - -echo "Checking SSL" -isInstalled=$( awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt | grep host ) || : - -echo "ps" -echo "$isInstalled" - -if [ -z "$isInstalled" ]; then - echo "Importing SSL..." - cp cosmosdb-emulator/emulatorcert.crt /usr/local/share/ca-certificates/ - cp cosmosdb-emulator/emulatorcert.crt /usr/share/ca-certificates/ - update-ca-certificates --fresh - echo "Importing Containers..." - export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ - python3 ./cosmosdb-emulator/init_emulator_db.py - echo "Installation succeed!!" -fi - -echo "Starting Flask!!" -flask run \ No newline at end of file diff --git a/cosmosdb-emulator/init_emulator_db.py b/cosmosdb-emulator/init_emulator_db.py deleted file mode 100644 index 31868293..00000000 --- a/cosmosdb-emulator/init_emulator_db.py +++ /dev/null @@ -1,64 +0,0 @@ -from azure.cosmos import exceptions, CosmosClient, PartitionKey -import os, sys, json - -with open('/usr/src/app/cosmosdb-emulator/seed_database.json') as database_file: - seed_database=json.load(database_file) - -sys.path.append("/usr/src/app") - -DATABASE_ACCOUNT_URI = os.environ.get('DATABASE_ACCOUNT_URI') -DATABASE_MASTER_KEY = os.environ.get('DATABASE_MASTER_KEY') - -endpoint = DATABASE_ACCOUNT_URI -key = DATABASE_MASTER_KEY - -# -client = CosmosClient(endpoint, key) -# -database_name = 'time-tracker-db' -database = client.create_database_if_not_exists(id=database_name) -# - -print("Creating TimeTracker initial initial database schema...") - -try: - print('- Project') - from time_tracker_api.projects.projects_model import container_definition as project_definition - project_container=database.create_container_if_not_exists(**project_definition) - for project in seed_database['projects']: - project_container.create_item(body=project) - - print('- Project type') - from time_tracker_api.project_types.project_types_model import container_definition as project_type_definition - project_type_container=database.create_container_if_not_exists(**project_type_definition) - for project_type in seed_database['project_types']: - project_type_container.create_item(body=project_type) - - print('- Activity') - from time_tracker_api.activities.activities_model import container_definition as activity_definition - activity_container=database.create_container_if_not_exists(**activity_definition) - for activity in seed_database['activities']: - activity_container.create_item(body=activity) - - print('- Customer') - from time_tracker_api.customers.customers_model import container_definition as customer_definition - customer_container=database.create_container_if_not_exists(**customer_definition) - for customer in seed_database['customers']: - customer_container.create_item(body=customer) - - print('- Time entry') - from time_tracker_api.time_entries.time_entries_model import container_definition as time_entry_definition - time_entry_container=database.create_container_if_not_exists(**time_entry_definition) - for time_entry in seed_database['time_entries']: - time_entry_container.create_item(body=time_entry) - - print('- Technology') - from time_tracker_api.technologies.technologies_model import container_definition as technologies_definition - database.create_container_if_not_exists(**technologies_definition) -except exceptions.CosmosResourceExistsError as e: - print("Unexpected error while creating initial database schema: %s" % e.message) - -database_file.close() - -print("Done!") - diff --git a/cosmosdb_emulator/cli.sh b/cosmosdb_emulator/cli.sh new file mode 100755 index 00000000..709f6392 --- /dev/null +++ b/cosmosdb_emulator/cli.sh @@ -0,0 +1,22 @@ +#!/bin/sh +COMMAND=$@ +API_CONTAINER_NAME="time-tracker-backend_api" +TIME_TRACKER_CLI_URL="cosmosdb_emulator/time_tracker_cli" +DEFAULT_SCRIPT_NAME="main.py" +FIRST_ARG=$1 + +execute(){ + docker exec -it $API_CONTAINER_NAME sh "cosmosdb_emulator/verify_environment.sh" + + if [ "$FIRST_ARG" != "$DEFAULT_SCRIPT_NAME" ]; then + echo "Do not forget that the file name is $DEFAULT_SCRIPT_NAME and needs to be sent as first parameter" + echo "For example: ./cli.sh main.py" + exit 0 + fi + + TIME_TRACKER_CLI="python3 $COMMAND" + + docker exec -it $API_CONTAINER_NAME sh -c "cd $TIME_TRACKER_CLI_URL && $TIME_TRACKER_CLI" +} + +execute \ No newline at end of file diff --git a/cosmosdb-emulator/entrypoint.sh b/cosmosdb_emulator/entrypoint.sh similarity index 58% rename from cosmosdb-emulator/entrypoint.sh rename to cosmosdb_emulator/entrypoint.sh index 8978d832..3960bd26 100644 --- a/cosmosdb-emulator/entrypoint.sh +++ b/cosmosdb_emulator/entrypoint.sh @@ -1,19 +1,21 @@ #!/bin/sh -until curl -ksf "${DATABASE_ACCOUNT_URI}/_explorer/emulator.pem" -o 'cosmosdb-emulator/emulatorcert.crt'; do +until curl -ksf "${DATABASE_ACCOUNT_URI}/_explorer/emulator.pem" -o 'cosmosdb_emulator/emulatorcert.crt'; do echo "Waiting for Cosmosdb to start..." sleep 10 done +source cosmosdb_emulator/verify_environment.sh + echo "Container cosmosemulator started." echo "Importing SSL..." -cp cosmosdb-emulator/emulatorcert.crt /usr/local/share/ca-certificates/ -cp cosmosdb-emulator/emulatorcert.crt /usr/share/ca-certificates/ +cp cosmosdb_emulator/emulatorcert.crt /usr/local/share/ca-certificates/ +cp cosmosdb_emulator/emulatorcert.crt /usr/share/ca-certificates/ update-ca-certificates --fresh echo "Importing Containers..." export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ -python3 ./cosmosdb-emulator/init_emulator_db.py +python3 ./cosmosdb_emulator/init_emulator_db.py echo "Installation succeed!!" echo "Starting Flask!!" diff --git a/cosmosdb_emulator/init_emulator_db.py b/cosmosdb_emulator/init_emulator_db.py new file mode 100644 index 00000000..6b4d3438 --- /dev/null +++ b/cosmosdb_emulator/init_emulator_db.py @@ -0,0 +1,63 @@ +from azure.cosmos import exceptions, CosmosClient, PartitionKey +import os, sys + +sys.path.append("/usr/src/app") + +DATABASE_ACCOUNT_URI = os.environ.get('DATABASE_ACCOUNT_URI') +DATABASE_MASTER_KEY = os.environ.get('DATABASE_MASTER_KEY') +DATABASE_NAME = os.environ.get('DATABASE_NAME') + +client = CosmosClient(DATABASE_ACCOUNT_URI, DATABASE_MASTER_KEY) +database = client.create_database_if_not_exists(id=DATABASE_NAME) + +print("Creating TimeTracker initial initial database schema...") + +try: + print('- Project') + from time_tracker_api.projects.projects_model import ( + container_definition as project_definition, + ) + + database.create_container_if_not_exists(**project_definition) + + print('- Project type') + from time_tracker_api.project_types.project_types_model import ( + container_definition as project_type_definition, + ) + + database.create_container_if_not_exists(**project_type_definition) + + print('- Activity') + from time_tracker_api.activities.activities_model import ( + container_definition as activity_definition, + ) + + database.create_container_if_not_exists(**activity_definition) + + print('- Customer') + from time_tracker_api.customers.customers_model import ( + container_definition as customer_definition, + ) + + database.create_container_if_not_exists(**customer_definition) + + print('- Time entry') + from time_tracker_api.time_entries.time_entries_model import ( + container_definition as time_entry_definition, + ) + + database.create_container_if_not_exists(**time_entry_definition) + + print('- Technology') + from time_tracker_api.technologies.technologies_model import ( + container_definition as technologies_definition, + ) + + database.create_container_if_not_exists(**technologies_definition) +except exceptions.CosmosResourceExistsError as e: + print( + "Unexpected error while creating initial database schema: %s" + % e.message + ) + +print("Done!") diff --git a/cosmosdb_emulator/time_tracker_cli/data_target/cosmos.py b/cosmosdb_emulator/time_tracker_cli/data_target/cosmos.py new file mode 100644 index 00000000..39c72acd --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/data_target/cosmos.py @@ -0,0 +1,116 @@ +import os +import sys + +from azure.cosmos import CosmosClient +from azure.cosmos.exceptions import ( + CosmosResourceExistsError, + CosmosResourceNotFoundError, +) + +from cosmosdb_emulator.time_tracker_cli.data_target.data_target import ( + DataTarget, +) +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.utils.activity import get_activity_json +from cosmosdb_emulator.time_tracker_cli.utils.customer import get_customer_json +from cosmosdb_emulator.time_tracker_cli.utils.project import get_project_json +from cosmosdb_emulator.time_tracker_cli.utils.project_type import ( + get_project_type_json, +) +from cosmosdb_emulator.time_tracker_cli.utils.time_entry import get_entry_json + +from time_tracker_api.customers.customers_model import ( + container_definition as customer_definition, +) +from time_tracker_api.project_types.project_types_model import ( + container_definition as project_type_definition, +) +from time_tracker_api.projects.projects_model import ( + container_definition as project_definition, +) +from time_tracker_api.activities.activities_model import ( + container_definition as activity_definition, +) +from time_tracker_api.time_entries.time_entries_model import ( + container_definition as time_entry_definition, +) + +DATABASE_ACCOUNT_URI = os.environ.get('DATABASE_ACCOUNT_URI') +DATABASE_MASTER_KEY = os.environ.get('DATABASE_MASTER_KEY') +DATABASE_NAME = os.environ.get('DATABASE_NAME') + + +class CosmosDataTarget(DataTarget): + def __init__(self): + self.cosmos_client = CosmosClient( + DATABASE_ACCOUNT_URI, DATABASE_MASTER_KEY + ) + self.database = self.cosmos_client.create_database_if_not_exists( + DATABASE_NAME + ) + + @staticmethod + def get_container_definition_by_entity_name(container_name: str) -> dict: + containers_definition = { + TimeTrackerEntities.CUSTOMER.value: customer_definition, + TimeTrackerEntities.PROJECT_TYPE.value: project_type_definition, + TimeTrackerEntities.PROJECT.value: project_definition, + TimeTrackerEntities.ACTIVITY.value: activity_definition, + TimeTrackerEntities.TIME_ENTRY.value: time_entry_definition, + } + + return containers_definition.get(container_name) + + @staticmethod + def get_json_method_entity_name(entity_name): + available_json = { + TimeTrackerEntities.CUSTOMER.value: get_customer_json, + TimeTrackerEntities.PROJECT_TYPE.value: get_project_type_json, + TimeTrackerEntities.PROJECT.value: get_project_json, + TimeTrackerEntities.ACTIVITY.value: get_activity_json, + TimeTrackerEntities.TIME_ENTRY.value: get_entry_json, + } + + return available_json.get(entity_name) + + def delete(self, entities: dict): + for entity in entities: + entity_container_definition = ( + CosmosDataTarget.get_container_definition_by_entity_name( + entity + ) + ) + entity_container_id = entity_container_definition.get('id') + try: + self.database.delete_container(entity_container_id) + self.database.create_container_if_not_exists( + **entity_container_definition + ) + except CosmosResourceNotFoundError: + pass + + def save(self, entities: dict): + for entity in entities: + entity_container_definition = ( + CosmosDataTarget.get_container_definition_by_entity_name( + entity + ) + ) + entities_list = entities.get(entity) + entity_container = self.database.create_container_if_not_exists( + **entity_container_definition + ) + + for element in entities_list: + get_json_entity = CosmosDataTarget.get_json_method_entity_name( + entity + ) + json_entity = get_json_entity(element) + try: + entity_container.create_item(body=json_entity) + except CosmosResourceExistsError: + print( + f'The {entity} entity with the ID ({element.id}) already exists, so it has not been created.' + ) diff --git a/cosmosdb_emulator/time_tracker_cli/data_target/data_target.py b/cosmosdb_emulator/time_tracker_cli/data_target/data_target.py new file mode 100644 index 00000000..0a7a3854 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/data_target/data_target.py @@ -0,0 +1,11 @@ +from abc import ABC, abstractmethod + + +class DataTarget(ABC): + @abstractmethod + def save(self, entities: dict): + pass + + @abstractmethod + def delete(self, entities: set): + pass diff --git a/cosmosdb_emulator/time_tracker_cli/enums/entites.py b/cosmosdb_emulator/time_tracker_cli/enums/entites.py new file mode 100644 index 00000000..022b7967 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/enums/entites.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class TimeTrackerEntities(Enum): + def __str__(self): + return str(self.value) + + CUSTOMER = 'Customers' + PROJECT = 'Projects' + PROJECT_TYPE = 'Project-Types' + ACTIVITY = 'Activities' + TIME_ENTRY = 'Time-entries' diff --git a/cosmosdb_emulator/time_tracker_cli/factories/activity_factory.py b/cosmosdb_emulator/time_tracker_cli/factories/activity_factory.py new file mode 100644 index 00000000..13d7c843 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/factories/activity_factory.py @@ -0,0 +1,29 @@ +from typing import NamedTuple + +from factory import Factory, Faker + +from cosmosdb_emulator.time_tracker_cli.providers.common import CommonProvider +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_time_tracker_tenant_id, +) + +Faker.add_provider(CommonProvider) + + +class Activity(NamedTuple): + id: str + name: str + description: str + status: str + tenant_id: str + + +class ActivityFactory(Factory): + class Meta: + model = Activity + + id = Faker('uuid4') + name = Faker('job') + description = Faker('sentence', nb_words=6) + status = Faker('status') + tenant_id = get_time_tracker_tenant_id() diff --git a/cosmosdb_emulator/time_tracker_cli/factories/customer_factory.py b/cosmosdb_emulator/time_tracker_cli/factories/customer_factory.py new file mode 100644 index 00000000..4c63d0f3 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/factories/customer_factory.py @@ -0,0 +1,24 @@ +from typing import NamedTuple + +from factory import Factory, Faker + +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_time_tracker_tenant_id, +) + + +class Customer(NamedTuple): + id: str + name: str + description: str + tenant_id: str + + +class CustomerFactory(Factory): + class Meta: + model = Customer + + id = Faker('uuid4') + name = Faker('company') + description = Faker('sentence', nb_words=10) + tenant_id = get_time_tracker_tenant_id() diff --git a/cosmosdb_emulator/time_tracker_cli/factories/project_factory.py b/cosmosdb_emulator/time_tracker_cli/factories/project_factory.py new file mode 100644 index 00000000..a03f9ae0 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/factories/project_factory.py @@ -0,0 +1,30 @@ +from typing import NamedTuple + +from factory import Factory, Faker + +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_time_tracker_tenant_id, +) + + +class Project(NamedTuple): + id: str + name: str + description: str + project_type_id: int + customer_id: str + tenant_id: str + + +class ProjectFactory(Factory): + class Meta: + model = Project + + def __init__(self, project_type_id, customer_id): + self.project_type_id = project_type_id + self.customer_id = customer_id + + id = Faker('uuid4') + name = Faker('name') + description = Faker('sentence', nb_words=10) + tenant_id = get_time_tracker_tenant_id() diff --git a/cosmosdb_emulator/time_tracker_cli/factories/project_type_factory.py b/cosmosdb_emulator/time_tracker_cli/factories/project_type_factory.py new file mode 100644 index 00000000..3978100b --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/factories/project_type_factory.py @@ -0,0 +1,28 @@ +from typing import NamedTuple + +from factory import Factory, Faker + +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_time_tracker_tenant_id, +) + + +class ProjectType(NamedTuple): + id: str + name: str + description: str + customer_id: str + tenant_id: str + + +class ProjectTypeFactory(Factory): + class Meta: + model = ProjectType + + def __init__(self, customer_id): + self.customer_id = customer_id + + id = Faker('uuid4') + name = Faker('name') + description = Faker('sentence', nb_words=10) + tenant_id = get_time_tracker_tenant_id() diff --git a/cosmosdb_emulator/time_tracker_cli/factories/time_entry_factory.py b/cosmosdb_emulator/time_tracker_cli/factories/time_entry_factory.py new file mode 100644 index 00000000..5cf1bd9d --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/factories/time_entry_factory.py @@ -0,0 +1,38 @@ +from typing import NamedTuple, List + +from factory import Factory, Faker + +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_time_tracker_tenant_id, +) + + +class TimeEntry(NamedTuple): + project_id: str + start_date: str + owner_id: str + id: str + tenant_id: str + description: str + activity_id: str + technologies: List[str] + end_date: str + + +class TimeEntryFactory(Factory): + class Meta: + model = TimeEntry + + def __init__( + self, owner_id, start_date, end_date, project_id, activity_id + ): + self.start_date = start_date + self.end_date = end_date + self.owner_id = owner_id + self.project_id = project_id + self.activity_id = activity_id + + id = Faker('uuid4') + description = Faker('sentence', nb_words=10) + technologies = Faker('words', nb=3) + tenant_id = get_time_tracker_tenant_id() diff --git a/cosmosdb_emulator/time_tracker_cli/main.py b/cosmosdb_emulator/time_tracker_cli/main.py new file mode 100644 index 00000000..e8e1defe --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/main.py @@ -0,0 +1,90 @@ +import sys + +project_source = '/usr/src/app' +sys.path.append(project_source) + +import click +from pyfiglet import Figlet + +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.activity_management_strategy import ( + ActivityManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.time_entry_management_strategy import ( + TimeEntryManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.project_management_strategy import ( + ProjectManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.customer_management_strategy import ( + CustomerManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.project_type_management_strategy import ( + ProjectTypeManagementStrategy, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_context import ( + ManagementContext, +) +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_entity, + time_tracker_entities, + ask_action, + entities_actions, +) +from cosmosdb_emulator.time_tracker_cli.data_target.cosmos import ( + CosmosDataTarget, +) + + +@click.command() +@click.option( + '--action', + '-a', + type=click.Choice(entities_actions, case_sensitive=True), + help='Action to be implemented in the entities.', +) +@click.option( + '--entity', + '-e', + type=click.Choice(time_tracker_entities, case_sensitive=True), + help='Entity to which the action is to be applied', +) +def main(action: str, entity: str): + time_tracker_cli_header = Figlet(font='slant').renderText( + 'Time Tracker CLI' + ) + print(time_tracker_cli_header) + + selected_action = action if action else ask_action() + selected_entity = entity if entity else ask_entity(action=selected_action) + + management_strategy = get_strategy_by_selected_entity(selected_entity) + data_target = CosmosDataTarget() + management_context = ManagementContext(management_strategy, data_target) + + if selected_action == 'Delete': + management_context.delete_data() + sys.exit() + + management_context.create_data() + + +def get_strategy_by_selected_entity(selected_entity) -> ManagementStrategy: + strategies = { + TimeTrackerEntities.TIME_ENTRY.value: TimeEntryManagementStrategy(), + TimeTrackerEntities.PROJECT.value: ProjectManagementStrategy(), + TimeTrackerEntities.ACTIVITY.value: ActivityManagementStrategy(), + TimeTrackerEntities.CUSTOMER.value: CustomerManagementStrategy(), + TimeTrackerEntities.PROJECT_TYPE.value: ProjectTypeManagementStrategy(), + } + + return strategies.get(selected_entity) + + +if __name__ == '__main__': + main() diff --git a/cosmosdb_emulator/time_tracker_cli/providers/common.py b/cosmosdb_emulator/time_tracker_cli/providers/common.py new file mode 100644 index 00000000..c5ec3e24 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/providers/common.py @@ -0,0 +1,7 @@ +from faker.providers import BaseProvider + + +class CommonProvider(BaseProvider): + def status(self) -> str: + available_status = ['active', 'inactive'] + return self.random_element(elements=available_status) diff --git a/cosmosdb_emulator/time_tracker_cli/questions/common.py b/cosmosdb_emulator/time_tracker_cli/questions/common.py new file mode 100644 index 00000000..8a222afc --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/questions/common.py @@ -0,0 +1,69 @@ +from PyInquirer import prompt + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + stop_execution_if_user_input_is_invalid, +) + + +time_tracker_entities = [entity.value for entity in TimeTrackerEntities] + +entities_actions = ['Create', 'Delete'] + + +def ask_entity(action: str): + question_key = 'entity' + + select_entity_question = { + 'type': 'list', + 'name': question_key, + 'message': f'Perfect, please provide the entity that you want to {action.lower()}:', + 'choices': time_tracker_entities, + } + + selected_entity_answer = prompt(select_entity_question) + selected_entity = selected_entity_answer.get(question_key) + stop_execution_if_user_input_is_invalid(selected_entity) + + return selected_entity + + +def ask_action(): + question_key = 'action' + + select_action_question = { + 'type': 'list', + 'name': question_key, + 'message': 'Hello TT Coder, what action do you want to generate on the entities?', + 'choices': entities_actions, + } + + selected_action_answer = prompt(select_action_question) + selected_action = selected_action_answer.get(question_key) + stop_execution_if_user_input_is_invalid(selected_action) + + return selected_action + + +def ask_delete_confirmation(entities_to_eliminate: set) -> bool: + question_key = 'delete_confirmation' + + join_element = ', ' + entities = join_element.join(entities_to_eliminate) + + message = f'Are you sure to delete these ({entities}) entities' + + delete_confirmation_question = { + 'type': 'confirm', + 'name': question_key, + 'message': message, + 'default': True, + } + + delete_confirmation_answer = prompt(delete_confirmation_question) + is_user_agree_to_delete = delete_confirmation_answer.get(question_key) + stop_execution_if_user_input_is_invalid(is_user_agree_to_delete) + + return is_user_agree_to_delete diff --git a/cosmosdb_emulator/time_tracker_cli/questions/entries.py b/cosmosdb_emulator/time_tracker_cli/questions/entries.py new file mode 100644 index 00000000..2a8078b7 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/questions/entries.py @@ -0,0 +1,108 @@ +from PyInquirer import Separator, prompt + +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + stop_execution_if_user_input_is_invalid, +) +from cosmosdb_emulator.time_tracker_cli.validators.max_amount import ( + MaxAmountValidator, +) +from cosmosdb_emulator.time_tracker_cli.validators.number import ( + NumberValidator, +) +from cosmosdb_emulator.time_tracker_cli.validators.uuid import UUIDValidator + + +def ask_delete_entries(): + question_key = 'delete' + delete_entries_question = { + 'type': 'confirm', + 'name': question_key, + 'message': ( + 'We are going to delete all entries that is currently in the emulator, are you sure to continue?' + ), + 'default': True, + } + + delete_data_answer = prompt(delete_entries_question) + user_agree_to_delete_data = delete_data_answer.get(question_key) + stop_execution_if_user_input_is_invalid(user_agree_to_delete_data) + return user_agree_to_delete_data + + +def ask_entry_type(): + question_key = 'entry_type' + entry_type_question = { + 'type': 'list', + 'name': question_key, + 'message': 'What type of entry do you want to generate?', + 'choices': [ + Separator('<=== AVAILABLE ENTRY TYPES ====>'), + {'name': 'Own entries (Time Entries Page)', 'value': 'OE'}, + {'name': 'General entries (Reports Page)', 'value': 'GE'}, + ], + } + entry_type_answer = prompt(entry_type_question) + entry_type = entry_type_answer.get('entry_type') + stop_execution_if_user_input_is_invalid(entry_type) + return entry_type + + +def ask_entries_amount(entries_type: str): + question_key = 'entries_amount' + message_for_own_entries = 'Enter the amount of entries that you need:' + message_for_general_entries = ( + 'Enter the amount of entries per user that you need:' + ) + own_entries_id = 'OE' + + entries_amount_message = ( + message_for_own_entries + if entries_type == own_entries_id + else message_for_general_entries + ) + + entries_amount_question = { + 'type': 'input', + 'name': question_key, + 'message': entries_amount_message, + 'validate': NumberValidator, + } + + entries_amount_answer = prompt(entries_amount_question).get(question_key) + stop_execution_if_user_input_is_invalid(entries_amount_answer) + entries_amount = int(entries_amount_answer) + return entries_amount + + +def ask_user_identifier() -> str: + question_key = 'user_id' + user_identifier_question = { + 'type': 'input', + 'name': question_key, + 'message': 'Please your identifier:', + 'validate': UUIDValidator, + } + user_identifier_answer = prompt(user_identifier_question) + user_identifier = user_identifier_answer.get(question_key) + stop_execution_if_user_input_is_invalid(user_identifier) + return user_identifier + + +def ask_entries_owners_amount(users_amount: int) -> int: + question_key = 'entries_owners_amount' + entries_owners_amount_question = { + 'type': 'input', + 'name': question_key, + 'message': 'Enter the number of users to be assigned entries:', + } + + max_amount_validator = MaxAmountValidator( + max_amount=users_amount, + error_message='We do not have that amount of users, do not be smart!', + ) + + entries_owners_amount_answer = prompt( + entries_owners_amount_question, validator=max_amount_validator + ).get(question_key) + stop_execution_if_user_input_is_invalid(entries_owners_amount_answer) + return int(entries_owners_amount_answer) diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/activity_management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/activity_management_strategy.py new file mode 100644 index 00000000..4e937040 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/activity_management_strategy.py @@ -0,0 +1,36 @@ +import sys + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_delete_confirmation, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + + +class ActivityManagementStrategy(ManagementStrategy): + + _conflict_entities: set = { + TimeTrackerEntities.TIME_ENTRY.value, + TimeTrackerEntities.ACTIVITY.value, + } + + def get_confirmation_to_delete_data(self) -> bool: + is_user_agree_to_delete_activities_data = ask_delete_confirmation( + self.get_conflict_entities() + ) + return is_user_agree_to_delete_activities_data + + def get_answers_needed_to_create_data(self) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def generate_entities(self, entity_information: dict) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def get_conflict_entities(self) -> set: + return self._conflict_entities diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/customer_management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/customer_management_strategy.py new file mode 100644 index 00000000..38574dde --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/customer_management_strategy.py @@ -0,0 +1,38 @@ +import sys + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_delete_confirmation, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + + +class CustomerManagementStrategy(ManagementStrategy): + + _conflict_entities: set = { + TimeTrackerEntities.CUSTOMER.value, + TimeTrackerEntities.PROJECT.value, + TimeTrackerEntities.PROJECT_TYPE.value, + TimeTrackerEntities.TIME_ENTRY.value, + } + + def get_confirmation_to_delete_data(self) -> bool: + is_user_agree_to_delete_customers_data = ask_delete_confirmation( + self.get_conflict_entities() + ) + return is_user_agree_to_delete_customers_data + + def get_answers_needed_to_create_data(self) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def generate_entities(self, entity_information: dict) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def get_conflict_entities(self) -> set: + return self._conflict_entities diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/management_context.py b/cosmosdb_emulator/time_tracker_cli/strategies/management_context.py new file mode 100644 index 00000000..ac2e5a29 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/management_context.py @@ -0,0 +1,46 @@ +from cosmosdb_emulator.time_tracker_cli.data_target.data_target import ( + DataTarget, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + + +class ManagementContext: + def __init__( + self, strategy: ManagementStrategy, data_target: DataTarget + ) -> None: + self._strategy = strategy + self._data_target = data_target + + @property + def strategy(self) -> ManagementStrategy: + return self._strategy + + @strategy.setter + def strategy(self, strategy: ManagementStrategy) -> None: + self._strategy = strategy + + def create_data(self): + user_answers = self._strategy.get_answers_needed_to_create_data() + entities = self._strategy.generate_entities(user_answers) + conflict_entities = self._strategy.get_conflict_entities() + print( + 'We are trying to create all the requested information, so please wait and be patient!' + ) + print('Creating the data...') + self._data_target.delete(conflict_entities) + self._data_target.save(entities) + print('Great Job! The needed data was created!') + + def delete_data(self): + is_user_agree_to_delete_data = ( + self._strategy.get_confirmation_to_delete_data() + ) + if is_user_agree_to_delete_data: + conflict_entities = self._strategy.get_conflict_entities() + print( + 'We are trying to delete all the requested information, hope you do not regret it later' + ) + self._data_target.delete(conflict_entities) + print('The requested entity and related entities were eliminated.') diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/management_strategy.py new file mode 100644 index 00000000..c911f033 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/management_strategy.py @@ -0,0 +1,34 @@ +from abc import abstractmethod, ABC + + +class ManagementStrategy(ABC): + @abstractmethod + def get_confirmation_to_delete_data(self) -> bool: + """ + Ask the user if he/she agrees to delete the information + :return: True if user agrees to remove the information else False + """ + pass + + @abstractmethod + def get_answers_needed_to_create_data(self) -> dict: + """ + Ask the user all information needed to create a specific entity. + :return: a dict with all information needed to generate the entities + """ + pass + + @abstractmethod + def generate_entities(self, entity_information: dict) -> dict: + """ + Create all the entities related with a specific strategy. + """ + pass + + @abstractmethod + def get_conflict_entities(self) -> set: + """ + Returns all the entities that generate conflict with a specific entity + at the moment of generating the information + """ + pass diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/project_management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/project_management_strategy.py new file mode 100644 index 00000000..bbc7ff83 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/project_management_strategy.py @@ -0,0 +1,35 @@ +import sys + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_delete_confirmation, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + + +class ProjectManagementStrategy(ManagementStrategy): + _conflict_entities: set = { + TimeTrackerEntities.TIME_ENTRY.value, + TimeTrackerEntities.PROJECT.value, + } + + def get_confirmation_to_delete_data(self) -> bool: + is_user_agree_to_delete_projects_data = ask_delete_confirmation( + self.get_conflict_entities() + ) + return is_user_agree_to_delete_projects_data + + def get_conflict_entities(self) -> set: + return self._conflict_entities + + def generate_entities(self, entity_information: dict) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def get_answers_needed_to_create_data(self) -> dict: + print('This functionality has not yet been implemented') + sys.exit() diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/project_type_management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/project_type_management_strategy.py new file mode 100644 index 00000000..cf0e2b89 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/project_type_management_strategy.py @@ -0,0 +1,37 @@ +import sys + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_delete_confirmation, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + + +class ProjectTypeManagementStrategy(ManagementStrategy): + + _conflict_entities: set = { + TimeTrackerEntities.PROJECT_TYPE.value, + TimeTrackerEntities.PROJECT.value, + TimeTrackerEntities.TIME_ENTRY.value, + } + + def get_confirmation_to_delete_data(self) -> bool: + is_user_agree_to_delete_project_types_data = ask_delete_confirmation( + self.get_conflict_entities() + ) + return is_user_agree_to_delete_project_types_data + + def get_answers_needed_to_create_data(self) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def generate_entities(self, entity_information: dict) -> dict: + print('This functionality has not yet been implemented') + sys.exit() + + def get_conflict_entities(self) -> set: + return self._conflict_entities diff --git a/cosmosdb_emulator/time_tracker_cli/strategies/time_entry_management_strategy.py b/cosmosdb_emulator/time_tracker_cli/strategies/time_entry_management_strategy.py new file mode 100644 index 00000000..fe489ba0 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/strategies/time_entry_management_strategy.py @@ -0,0 +1,103 @@ +import sys + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.questions.common import ( + ask_delete_confirmation, +) +from cosmosdb_emulator.time_tracker_cli.strategies.management_strategy import ( + ManagementStrategy, +) + +from cosmosdb_emulator.time_tracker_cli.questions.entries import ( + ask_delete_entries, + ask_entry_type, + ask_user_identifier, + ask_entries_owners_amount, + ask_entries_amount, +) +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_unique_elements_from_list, +) + +from cosmosdb_emulator.time_tracker_cli.utils.time_entry import ( + get_related_information_for_entries, + generate_entries_per_user, + get_time_tracker_users_ids, +) + + +class TimeEntryManagementStrategy(ManagementStrategy): + + _conflict_entities: set = { + TimeTrackerEntities.TIME_ENTRY.value, + } + + def get_answers_needed_to_create_data(self) -> dict: + user_agree_to_delete_entries = ask_delete_entries() + + if not user_agree_to_delete_entries: + print('Thanks for coming! See you later') + sys.exit() + + entries_type = ask_entry_type() + entry_owners = [] + own_entries_type_id = 'OE' + + if entries_type == own_entries_type_id: + user_identifier = ask_user_identifier() + entry_owners.append(user_identifier) + else: + print('Be patient, we are loading important information...') + users_ids = get_time_tracker_users_ids() + users_amount = len(users_ids) + print(f'Currently in Time Tracker we are {users_amount} users') + entries_owners_amount = ask_entries_owners_amount(users_amount) + entry_owners = get_unique_elements_from_list( + elements_list=users_ids, + amount_of_elements=entries_owners_amount, + ) + + entries_amount = ask_entries_amount(entries_type) + + return {'entries_amount': entries_amount, 'entry_owners': entry_owners} + + def get_confirmation_to_delete_data(self) -> bool: + is_user_agree_to_delete_entries_data = ask_delete_confirmation( + self.get_conflict_entities() + ) + return is_user_agree_to_delete_entries_data + + def generate_entities(self, entity_information: dict) -> dict: + entries = [] + + entries_related_information = get_related_information_for_entries() + projects = entries_related_information.get( + TimeTrackerEntities.PROJECT.value + ) + activities = entries_related_information.get( + TimeTrackerEntities.ACTIVITY.value + ) + + entries_amount = entity_information.get('entries_amount') + entry_owners_ids = entity_information.get('entry_owners') + daily_entries_amount = 5 + + for owner_id in entry_owners_ids: + user_entries = generate_entries_per_user( + daily_entries_amount=daily_entries_amount, + entries_amount=entries_amount, + owner_id=owner_id, + projects=projects, + activities=activities, + ) + entries.extend(user_entries) + + entities = entries_related_information + entities[TimeTrackerEntities.TIME_ENTRY.value] = entries + + return entities + + def get_conflict_entities(self) -> set: + return self._conflict_entities diff --git a/cosmosdb_emulator/time_tracker_cli/utils/activity.py b/cosmosdb_emulator/time_tracker_cli/utils/activity.py new file mode 100644 index 00000000..a6832c41 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/activity.py @@ -0,0 +1,25 @@ +from typing import List + +from cosmosdb_emulator.time_tracker_cli.factories.activity_factory import ( + ActivityFactory, +) + + +def get_activity_json(activity_factory: ActivityFactory) -> dict: + activity = { + 'id': activity_factory.id, + 'name': activity_factory.name, + 'description': activity_factory.description, + 'tenant_id': activity_factory.tenant_id, + 'status': activity_factory.status, + } + + return activity + + +def get_activities(activities_amount: int) -> List[ActivityFactory]: + activities = [] + for index in range(activities_amount): + activity = ActivityFactory() + activities.append(activity) + return activities diff --git a/cosmosdb_emulator/time_tracker_cli/utils/common.py b/cosmosdb_emulator/time_tracker_cli/utils/common.py new file mode 100644 index 00000000..d1534824 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/common.py @@ -0,0 +1,31 @@ +import sys +from typing import List + +from faker import Faker + + +def get_time_tracker_tenant_id() -> str: + """ + This tenant id is necessary for all factories, use this value in + the field tenant_id of all factories + """ + time_tracker_tenant_id = 'cc925a5d-9644-4a4f-8d99-0bee49aadd05' + return time_tracker_tenant_id + + +def stop_execution_if_user_input_is_invalid(user_input: str): + if user_input is None: + print('Thanks for coming, see you later!') + sys.exit() + + +def get_unique_elements_from_list(elements_list, amount_of_elements) -> List: + entry_owners = Faker().random_elements( + elements=elements_list, length=amount_of_elements, unique=True + ) + return entry_owners + + +def get_random_element_from_list(elements_list): + random_element = Faker().random_element(elements=elements_list) + return random_element diff --git a/cosmosdb_emulator/time_tracker_cli/utils/customer.py b/cosmosdb_emulator/time_tracker_cli/utils/customer.py new file mode 100644 index 00000000..164d367b --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/customer.py @@ -0,0 +1,20 @@ +from typing import List + +from cosmosdb_emulator.time_tracker_cli.factories.customer_factory import ( + CustomerFactory, +) + + +def get_customers(customer_amount: int) -> List[CustomerFactory]: + customers = CustomerFactory.create_batch(customer_amount) + return customers + + +def get_customer_json(customer_factory: CustomerFactory) -> dict: + customer = { + 'id': customer_factory.id, + 'name': customer_factory.name, + 'description': customer_factory.description, + 'tenant_id': customer_factory.tenant_id, + } + return customer diff --git a/cosmosdb_emulator/time_tracker_cli/utils/project.py b/cosmosdb_emulator/time_tracker_cli/utils/project.py new file mode 100644 index 00000000..c082d29a --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/project.py @@ -0,0 +1,36 @@ +from typing import List + +from cosmosdb_emulator.time_tracker_cli.factories.project_factory import ( + ProjectFactory, +) +from cosmosdb_emulator.time_tracker_cli.factories.project_type_factory import ( + ProjectTypeFactory, +) + + +def get_projects( + projects_per_project_type: int, project_types: List[ProjectTypeFactory] +) -> List[ProjectFactory]: + projects = [] + + for project_type in project_types: + for index in range(projects_per_project_type): + project = ProjectFactory( + project_type_id=project_type.id, + customer_id=project_type.customer_id, + ) + projects.append(project) + + return projects + + +def get_project_json(project_factory: ProjectFactory) -> dict: + project = { + 'id': project_factory.id, + 'name': project_factory.name, + 'description': project_factory.description, + 'customer_id': project_factory.customer_id, + 'project_type_id': project_factory.project_type_id, + 'tenant_id': project_factory.tenant_id, + } + return project diff --git a/cosmosdb_emulator/time_tracker_cli/utils/project_type.py b/cosmosdb_emulator/time_tracker_cli/utils/project_type.py new file mode 100644 index 00000000..104b3044 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/project_type.py @@ -0,0 +1,33 @@ +from typing import List + +from cosmosdb_emulator.time_tracker_cli.factories.customer_factory import ( + CustomerFactory, +) +from cosmosdb_emulator.time_tracker_cli.factories.project_type_factory import ( + ProjectTypeFactory, +) + + +def get_project_types( + project_types_per_customer: int, customers: List[CustomerFactory] +) -> List[ProjectTypeFactory]: + project_types = [] + + for customer in customers: + for index in range(project_types_per_customer): + customer_id = customer.id + project_type = ProjectTypeFactory(customer_id=customer_id) + project_types.append(project_type) + + return project_types + + +def get_project_type_json(project_type_factory: ProjectTypeFactory) -> dict: + project_type = { + 'id': project_type_factory.id, + 'name': project_type_factory.name, + 'description': project_type_factory.description, + 'customer_id': project_type_factory.customer_id, + 'tenant_id': project_type_factory.tenant_id, + } + return project_type diff --git a/cosmosdb_emulator/time_tracker_cli/utils/time_entry.py b/cosmosdb_emulator/time_tracker_cli/utils/time_entry.py new file mode 100644 index 00000000..9689c763 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/utils/time_entry.py @@ -0,0 +1,147 @@ +import math +from datetime import datetime, timedelta +from typing import List + +from cosmosdb_emulator.time_tracker_cli.enums.entites import ( + TimeTrackerEntities, +) +from cosmosdb_emulator.time_tracker_cli.factories.activity_factory import ( + ActivityFactory, +) +from cosmosdb_emulator.time_tracker_cli.factories.project_factory import ( + ProjectFactory, +) +from cosmosdb_emulator.time_tracker_cli.factories.time_entry_factory import ( + TimeEntryFactory, +) +from cosmosdb_emulator.time_tracker_cli.utils.activity import get_activities +from cosmosdb_emulator.time_tracker_cli.utils.common import ( + get_random_element_from_list, +) +from cosmosdb_emulator.time_tracker_cli.utils.customer import get_customers +from cosmosdb_emulator.time_tracker_cli.utils.project import get_projects +from cosmosdb_emulator.time_tracker_cli.utils.project_type import ( + get_project_types, +) +from utils.azure_users import AzureConnection + +""" +Note that the time zone in the DB is different from how it is handled in the UI. +For example 13:00 in the DB in the UI it will be 08:00 +""" +base_hour = 13 +base_minute = 0 + + +def get_time_tracker_users_ids() -> List[str]: + time_tracker_users = AzureConnection().users() + users_ids = [user.id for user in time_tracker_users] + return users_ids + + +def get_reference_datetime( + entries_amount: int, daily_entries_amount: int +) -> datetime: + amount_of_days = math.ceil(entries_amount / daily_entries_amount) + base_datetime = datetime.today() - timedelta(days=amount_of_days) + base_datetime = base_datetime.replace(hour=base_hour, minute=base_minute) + return base_datetime + + +def get_next_day_from_datetime(reference_date: datetime) -> datetime: + next_day = reference_date + timedelta(days=1) + next_day = next_day.replace(hour=base_hour, minute=base_minute) + return next_day + + +def get_time_entry_duration() -> int: + time_entries_duration = [30, 40, 50, 60, 70, 80, 90, 100, 120] + time_entry_duration = get_random_element_from_list(time_entries_duration) + return time_entry_duration + + +def get_time_entry_end_date(entry_start_date) -> datetime: + entry_duration = get_time_entry_duration() + entry_end_date = entry_start_date + timedelta(minutes=entry_duration) + return entry_end_date + + +def get_related_information_for_entries() -> dict: + customers = get_customers(customer_amount=10) + project_types = get_project_types( + project_types_per_customer=2, customers=customers + ) + projects = get_projects( + projects_per_project_type=1, project_types=project_types + ) + + activities = get_activities(activities_amount=20) + + related_information = { + TimeTrackerEntities.CUSTOMER.value: customers, + TimeTrackerEntities.PROJECT_TYPE.value: project_types, + TimeTrackerEntities.PROJECT.value: projects, + TimeTrackerEntities.ACTIVITY.value: activities, + } + + return related_information + + +def generate_entries_per_user( + daily_entries_amount: int, + entries_amount: int, + owner_id: str, + projects: List[ProjectFactory], + activities: List[ActivityFactory], +) -> List[TimeEntryFactory]: + + utc_format = '%Y-%m-%dT%H:%M:%SZ' + entries_per_user = [] + daily_entries = 0 + projects_ids = [project.id for project in projects] + activities_ids = [activity.id for activity in activities] + + reference_datetime = get_reference_datetime( + entries_amount=entries_amount, + daily_entries_amount=daily_entries_amount, + ) + + for index in range(entries_amount): + project_id = get_random_element_from_list(projects_ids) + activity_id = get_random_element_from_list(activities_ids) + start_date = reference_datetime + timedelta(minutes=1) + + if daily_entries == daily_entries_amount: + start_date = get_next_day_from_datetime(reference_date=start_date) + daily_entries = 0 + + end_date = get_time_entry_end_date(start_date) + + current_entry = TimeEntryFactory( + start_date=start_date.strftime(utc_format), + end_date=end_date.strftime(utc_format), + owner_id=owner_id, + project_id=project_id, + activity_id=activity_id, + ) + + entries_per_user.append(current_entry) + daily_entries += 1 + reference_datetime = end_date + + return entries_per_user + + +def get_entry_json(time_entry_factory: TimeEntryFactory) -> dict: + time_entry = { + 'project_id': time_entry_factory.project_id, + 'activity_id': time_entry_factory.activity_id, + 'technologies': time_entry_factory.technologies, + 'description': time_entry_factory.description, + 'start_date': time_entry_factory.start_date, + 'owner_id': time_entry_factory.owner_id, + 'id': time_entry_factory.id, + 'tenant_id': time_entry_factory.tenant_id, + 'end_date': time_entry_factory.end_date, + } + return time_entry diff --git a/cosmosdb_emulator/time_tracker_cli/validators/max_amount.py b/cosmosdb_emulator/time_tracker_cli/validators/max_amount.py new file mode 100644 index 00000000..68f106a7 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/validators/max_amount.py @@ -0,0 +1,21 @@ +from prompt_toolkit.validation import ValidationError + +from cosmosdb_emulator.time_tracker_cli.validators.number import ( + NumberValidator, +) + + +class MaxAmountValidator(NumberValidator): + def __init__(self, max_amount, error_message): + self.max_amount = max_amount + self.error_message = error_message + + def validate(self, document): + super().validate(document) + + entered_value = int(document.text) + + if entered_value > self.max_amount: + raise ValidationError( + message=self.error_message, cursor_position=len(document.text) + ) diff --git a/cosmosdb_emulator/time_tracker_cli/validators/number.py b/cosmosdb_emulator/time_tracker_cli/validators/number.py new file mode 100644 index 00000000..6bf34589 --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/validators/number.py @@ -0,0 +1,21 @@ +from prompt_toolkit.validation import Validator, ValidationError + + +class NumberValidator(Validator): + def validate(self, document): + value_entered = document.text + is_number = value_entered.isnumeric() + + if not is_number: + raise ValidationError( + message='Please provide only a numeric value', + cursor_position=len(value_entered), + ) + + entered_number = int(value_entered) + + if entered_number < 1: + raise ValidationError( + message='Please provide numbers greater than 0', + cursor_position=len(value_entered), + ) diff --git a/cosmosdb_emulator/time_tracker_cli/validators/uuid.py b/cosmosdb_emulator/time_tracker_cli/validators/uuid.py new file mode 100644 index 00000000..27835bfa --- /dev/null +++ b/cosmosdb_emulator/time_tracker_cli/validators/uuid.py @@ -0,0 +1,15 @@ +import uuid + +from prompt_toolkit.validation import Validator, ValidationError + + +class UUIDValidator(Validator): + def validate(self, document): + value_entered = document.text + try: + uuid.UUID(value_entered, version=4) + except ValueError: + raise ValidationError( + message='Please provide a valid UUID', + cursor_position=len(value_entered), + ) diff --git a/cosmosdb_emulator/verify_environment.sh b/cosmosdb_emulator/verify_environment.sh new file mode 100644 index 00000000..8c0104d6 --- /dev/null +++ b/cosmosdb_emulator/verify_environment.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "We are checking the development environment..." + +DATABASE_EMULATOR_KEY="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" +DATABASE_ENV_KEY=$DATABASE_MASTER_KEY + +if [ "$DATABASE_EMULATOR_KEY" != "$DATABASE_ENV_KEY" ]; then + echo "You are trying to run this CLI in a non-development environment. We can not proceed with this action" + exit 0 +fi + +echo "GREAT! You are on development environment" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5c9f7349..a7ae7ce5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,39 +12,27 @@ services: - .env volumes: - .:/usr/src/app - # depends_on: - # - cosmosdb + depends_on: + - cosmosdb entrypoint: - /bin/sh - - ./cosmosdb-emulator/entrypoint.sh - # networks: - # services_net: - # ipv4_address: 172.20.0.77 + - ./cosmosdb_emulator/entrypoint.sh cosmosdb: container_name: "azurecosmosemulator" hostname: "azurecosmosemulator" image: 'mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator' tty: true + cpu_count: 2 + restart: always mem_limit: 3GB ports: - - '8081:8081' - - '10251:10251' - - '10252:10252' - - '10253:10253' - - '10254:10254' + - '8081:8081' + - '10251:10251' + - '10252:10252' + - '10253:10253' + - '10254:10254' environment: - - AZURE_COSMOS_EMULATOR_PARTITION_COUNT=7 - - AZURE_COSMOS_EMULATOR_ARGS=/alternativenames=azurecosmosemulator - - # networks: - # services_net: - # ipv4_address: 172.20.0.78 - - -# networks: -# services_net: -# ipam: -# driver: default -# config: -# - subnet: 172.20.0.0/16 \ No newline at end of file + - AZURE_COSMOS_EMULATOR_PARTITION_COUNT=7 + - AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE=true + - AZURE_COSMOS_EMULATOR_ARGS=/alternativenames=azurecosmosemulator \ No newline at end of file diff --git a/requirements/time_tracker_api/dev.txt b/requirements/time_tracker_api/dev.txt index c85a2bbc..6d8a1599 100644 --- a/requirements/time_tracker_api/dev.txt +++ b/requirements/time_tracker_api/dev.txt @@ -16,3 +16,8 @@ coverage==4.5.1 # Git hooks pre-commit==2.2.0 + +# CLI tools +PyInquirer==1.0.3 +pyfiglet==0.7 +factory_boy==3.2.0 \ No newline at end of file