Completed
Push — master ( c4c141...16569e )
by Chris
01:28
created

dump_fixtures()   F

Complexity

Conditions 9

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
dl 0
loc 33
rs 3
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""
4
flask_jsondash.model_factories
5
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
7
Data generation utilities for all charts and dashboards.
8
9
:copyright: (c) 2016 by Chris Tabor.
10
:license: MIT, see LICENSE for more details.
11
"""
12
13
import os
14
import json
15
from datetime import datetime as dt
16
from random import choice, randrange
17
from uuid import uuid1
18
19
import click
20
from werkzeug.datastructures import ImmutableMultiDict
21
22
from . import db, settings
23
24
adapter = db.get_db_handler()
25
26
27
def get_random_group():
28
    """Return a random namespaced group of charts.
29
30
    Returns:
31
        str: A random chart name.
32
33
    Examples:
34
        >>> get_random_group()
35
        >>> {'charts': [...], 'dependencies', 'js_url': '...', ...}
36
    """
37
    return choice(list(settings.CHARTS_CONFIG.values()))
38
39
40
def get_random_chart(group):
41
    """Get a random chart from a specific chart sub-group.
42
43
    Args:
44
        group (dict): A group from the global chart settings config.
45
    """
46
    return choice(list(group['charts']))
47
48
49
def load_fixtures(path):
50
    """Generate all dashboards using any given schemas.
51
52
    Note: this does NOT account for nested folders,
53
    only all json in the given directory.
54
55
    Args:
56
        name (path): The relative folder path for all json configs
57
    """
58
    click.echo('Loading fixtures for path ' + path)
59
    files = [f for f in os.listdir(path) if f.endswith('.json')]
60
    for file in files:
61
        file = '{}/{}/{}'.format(os.getcwd(), path, file)
62
        click.echo('Loading ' + file)
63
        with open(file, 'r') as fixture:
64
            data = json.loads(fixture.read())
65
            adapter.create(data=data)
66
67
68
def dump_fixtures(path, delete_after=False):
69
    """Generate fixture data (json) from existing records in db.
70
71
    Args:
72
        name (path): The folder path to save configs in (must exist first!)
73
    """
74
    click.echo('Saving db as fixtures to: ' + path)
75
    # If an error occured, don't delete any records
76
    errors = []
77
    # Allow absolute OR relative paths.
78
    cwd = '' if path.startswith('/') else os.getcwd()
79
    for dashboard in adapter.read():
80
        # Mongo id is not serializeable
81
        if '_id' in dashboard:
82
            dashboard.pop('_id')
83
        # Update date records
84
        dashboard.update(date=str(dashboard['date']))
85
        name = '{}-{}'.format(dashboard['name'], dashboard['id'])
86
        name = name.replace(' ', '-').lower()
87
        fullpath = '{}{}/{}.json'.format(cwd, path, name)
88
        click.echo('Saving fixture: ' + fullpath)
89
        try:
90
            with open(fullpath, 'w') as fixture:
91
                fixture.write(json.dumps(dashboard, indent=4))
92
        except Exception as e:
93
            print(e)
94
            errors.append(fullpath)
95
            continue
96
    if delete_after and not errors:
97
        adapter.delete_all()
98
    if errors:
99
        click.echo('The following records could not be dumped: {}'.format(
100
            errors
101
        ))
102
103
104
def make_fake_dashboard(name='Random chart', max_charts=10):
105
    """Generate fake dashboard data with a specific number of random charts.
106
107
    Args:
108
        name (str): The name of the new dashboard (default: {'Random chart'})
109
        max_charts (int): Max number of charts to make (default: {10})
110
111
    Returns:
112
        dict: The chart configuration.
113
    """
114
    charts = ImmutableMultiDict([
115
        make_fake_chart_data() for _ in range(max_charts)]
116
    )
117
    return dict(
118
        name=name,
119
        created_by='global',
120
        date=dt.now(),
121
        modules=db.format_charts(charts),
122
        id=str(uuid1()),
123
    )
124
125
126
def make_fake_chart_data(**kwargs):
127
    """Return chart data in required format.
128
129
    Args:
130
        name (None, optional): The name of the chart.
131
        height (None, optional): The height of the chart.
132
        width (None, optional): The width of the chart.
133
        data_source (None, optional): The data source (url) for the chart.
134
135
    Returns:
136
        tuple: A 2-tuple of type (string_label, jsonstring) for the chart.
137
138
    Examples:
139
        >>> make_fake_chart_date(width=10, height=10, name='foo')
140
        >>> ('foo', '{"name": "foo", "height": 10, "width": 10}')
141
    """
142
    _uuid = str(uuid1())
143
    # All of these chart types have example endpoints to use locally.
144
    chart = choice(['bar', 'line', 'step', 'area'])
145
    will_use_inputs = randrange(1, 100) > 50
146
    url = 'http://127.0.0.1:5004/bar'
147
    config = dict(
148
        name=kwargs.get('name'),
149
        width=kwargs.get('width', randrange(200, 2000)),
150
        height=kwargs.get('height', randrange(200, 2000)),
151
        type=chart,
152
        family='C3',
153
        dataSource=kwargs.get('data_source', url),
154
        guid=_uuid,
155
    )
156
    if will_use_inputs:
157
        config.update(
158
            inputs=dict(
159
                btn_classes=['btn', 'btn-info', 'btn-sm'],
160
                submit_text='Submit',
161
                help_text='Change a value',
162
                options=[
163
                    dict(type='number', name='range', label='Number'),
164
                ]
165
            )
166
        )
167
    return (
168
        'module_{}'.format(_uuid),
169
        json.dumps(config)
170
    )
171
172
173
@click.command()
174
@click.option('--records',
175
              default=10,
176
              help='Number of records to insert fake dashboard data into DB.')
177
@click.option('--max-charts',
178
              default=5,
179
              help='Number of charts per dashboard to create.')
180
@click.option('--fixtures',
181
              default=None,
182
              help='A path to load existing configurations from.')
183
@click.option('--dump',
184
              default=None,
185
              help='A path to dump db records, as json dashboard configs.')
186
@click.option('--delete',
187
              is_flag=True,
188
              default=False,
189
              help='A path to dump db records, as json dashboard configs.')
190
def insert_dashboards(records, max_charts, fixtures, dump, delete):
191
    """Insert a number of dashboard records into the database."""
192
    if fixtures is not None:
193
        return load_fixtures(fixtures)
194
    if dump is not None:
195
        return dump_fixtures(dump, delete_after=delete)
196
    for i in range(records):
197
        data = make_fake_dashboard(
198
            name='Test chart #{}'.format(i),
199
            max_charts=max_charts)
200
        adapter.create(data=data)
201
202
203
def delete_all():
204
    """Delete all dashboards."""
205
    adapter.delete_all()
206
207
208
if __name__ == '__main__':
209
    insert_dashboards()
210