birth_names.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. import json
  18. import textwrap
  19. from typing import Dict, Union
  20. import pandas as pd
  21. from sqlalchemy import DateTime, String
  22. from sqlalchemy.sql import column
  23. from superset import db, security_manager
  24. from superset.connectors.sqla.models import SqlMetric, TableColumn
  25. from superset.models.core import Database
  26. from superset.models.dashboard import Dashboard
  27. from superset.models.slice import Slice
  28. from superset.utils.core import get_example_database
  29. from .helpers import (
  30. config,
  31. get_example_data,
  32. get_slice_json,
  33. merge_slice,
  34. misc_dash_slices,
  35. TBL,
  36. update_slice_ids,
  37. )
  38. def gen_filter(
  39. subject: str, comparator: str, operator: str = "=="
  40. ) -> Dict[str, Union[bool, str]]:
  41. return {
  42. "clause": "WHERE",
  43. "comparator": comparator,
  44. "expressionType": "SIMPLE",
  45. "operator": operator,
  46. "subject": subject,
  47. }
  48. def load_data(tbl_name: str, database: Database) -> None:
  49. pdf = pd.read_json(get_example_data("birth_names.json.gz"))
  50. pdf.ds = pd.to_datetime(pdf.ds, unit="ms")
  51. pdf.to_sql(
  52. tbl_name,
  53. database.get_sqla_engine(),
  54. if_exists="replace",
  55. chunksize=500,
  56. dtype={
  57. "ds": DateTime,
  58. "gender": String(16),
  59. "state": String(10),
  60. "name": String(255),
  61. },
  62. index=False,
  63. )
  64. print("Done loading table!")
  65. print("-" * 80)
  66. def load_birth_names(only_metadata: bool = False, force: bool = False) -> None:
  67. """Loading birth name dataset from a zip file in the repo"""
  68. # pylint: disable=too-many-locals
  69. tbl_name = "birth_names"
  70. database = get_example_database()
  71. table_exists = database.has_table_by_name(tbl_name)
  72. if not only_metadata and (not table_exists or force):
  73. load_data(tbl_name, database)
  74. obj = db.session.query(TBL).filter_by(table_name=tbl_name).first()
  75. if not obj:
  76. print(f"Creating table [{tbl_name}] reference")
  77. obj = TBL(table_name=tbl_name)
  78. db.session.add(obj)
  79. obj.main_dttm_col = "ds"
  80. obj.database = database
  81. obj.filter_select_enabled = True
  82. if not any(col.column_name == "num_california" for col in obj.columns):
  83. col_state = str(column("state").compile(db.engine))
  84. col_num = str(column("num").compile(db.engine))
  85. obj.columns.append(
  86. TableColumn(
  87. column_name="num_california",
  88. expression=f"CASE WHEN {col_state} = 'CA' THEN {col_num} ELSE 0 END",
  89. )
  90. )
  91. if not any(col.metric_name == "sum__num" for col in obj.metrics):
  92. col = str(column("num").compile(db.engine))
  93. obj.metrics.append(SqlMetric(metric_name="sum__num", expression=f"SUM({col})"))
  94. db.session.commit()
  95. obj.fetch_metadata()
  96. tbl = obj
  97. metrics = [
  98. {
  99. "expressionType": "SIMPLE",
  100. "column": {"column_name": "num", "type": "BIGINT"},
  101. "aggregate": "SUM",
  102. "label": "Births",
  103. "optionName": "metric_11",
  104. }
  105. ]
  106. metric = "sum__num"
  107. defaults = {
  108. "compare_lag": "10",
  109. "compare_suffix": "o10Y",
  110. "limit": "25",
  111. "granularity_sqla": "ds",
  112. "groupby": [],
  113. "row_limit": config["ROW_LIMIT"],
  114. "since": "100 years ago",
  115. "until": "now",
  116. "viz_type": "table",
  117. "markup_type": "markdown",
  118. }
  119. admin = security_manager.find_user("admin")
  120. print("Creating some slices")
  121. slices = [
  122. Slice(
  123. slice_name="Participants",
  124. viz_type="big_number",
  125. datasource_type="table",
  126. datasource_id=tbl.id,
  127. params=get_slice_json(
  128. defaults,
  129. viz_type="big_number",
  130. granularity_sqla="ds",
  131. compare_lag="5",
  132. compare_suffix="over 5Y",
  133. metric=metric,
  134. ),
  135. ),
  136. Slice(
  137. slice_name="Genders",
  138. viz_type="pie",
  139. datasource_type="table",
  140. datasource_id=tbl.id,
  141. params=get_slice_json(
  142. defaults, viz_type="pie", groupby=["gender"], metric=metric
  143. ),
  144. ),
  145. Slice(
  146. slice_name="Trends",
  147. viz_type="line",
  148. datasource_type="table",
  149. datasource_id=tbl.id,
  150. params=get_slice_json(
  151. defaults,
  152. viz_type="line",
  153. groupby=["name"],
  154. granularity_sqla="ds",
  155. rich_tooltip=True,
  156. show_legend=True,
  157. metrics=metrics,
  158. ),
  159. ),
  160. Slice(
  161. slice_name="Genders by State",
  162. viz_type="dist_bar",
  163. datasource_type="table",
  164. datasource_id=tbl.id,
  165. params=get_slice_json(
  166. defaults,
  167. adhoc_filters=[
  168. {
  169. "clause": "WHERE",
  170. "expressionType": "SIMPLE",
  171. "filterOptionName": "2745eae5",
  172. "comparator": ["other"],
  173. "operator": "NOT IN",
  174. "subject": "state",
  175. }
  176. ],
  177. viz_type="dist_bar",
  178. metrics=[
  179. {
  180. "expressionType": "SIMPLE",
  181. "column": {"column_name": "sum_boys", "type": "BIGINT(20)"},
  182. "aggregate": "SUM",
  183. "label": "Boys",
  184. "optionName": "metric_11",
  185. },
  186. {
  187. "expressionType": "SIMPLE",
  188. "column": {"column_name": "sum_girls", "type": "BIGINT(20)"},
  189. "aggregate": "SUM",
  190. "label": "Girls",
  191. "optionName": "metric_12",
  192. },
  193. ],
  194. groupby=["state"],
  195. ),
  196. ),
  197. Slice(
  198. slice_name="Girls",
  199. viz_type="table",
  200. datasource_type="table",
  201. datasource_id=tbl.id,
  202. params=get_slice_json(
  203. defaults,
  204. groupby=["name"],
  205. adhoc_filters=[gen_filter("gender", "girl")],
  206. row_limit=50,
  207. timeseries_limit_metric="sum__num",
  208. metrics=metrics,
  209. ),
  210. ),
  211. Slice(
  212. slice_name="Girl Name Cloud",
  213. viz_type="word_cloud",
  214. datasource_type="table",
  215. datasource_id=tbl.id,
  216. params=get_slice_json(
  217. defaults,
  218. viz_type="word_cloud",
  219. size_from="10",
  220. series="name",
  221. size_to="70",
  222. rotation="square",
  223. limit="100",
  224. adhoc_filters=[gen_filter("gender", "girl")],
  225. metric=metric,
  226. ),
  227. ),
  228. Slice(
  229. slice_name="Boys",
  230. viz_type="table",
  231. datasource_type="table",
  232. datasource_id=tbl.id,
  233. params=get_slice_json(
  234. defaults,
  235. groupby=["name"],
  236. adhoc_filters=[gen_filter("gender", "boy")],
  237. row_limit=50,
  238. metrics=metrics,
  239. ),
  240. ),
  241. Slice(
  242. slice_name="Boy Name Cloud",
  243. viz_type="word_cloud",
  244. datasource_type="table",
  245. datasource_id=tbl.id,
  246. params=get_slice_json(
  247. defaults,
  248. viz_type="word_cloud",
  249. size_from="10",
  250. series="name",
  251. size_to="70",
  252. rotation="square",
  253. limit="100",
  254. adhoc_filters=[gen_filter("gender", "boy")],
  255. metric=metric,
  256. ),
  257. ),
  258. Slice(
  259. slice_name="Top 10 Girl Name Share",
  260. viz_type="area",
  261. datasource_type="table",
  262. datasource_id=tbl.id,
  263. params=get_slice_json(
  264. defaults,
  265. adhoc_filters=[gen_filter("gender", "girl")],
  266. comparison_type="values",
  267. groupby=["name"],
  268. limit=10,
  269. stacked_style="expand",
  270. time_grain_sqla="P1D",
  271. viz_type="area",
  272. x_axis_forma="smart_date",
  273. metrics=metrics,
  274. ),
  275. ),
  276. Slice(
  277. slice_name="Top 10 Boy Name Share",
  278. viz_type="area",
  279. datasource_type="table",
  280. datasource_id=tbl.id,
  281. params=get_slice_json(
  282. defaults,
  283. adhoc_filters=[gen_filter("gender", "boy")],
  284. comparison_type="values",
  285. groupby=["name"],
  286. limit=10,
  287. stacked_style="expand",
  288. time_grain_sqla="P1D",
  289. viz_type="area",
  290. x_axis_forma="smart_date",
  291. metrics=metrics,
  292. ),
  293. ),
  294. ]
  295. misc_slices = [
  296. Slice(
  297. slice_name="Average and Sum Trends",
  298. viz_type="dual_line",
  299. datasource_type="table",
  300. datasource_id=tbl.id,
  301. params=get_slice_json(
  302. defaults,
  303. viz_type="dual_line",
  304. metric={
  305. "expressionType": "SIMPLE",
  306. "column": {"column_name": "num", "type": "BIGINT(20)"},
  307. "aggregate": "AVG",
  308. "label": "AVG(num)",
  309. "optionName": "metric_vgops097wej_g8uff99zhk7",
  310. },
  311. metric_2="sum__num",
  312. granularity_sqla="ds",
  313. metrics=metrics,
  314. ),
  315. ),
  316. Slice(
  317. slice_name="Num Births Trend",
  318. viz_type="line",
  319. datasource_type="table",
  320. datasource_id=tbl.id,
  321. params=get_slice_json(defaults, viz_type="line", metrics=metrics),
  322. ),
  323. Slice(
  324. slice_name="Daily Totals",
  325. viz_type="table",
  326. datasource_type="table",
  327. datasource_id=tbl.id,
  328. created_by=admin,
  329. params=get_slice_json(
  330. defaults,
  331. groupby=["ds"],
  332. since="40 years ago",
  333. until="now",
  334. viz_type="table",
  335. metrics=metrics,
  336. ),
  337. ),
  338. Slice(
  339. slice_name="Number of California Births",
  340. viz_type="big_number_total",
  341. datasource_type="table",
  342. datasource_id=tbl.id,
  343. params=get_slice_json(
  344. defaults,
  345. metric={
  346. "expressionType": "SIMPLE",
  347. "column": {
  348. "column_name": "num_california",
  349. "expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
  350. },
  351. "aggregate": "SUM",
  352. "label": "SUM(num_california)",
  353. },
  354. viz_type="big_number_total",
  355. granularity_sqla="ds",
  356. ),
  357. ),
  358. Slice(
  359. slice_name="Top 10 California Names Timeseries",
  360. viz_type="line",
  361. datasource_type="table",
  362. datasource_id=tbl.id,
  363. params=get_slice_json(
  364. defaults,
  365. metrics=[
  366. {
  367. "expressionType": "SIMPLE",
  368. "column": {
  369. "column_name": "num_california",
  370. "expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
  371. },
  372. "aggregate": "SUM",
  373. "label": "SUM(num_california)",
  374. }
  375. ],
  376. viz_type="line",
  377. granularity_sqla="ds",
  378. groupby=["name"],
  379. timeseries_limit_metric={
  380. "expressionType": "SIMPLE",
  381. "column": {
  382. "column_name": "num_california",
  383. "expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
  384. },
  385. "aggregate": "SUM",
  386. "label": "SUM(num_california)",
  387. },
  388. limit="10",
  389. ),
  390. ),
  391. Slice(
  392. slice_name="Names Sorted by Num in California",
  393. viz_type="table",
  394. datasource_type="table",
  395. datasource_id=tbl.id,
  396. params=get_slice_json(
  397. defaults,
  398. metrics=metrics,
  399. groupby=["name"],
  400. row_limit=50,
  401. timeseries_limit_metric={
  402. "expressionType": "SIMPLE",
  403. "column": {
  404. "column_name": "num_california",
  405. "expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
  406. },
  407. "aggregate": "SUM",
  408. "label": "SUM(num_california)",
  409. },
  410. ),
  411. ),
  412. Slice(
  413. slice_name="Number of Girls",
  414. viz_type="big_number_total",
  415. datasource_type="table",
  416. datasource_id=tbl.id,
  417. params=get_slice_json(
  418. defaults,
  419. metric=metric,
  420. viz_type="big_number_total",
  421. granularity_sqla="ds",
  422. adhoc_filters=[gen_filter("gender", "girl")],
  423. subheader="total female participants",
  424. ),
  425. ),
  426. Slice(
  427. slice_name="Pivot Table",
  428. viz_type="pivot_table",
  429. datasource_type="table",
  430. datasource_id=tbl.id,
  431. params=get_slice_json(
  432. defaults,
  433. viz_type="pivot_table",
  434. groupby=["name"],
  435. columns=["state"],
  436. metrics=metrics,
  437. ),
  438. ),
  439. ]
  440. for slc in slices:
  441. merge_slice(slc)
  442. for slc in misc_slices:
  443. merge_slice(slc)
  444. misc_dash_slices.add(slc.slice_name)
  445. print("Creating a dashboard")
  446. dash = db.session.query(Dashboard).filter_by(slug="births").first()
  447. if not dash:
  448. dash = Dashboard()
  449. db.session.add(dash)
  450. dash.published = True
  451. dash.json_metadata = textwrap.dedent(
  452. """\
  453. {
  454. "label_colors": {
  455. "Girls": "#FF69B4",
  456. "Boys": "#ADD8E6",
  457. "girl": "#FF69B4",
  458. "boy": "#ADD8E6"
  459. }
  460. }"""
  461. )
  462. js = textwrap.dedent(
  463. # pylint: disable=line-too-long
  464. """\
  465. {
  466. "CHART-6GdlekVise": {
  467. "children": [],
  468. "id": "CHART-6GdlekVise",
  469. "meta": {
  470. "chartId": 5547,
  471. "height": 50,
  472. "sliceName": "Top 10 Girl Name Share",
  473. "width": 5
  474. },
  475. "parents": [
  476. "ROOT_ID",
  477. "GRID_ID",
  478. "ROW-eh0w37bWbR"
  479. ],
  480. "type": "CHART"
  481. },
  482. "CHART-6n9jxb30JG": {
  483. "children": [],
  484. "id": "CHART-6n9jxb30JG",
  485. "meta": {
  486. "chartId": 5540,
  487. "height": 36,
  488. "sliceName": "Genders by State",
  489. "width": 5
  490. },
  491. "parents": [
  492. "ROOT_ID",
  493. "GRID_ID",
  494. "ROW--EyBZQlDi"
  495. ],
  496. "type": "CHART"
  497. },
  498. "CHART-Jj9qh1ol-N": {
  499. "children": [],
  500. "id": "CHART-Jj9qh1ol-N",
  501. "meta": {
  502. "chartId": 5545,
  503. "height": 50,
  504. "sliceName": "Boy Name Cloud",
  505. "width": 4
  506. },
  507. "parents": [
  508. "ROOT_ID",
  509. "GRID_ID",
  510. "ROW-kzWtcvo8R1"
  511. ],
  512. "type": "CHART"
  513. },
  514. "CHART-ODvantb_bF": {
  515. "children": [],
  516. "id": "CHART-ODvantb_bF",
  517. "meta": {
  518. "chartId": 5548,
  519. "height": 50,
  520. "sliceName": "Top 10 Boy Name Share",
  521. "width": 5
  522. },
  523. "parents": [
  524. "ROOT_ID",
  525. "GRID_ID",
  526. "ROW-kzWtcvo8R1"
  527. ],
  528. "type": "CHART"
  529. },
  530. "CHART-PAXUUqwmX9": {
  531. "children": [],
  532. "id": "CHART-PAXUUqwmX9",
  533. "meta": {
  534. "chartId": 5538,
  535. "height": 34,
  536. "sliceName": "Genders",
  537. "width": 3
  538. },
  539. "parents": [
  540. "ROOT_ID",
  541. "GRID_ID",
  542. "ROW-2n0XgiHDgs"
  543. ],
  544. "type": "CHART"
  545. },
  546. "CHART-_T6n_K9iQN": {
  547. "children": [],
  548. "id": "CHART-_T6n_K9iQN",
  549. "meta": {
  550. "chartId": 5539,
  551. "height": 36,
  552. "sliceName": "Trends",
  553. "width": 7
  554. },
  555. "parents": [
  556. "ROOT_ID",
  557. "GRID_ID",
  558. "ROW--EyBZQlDi"
  559. ],
  560. "type": "CHART"
  561. },
  562. "CHART-eNY0tcE_ic": {
  563. "children": [],
  564. "id": "CHART-eNY0tcE_ic",
  565. "meta": {
  566. "chartId": 5537,
  567. "height": 34,
  568. "sliceName": "Participants",
  569. "width": 3
  570. },
  571. "parents": [
  572. "ROOT_ID",
  573. "GRID_ID",
  574. "ROW-2n0XgiHDgs"
  575. ],
  576. "type": "CHART"
  577. },
  578. "CHART-g075mMgyYb": {
  579. "children": [],
  580. "id": "CHART-g075mMgyYb",
  581. "meta": {
  582. "chartId": 5541,
  583. "height": 50,
  584. "sliceName": "Girls",
  585. "width": 3
  586. },
  587. "parents": [
  588. "ROOT_ID",
  589. "GRID_ID",
  590. "ROW-eh0w37bWbR"
  591. ],
  592. "type": "CHART"
  593. },
  594. "CHART-n-zGGE6S1y": {
  595. "children": [],
  596. "id": "CHART-n-zGGE6S1y",
  597. "meta": {
  598. "chartId": 5542,
  599. "height": 50,
  600. "sliceName": "Girl Name Cloud",
  601. "width": 4
  602. },
  603. "parents": [
  604. "ROOT_ID",
  605. "GRID_ID",
  606. "ROW-eh0w37bWbR"
  607. ],
  608. "type": "CHART"
  609. },
  610. "CHART-vJIPjmcbD3": {
  611. "children": [],
  612. "id": "CHART-vJIPjmcbD3",
  613. "meta": {
  614. "chartId": 5543,
  615. "height": 50,
  616. "sliceName": "Boys",
  617. "width": 3
  618. },
  619. "parents": [
  620. "ROOT_ID",
  621. "GRID_ID",
  622. "ROW-kzWtcvo8R1"
  623. ],
  624. "type": "CHART"
  625. },
  626. "DASHBOARD_VERSION_KEY": "v2",
  627. "GRID_ID": {
  628. "children": [
  629. "ROW-2n0XgiHDgs",
  630. "ROW--EyBZQlDi",
  631. "ROW-eh0w37bWbR",
  632. "ROW-kzWtcvo8R1"
  633. ],
  634. "id": "GRID_ID",
  635. "parents": [
  636. "ROOT_ID"
  637. ],
  638. "type": "GRID"
  639. },
  640. "HEADER_ID": {
  641. "id": "HEADER_ID",
  642. "meta": {
  643. "text": "Births"
  644. },
  645. "type": "HEADER"
  646. },
  647. "MARKDOWN-zaflB60tbC": {
  648. "children": [],
  649. "id": "MARKDOWN-zaflB60tbC",
  650. "meta": {
  651. "code": "<div style=\\"text-align:center\\"> <h1>Birth Names Dashboard</h1> <img src=\\"/static/assets/images/babies.png\\" style=\\"width:50%;\\"></div>",
  652. "height": 34,
  653. "width": 6
  654. },
  655. "parents": [
  656. "ROOT_ID",
  657. "GRID_ID",
  658. "ROW-2n0XgiHDgs"
  659. ],
  660. "type": "MARKDOWN"
  661. },
  662. "ROOT_ID": {
  663. "children": [
  664. "GRID_ID"
  665. ],
  666. "id": "ROOT_ID",
  667. "type": "ROOT"
  668. },
  669. "ROW--EyBZQlDi": {
  670. "children": [
  671. "CHART-_T6n_K9iQN",
  672. "CHART-6n9jxb30JG"
  673. ],
  674. "id": "ROW--EyBZQlDi",
  675. "meta": {
  676. "background": "BACKGROUND_TRANSPARENT"
  677. },
  678. "parents": [
  679. "ROOT_ID",
  680. "GRID_ID"
  681. ],
  682. "type": "ROW"
  683. },
  684. "ROW-2n0XgiHDgs": {
  685. "children": [
  686. "CHART-eNY0tcE_ic",
  687. "MARKDOWN-zaflB60tbC",
  688. "CHART-PAXUUqwmX9"
  689. ],
  690. "id": "ROW-2n0XgiHDgs",
  691. "meta": {
  692. "background": "BACKGROUND_TRANSPARENT"
  693. },
  694. "parents": [
  695. "ROOT_ID",
  696. "GRID_ID"
  697. ],
  698. "type": "ROW"
  699. },
  700. "ROW-eh0w37bWbR": {
  701. "children": [
  702. "CHART-g075mMgyYb",
  703. "CHART-n-zGGE6S1y",
  704. "CHART-6GdlekVise"
  705. ],
  706. "id": "ROW-eh0w37bWbR",
  707. "meta": {
  708. "background": "BACKGROUND_TRANSPARENT"
  709. },
  710. "parents": [
  711. "ROOT_ID",
  712. "GRID_ID"
  713. ],
  714. "type": "ROW"
  715. },
  716. "ROW-kzWtcvo8R1": {
  717. "children": [
  718. "CHART-vJIPjmcbD3",
  719. "CHART-Jj9qh1ol-N",
  720. "CHART-ODvantb_bF"
  721. ],
  722. "id": "ROW-kzWtcvo8R1",
  723. "meta": {
  724. "background": "BACKGROUND_TRANSPARENT"
  725. },
  726. "parents": [
  727. "ROOT_ID",
  728. "GRID_ID"
  729. ],
  730. "type": "ROW"
  731. }
  732. }
  733. """ # pylint: enable=line-too-long
  734. )
  735. pos = json.loads(js)
  736. # dashboard v2 doesn't allow add markup slice
  737. dash.slices = [slc for slc in slices if slc.viz_type != "markup"]
  738. update_slice_ids(pos, dash.slices)
  739. dash.dashboard_title = "USA Births Names"
  740. dash.position_json = json.dumps(pos, indent=4)
  741. dash.slug = "births"
  742. db.session.commit()