{"id":11626,"date":"2022-09-21T13:36:27","date_gmt":"2022-09-21T13:36:27","guid":{"rendered":"https:\/\/predictly.se\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/"},"modified":"2024-05-08T11:46:39","modified_gmt":"2024-05-08T11:46:39","slug":"using-circleci-to-fix-ci-cd-in-dataform-part-2-2","status":"publish","type":"post","link":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/","title":{"rendered":"Using CircleCI to fix CI\/CD in Dataform &#8211; Part 2\/2"},"content":{"rendered":"<p>In my <a href=\"https:\/\/www.predictly.se\/anvand-circleci-for-att-fixa-ci-cd-i-dataform-del-1-2\/\" target=\"_blank\" rel=\"noopener\">previous article<\/a> I tried to sketch a CI\/CD solution for Dataform using CircleCI in a compact way; I had to leave out some details and obstacles that usually surface along the way. Today, I&#8217;d like to explore that a little further by looking at a real deal breaker: how to manage changes in the datamodel.<\/p>\n<p>This has always been a delicate question. Most of the cases, there are more or less advanced SQL scripts that are supposed to do the job, some teams have discovered the power of <a href=\"https:\/\/www.liquibase.org\/\" target=\"_blank\" rel=\"noopener\">Liquibase<\/a> &#8211; which unfortunately doesn&#8217;t support BigQuery at this point &#8211; and some are still scratching their heads.<\/p>\n<p>One of the core objectives of CI\/CD is to remove the human factor from releasing and deploying. There shouldn&#8217;t be any obscure script that needs to be run and can be forgotten &#8211; or is run but against the wrong database or schema. We all have seen it happen. Instead, this should be done in a controlled fashion, reproducible, automated.<\/p>\n<p>Looking at a typical DWH implemented in Dataform: which objects and potential changes do we have? Which are handled automatically and which need some attention? To get a clearer picture I put it all in a table:<\/p>\n<p><img decoding=\"async\" class=\"lazyload alignnone\" src=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27560%27%20height%3D%27141%27%20viewBox%3D%270%200%20560%20141%27%3E%3Crect%20width%3D%27560%27%20height%3D%27141%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-orig-src=\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/CircleCI-Dataform2-1.png\" alt=\"Table\" width=\"560\" height=\"141\"><\/p>\n<p>As we can see, problems exist with both incremental and partitioned tables. There is a simple solution, though: the &#8220;Run with full refresh&#8221; option. This means that Dataform will force a complete recreation of all tables and views in the run, building them from scratch &#8211; and it actually solves the problems seen above completely:<\/p>\n<p><img class=\"lazyload\" decoding=\"async\" src=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27588%27%20height%3D%27694%27%20viewBox%3D%270%200%20588%20694%27%3E%3Crect%20width%3D%27588%27%20height%3D%27694%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-orig-src=\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/CircleCI-Dataform2-2.png\" alt=\"Settings\"><\/p>\n<p>In order to use the full refresh option in CircleCI, we need to get the Dataform command line interface (CLI) running within our Workspace which is the container in which all commands and jobs of the Workflow are run. For that we need to install it locally, get some configuration in place and finally execute the run. Here&#8217;s how this might look like:<\/p>\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">\nexecutor: node_df\nsteps:\n  - checkout\n  - attach_workspace:\n  at: .\n  - run:\n  name: run whole project with full refresh\n  command: |\n  LAST_COMMIT_MSG=$(git --no-pager log -n1 --no-merges $CIRCLE_SHA1 --pretty=format:%s)\n  # perform a full refresh if requested in commit-message:\n  if &#x5B;&#x5B; $LAST_COMMIT_MSG == *\"&#x5B;full refresh]\"* ]]]; then\n  echo \"Found request to trigger a full refresh, executing... Message: $LAST_COMMIT_MSG\"\n  sed -i 's\/&#x5B;your_project_id]\/'${GOOGLE_PROJECT_ID}'\/g' dataform.json\n  sudo npm i -g @dataform\/cli\n  echo $DF_CREDENTIALS &gt;.df-credentials.json\n  mkdir -p ~\/.dataform &amp;&amp; echo '{\n  \"allowAnonymousAnalytics\": false,\n  \"anonymousUserId\": \"'${DF_ANONYMOUS_USERID}'\"\n  }' &gt; ~\/.dataform\/settings.json\n  data form install\n  dataform run --full-refresh\n  fi\n<\/pre>\n<p>Let&#8217;s go through some of the most interesting &#8211; or least obvious &#8211; places in that job.<\/p>\n<p>On line 13,<em> [your_project_id]<\/em>  in the configuration file <em>dataform.json<\/em> is being replaced with a variable called <em>GOOGLE_PROJECT_ID<\/em>. This will be relevant if your environments are in different projects. For example, your test environment might be on <em>elegant-elephant-12345<\/em> while production is on <em>precious-panda-98765<\/em>. Depending on where you deploy, you&#8217;d have the current project ID in the variable making the job more flexible. After that we actually install the Dataform CLI using <a href=\"https:\/\/docs.npmjs.com\/about-npm\" target=\"_blank\" rel=\"noopener\">npm<\/a>.<\/p>\n<p>Now we&#8217;ve come to the fiddly part, the one I detest: getting the configuration done. I can point you in the direction here, introduce you to some basics, but I&#8217;m afraid you&#8217;ll have to get the details right yourself for your own setup. Trial and error&#8230;<\/p>\n<p>First off is the credentials file required by Dataform. Normally, you&#8217;d run the <em>init-creds<\/em> command but as that requires user input the easiest way is to do this once manually and just copy the contents of the resulting file. Store that in a secret which you then write back into a file just as seen on line 15.<\/p>\n<p>Then there&#8217;s the question of some settings to make life with the CLI easier. You&#8217;ll want to switch off Anonymous Analytics for these runs in order to avoid the CLI to prompt a question about it and also use an anonymous user ID. This can be hardcoded and written to a settings file as seen on lines 16-19.<\/p>\n<p>After that you&#8217;re set! Two more simple commands will install and run the whole project with the full refresh option. So, time for a short recap on what we&#8217;ve done so far:<\/p>\n<ul>\n<li>we found a solution to apply changes in the datamodel to BigQuery automatically &#8211; running the project with the full refresh option<\/li>\n<li>we then created a job in CircleCI that sets up an environment to run a dataform project including all configuration<\/li>\n<li>and in addition, the developer decides if this job should be run; this is decided on line 11 and the job is only run if the commit message contains the string  <em>[full refresh]<\/em>  (depending on the size of your project or tables you might only want to run a full refresh when it&#8217;s really necessary)<\/li>\n<\/ul>\n<p>There are some limitations of this solution to keep in mind. First of all, if you remove a table from the Dataform project, it will not be dropped but remain in your dataset. You would have to drop it manually if you want to save the storage cost. As well, Dataform will not accept any changes to the partitioning key or column nor changes in its datatype. Here, a manual drop of the partitioned table followed by a <em>dataform run<\/em> does the trick as well, but careful planning in advance is advised.<\/p>\n<p>Another thing to keep in mind is the size of the data. Recreating tables with a few gigabytes every now and then will work out fine but if you for some reason have a situation where you have enormous amounts of data and a huge volatility in your datamodel this won&#8217;t work well for you. But then again, off the top of my head I don&#8217;t see any solution fitting these two conflicting requirements.<\/p>\n<p>Finally, there&#8217;s the obvious but also biggest prerequisite for all of the above: that there is a source that&#8217;s persisted and thus permits for repeated complete loads of the data &#8211; call it a Data Lake or similar. If you load your data incrementally and drop the source afterwards the described solution will leave you with empty tables! Instead, we work with raw data tables keeping a complete history of the incoming data. That enables the above and can in addition also be the source for other data products that we don&#8217;t even see happening today.<\/p>\n<p>Despite the mentioned restrictions I find this solution really handy as it elegantly solves a lot of problems that each data engineer has dealt with, possibly several times. I hope you found some useful information in this article. If so, give it a like and watch out for more to come!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Manage changes in the BigQuery data model and your Dataform scripts with a reliable CI\/CD pipeline<\/p>\n","protected":false},"author":8,"featured_media":11454,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"cybocfi_hide_featured_image":"","footnotes":""},"categories":[104,110,111,102],"tags":[],"class_list":["post-11626","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd-en","category-data-en","category-dataform-en","category-delivery"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly\" \/>\n<meta property=\"og:description\" content=\"Manage changes in the BigQuery data model and your Dataform scripts with a reliable CI\/CD pipeline\" \/>\n<meta property=\"og:url\" content=\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\" \/>\n<meta property=\"og:site_name\" content=\"Predictly\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/predictly.se\" \/>\n<meta property=\"article:published_time\" content=\"2022-09-21T13:36:27+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-05-08T11:46:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1429\" \/>\n\t<meta property=\"og:image:height\" content=\"888\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Daniel Hommrich\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@predictly_se\" \/>\n<meta name=\"twitter:site\" content=\"@predictly_se\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Daniel Hommrich\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\"},\"author\":{\"name\":\"Daniel Hommrich\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/f2b3064f1d3930252aa7dbf59c04d992\"},\"headline\":\"Using CircleCI to fix CI\/CD in Dataform &#8211; Part 2\/2\",\"datePublished\":\"2022-09-21T13:36:27+00:00\",\"dateModified\":\"2024-05-08T11:46:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\"},\"wordCount\":1159,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/predictly.se\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg\",\"articleSection\":[\"CI\/CD\",\"Data\",\"Dataform\",\"Delivery\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\",\"url\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\",\"name\":\"Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg\",\"datePublished\":\"2022-09-21T13:36:27+00:00\",\"dateModified\":\"2024-05-08T11:46:39+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage\",\"url\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg\",\"contentUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg\",\"width\":1429,\"height\":888},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/predictly.se\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Delivery\",\"item\":\"https:\/\/predictly.se\/en\/insikter\/delivery\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Using CircleCI to fix CI\/CD in Dataform &#8211; Part 2\/2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/predictly.se\/en\/#website\",\"url\":\"https:\/\/predictly.se\/en\/\",\"name\":\"Predictly\",\"description\":\"Professional IT services\",\"publisher\":{\"@id\":\"https:\/\/predictly.se\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/predictly.se\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/predictly.se\/en\/#organization\",\"name\":\"Predictly\",\"url\":\"https:\/\/predictly.se\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg\",\"contentUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg\",\"width\":532,\"height\":96,\"caption\":\"Predictly\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/predictly.se\",\"https:\/\/x.com\/predictly_se\",\"https:\/\/www.linkedin.com\/company\/predictly\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/f2b3064f1d3930252aa7dbf59c04d992\",\"name\":\"Daniel Hommrich\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/65916cb75efae5810937b164c1721b4ec6b27863bf190d6d5ea734a935569984?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/65916cb75efae5810937b164c1721b4ec6b27863bf190d6d5ea734a935569984?s=96&d=mm&r=g\",\"caption\":\"Daniel Hommrich\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/","og_locale":"en_US","og_type":"article","og_title":"Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly","og_description":"Manage changes in the BigQuery data model and your Dataform scripts with a reliable CI\/CD pipeline","og_url":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/","og_site_name":"Predictly","article_publisher":"https:\/\/www.facebook.com\/predictly.se","article_published_time":"2022-09-21T13:36:27+00:00","article_modified_time":"2024-05-08T11:46:39+00:00","og_image":[{"width":1429,"height":888,"url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg","type":"image\/jpeg"}],"author":"Daniel Hommrich","twitter_card":"summary_large_image","twitter_creator":"@predictly_se","twitter_site":"@predictly_se","twitter_misc":{"Written by":"Daniel Hommrich","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#article","isPartOf":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/"},"author":{"name":"Daniel Hommrich","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/f2b3064f1d3930252aa7dbf59c04d992"},"headline":"Using CircleCI to fix CI\/CD in Dataform &#8211; Part 2\/2","datePublished":"2022-09-21T13:36:27+00:00","dateModified":"2024-05-08T11:46:39+00:00","mainEntityOfPage":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/"},"wordCount":1159,"commentCount":0,"publisher":{"@id":"https:\/\/predictly.se\/en\/#organization"},"image":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg","articleSection":["CI\/CD","Data","Dataform","Delivery"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/","url":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/","name":"Using CircleCI to fix CI\/CD in Dataform - Part 2\/2 &#8211; Predictly","isPartOf":{"@id":"https:\/\/predictly.se\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage"},"image":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg","datePublished":"2022-09-21T13:36:27+00:00","dateModified":"2024-05-08T11:46:39+00:00","breadcrumb":{"@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#primaryimage","url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg","contentUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/data-2.jpg","width":1429,"height":888},{"@type":"BreadcrumbList","@id":"https:\/\/predictly.se\/en\/using-circleci-to-fix-ci-cd-in-dataform-part-2-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/predictly.se\/en\/"},{"@type":"ListItem","position":2,"name":"Delivery","item":"https:\/\/predictly.se\/en\/insikter\/delivery\/"},{"@type":"ListItem","position":3,"name":"Using CircleCI to fix CI\/CD in Dataform &#8211; Part 2\/2"}]},{"@type":"WebSite","@id":"https:\/\/predictly.se\/en\/#website","url":"https:\/\/predictly.se\/en\/","name":"Predictly","description":"Professional IT services","publisher":{"@id":"https:\/\/predictly.se\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/predictly.se\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/predictly.se\/en\/#organization","name":"Predictly","url":"https:\/\/predictly.se\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/","url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg","contentUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg","width":532,"height":96,"caption":"Predictly"},"image":{"@id":"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/predictly.se","https:\/\/x.com\/predictly_se","https:\/\/www.linkedin.com\/company\/predictly\/"]},{"@type":"Person","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/f2b3064f1d3930252aa7dbf59c04d992","name":"Daniel Hommrich","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/65916cb75efae5810937b164c1721b4ec6b27863bf190d6d5ea734a935569984?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/65916cb75efae5810937b164c1721b4ec6b27863bf190d6d5ea734a935569984?s=96&d=mm&r=g","caption":"Daniel Hommrich"}}]}},"_links":{"self":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11626","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/comments?post=11626"}],"version-history":[{"count":1,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11626\/revisions"}],"predecessor-version":[{"id":11637,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11626\/revisions\/11637"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media\/11454"}],"wp:attachment":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media?parent=11626"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/categories?post=11626"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/tags?post=11626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}